1+ # .github/actions/build-docc/action.yml
2+ name : ' Build DocC Documentation'
3+ description : ' Builds Swift DocC documentation for multiple products and platforms'
4+
5+ inputs :
6+ products :
7+ description : ' Comma-separated list of product names (e.g., "ValidatorCore,ValidatorUI")'
8+ required : true
9+ docc-paths :
10+ description : ' Comma-separated list of .docc paths (e.g., "Sources/ValidatorCore/Validator.docc,Sources/ValidatorUI/ValidatorUI.docc")'
11+ required : false
12+ default : ' '
13+ bundle-identifier-prefix :
14+ description : ' Bundle identifier prefix (e.g., "dev.validator")'
15+ required : false
16+ default : ' dev.package'
17+ bundle-version :
18+ description : ' Bundle version for documentation'
19+ required : false
20+ default : ' 1.0.0'
21+ platforms :
22+ description : ' Comma-separated list of platforms to build for (e.g., "iOS,macOS,watchOS,tvOS,visionOS")'
23+ required : false
24+ default : ' iOS,macOS,watchOS,tvOS,visionOS'
25+ output-path :
26+ description : ' Output directory for generated documentation'
27+ required : false
28+ default : ' ./docs'
29+ hosting-base-path :
30+ description : ' Base path for static hosting (leave empty for root)'
31+ required : false
32+ default : ' '
33+
34+ runs :
35+ using : ' composite'
36+ steps :
37+ - name : Verify Schemes
38+ shell : bash
39+ run : |
40+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
41+ echo "Verifying available schemes"
42+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
43+ xcodebuild -list
44+ echo ""
45+
46+ IFS=',' read -ra PRODUCTS <<< "${{ inputs.products }}"
47+ for PRODUCT in "${PRODUCTS[@]}"; do
48+ if xcodebuild -list | grep -q "^\s*${PRODUCT}\s*$"; then
49+ echo "✓ Scheme '${PRODUCT}' found"
50+ else
51+ echo "✗ Scheme '${PRODUCT}' NOT FOUND"
52+ echo " Available schemes:"
53+ xcodebuild -list | grep -A 100 "Schemes:" | grep "^\s" | head -20
54+ exit 1
55+ fi
56+ done
57+
58+ - name : Setup Build Directories
59+ shell : bash
60+ run : |
61+ mkdir -p .build/symbol-graphs
62+ mkdir -p ${{ inputs.output-path }}
63+
64+ - name : Build Symbol Graphs
65+ shell : bash
66+ run : |
67+ IFS=',' read -ra PRODUCTS <<< "${{ inputs.products }}"
68+ IFS=',' read -ra PLATFORMS <<< "${{ inputs.platforms }}"
69+
70+ for PRODUCT in "${PRODUCTS[@]}"; do
71+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
72+ echo "Building symbol graphs for ${PRODUCT}"
73+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
74+
75+ for PLATFORM in "${PLATFORMS[@]}"; do
76+ echo "→ Platform: ${PLATFORM}"
77+ SYMBOL_DIR=".build/symbol-graphs/${PRODUCT}/${PLATFORM}"
78+ mkdir -p "${SYMBOL_DIR}"
79+
80+ xcodebuild build \
81+ -scheme "${PRODUCT}" \
82+ -destination "generic/platform=${PLATFORM}" \
83+ -derivedDataPath .deriveddata \
84+ DOCC_EXTRACT_EXTENSION_SYMBOLS=YES \
85+ OTHER_SWIFT_FLAGS="-Xfrontend -emit-symbol-graph -Xfrontend -emit-symbol-graph-dir -Xfrontend ${SYMBOL_DIR} -Xfrontend -emit-extension-block-symbols"
86+
87+ BUILD_STATUS=$?
88+ if [ $BUILD_STATUS -eq 0 ]; then
89+ echo "✓ ${PRODUCT} built successfully for ${PLATFORM}"
90+ echo " Symbol graphs in: ${SYMBOL_DIR}"
91+ ls -la "${SYMBOL_DIR}" || echo " (empty)"
92+ else
93+ echo "✗ ${PRODUCT} build failed for ${PLATFORM} (exit code: ${BUILD_STATUS})"
94+ fi
95+ done
96+ done
97+
98+ - name : Generate Documentation
99+ shell : bash
100+ run : |
101+ IFS=',' read -ra PRODUCTS <<< "${{ inputs.products }}"
102+ IFS=',' read -ra DOCC_PATHS <<< "${{ inputs.docc-paths }}"
103+
104+ for i in "${!PRODUCTS[@]}"; do
105+ PRODUCT="${PRODUCTS[$i]}"
106+ DOCC_PATH=""
107+
108+ if [ ${#DOCC_PATHS[@]} -gt $i ]; then
109+ DOCC_PATH="${DOCC_PATHS[$i]}"
110+ fi
111+
112+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
113+ echo "Generating documentation for ${PRODUCT}"
114+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
115+
116+ BUNDLE_ID="${{ inputs.bundle-identifier-prefix }}.${PRODUCT}"
117+ SYMBOL_GRAPHS_DIR=".build/symbol-graphs/${PRODUCT}"
118+
119+ # Check if symbol graphs exist
120+ if [ -d "${SYMBOL_GRAPHS_DIR}" ]; then
121+ SYMBOL_COUNT=$(find "${SYMBOL_GRAPHS_DIR}" -name "*.symbols.json" | wc -l)
122+ echo "Found ${SYMBOL_COUNT} symbol graph files"
123+
124+ if [ $SYMBOL_COUNT -gt 0 ]; then
125+ echo "Symbol graph files:"
126+ find "${SYMBOL_GRAPHS_DIR}" -name "*.symbols.json" -exec basename {} \; | head -10
127+
128+ # Count public symbols
129+ TOTAL_SYMBOLS=0
130+ for SYMBOL_FILE in $(find "${SYMBOL_GRAPHS_DIR}" -name "*.symbols.json"); do
131+ SYMBOL_FILE_COUNT=$(jq '[.symbols[].kind.identifier] | length' "${SYMBOL_FILE}" 2>/dev/null || echo "0")
132+ TOTAL_SYMBOLS=$((TOTAL_SYMBOLS + SYMBOL_FILE_COUNT))
133+ done
134+ echo "Total public symbols found: ${TOTAL_SYMBOLS}"
135+
136+ if [ $TOTAL_SYMBOLS -eq 0 ]; then
137+ echo "⚠ Warning: Symbol graphs exist but contain no public symbols"
138+ echo " Make sure your types/functions are declared as 'public' or 'open'"
139+ fi
140+ else
141+ echo "⚠ Warning: No symbol graphs found for ${PRODUCT}"
142+ echo " This usually means:"
143+ echo " - The build failed"
144+ echo " - There are no public APIs (everything is internal/private)"
145+ echo " - The scheme doesn't include the right targets"
146+ fi
147+ else
148+ echo "⚠ Warning: Symbol graphs directory not found: ${SYMBOL_GRAPHS_DIR}"
149+ fi
150+
151+ if [ -n "${DOCC_PATH}" ] && [ -d "${DOCC_PATH}" ]; then
152+ echo "Using .docc catalog: ${DOCC_PATH}"
153+ echo "Contents of .docc catalog:"
154+ ls -la "${DOCC_PATH}"
155+
156+ $(xcrun --find docc) convert "${DOCC_PATH}" \
157+ --fallback-display-name "${PRODUCT}" \
158+ --fallback-bundle-identifier "${BUNDLE_ID}" \
159+ --fallback-bundle-version "${{ inputs.bundle-version }}" \
160+ --output-dir "${PRODUCT}.doccarchive" \
161+ --additional-symbol-graph-dir "${SYMBOL_GRAPHS_DIR}"
162+ else
163+ echo "⚠ .docc catalog not found at: ${DOCC_PATH}"
164+ echo "Generating from symbol graphs only"
165+
166+ if [ ! -d "${SYMBOL_GRAPHS_DIR}" ] || [ $(find "${SYMBOL_GRAPHS_DIR}" -name "*.symbols.json" | wc -l) -eq 0 ]; then
167+ echo "✗ Error: Cannot generate documentation without .docc catalog or symbol graphs"
168+ continue
169+ fi
170+
171+ $(xcrun --find docc) convert \
172+ --fallback-display-name "${PRODUCT}" \
173+ --fallback-bundle-identifier "${BUNDLE_ID}" \
174+ --fallback-bundle-version "${{ inputs.bundle-version }}" \
175+ --output-dir "${PRODUCT}.doccarchive" \
176+ --additional-symbol-graph-dir "${SYMBOL_GRAPHS_DIR}"
177+ fi
178+
179+ DOCC_STATUS=$?
180+ if [ $DOCC_STATUS -ne 0 ]; then
181+ echo "✗ Failed to generate documentation for ${PRODUCT}"
182+ continue
183+ fi
184+
185+ # Check if .doccarchive was created
186+ if [ ! -d "${PRODUCT}.doccarchive" ]; then
187+ echo "✗ Error: ${PRODUCT}.doccarchive was not created"
188+ continue
189+ fi
190+
191+ echo "✓ Documentation archive created"
192+
193+ # Transform for static hosting
194+ if [ -n "${{ inputs.hosting-base-path }}" ]; then
195+ BASE_PATH="${{ inputs.hosting-base-path }}/${PRODUCT}"
196+ else
197+ BASE_PATH="${PRODUCT}"
198+ fi
199+
200+ echo "Transforming for static hosting with base path: ${BASE_PATH}"
201+
202+ $(xcrun --find docc) process-archive transform-for-static-hosting \
203+ "${PRODUCT}.doccarchive" \
204+ --output-path "${{ inputs.output-path }}/${PRODUCT}" \
205+ --hosting-base-path "${BASE_PATH}"
206+
207+ TRANSFORM_STATUS=$?
208+ if [ $TRANSFORM_STATUS -eq 0 ]; then
209+ echo "✓ ${PRODUCT} documentation generated successfully"
210+ echo " Output: ${{ inputs.output-path }}/${PRODUCT}"
211+ ls -la "${{ inputs.output-path }}/${PRODUCT}" | head -10
212+ else
213+ echo "✗ Failed to transform ${PRODUCT} for static hosting"
214+ fi
215+ done
216+
217+ - name : Verify Generated Documentation
218+ shell : bash
219+ run : |
220+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
221+ echo "Verifying generated documentation structure"
222+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
223+
224+ IFS=',' read -ra PRODUCTS <<< "${{ inputs.products }}"
225+
226+ for PRODUCT in "${PRODUCTS[@]}"; do
227+ DOCS_DIR="${{ inputs.output-path }}/${PRODUCT}"
228+
229+ if [ ! -d "${DOCS_DIR}" ]; then
230+ echo "✗ Documentation directory not found: ${DOCS_DIR}"
231+ continue
232+ fi
233+
234+ echo "Checking ${PRODUCT}:"
235+
236+ # Check for essential files
237+ if [ -f "${DOCS_DIR}/index.html" ]; then
238+ echo " ✓ index.html exists"
239+ else
240+ echo " ✗ index.html MISSING"
241+ fi
242+
243+ if [ -d "${DOCS_DIR}/documentation" ]; then
244+ echo " ✓ documentation/ directory exists"
245+ DOC_COUNT=$(find "${DOCS_DIR}/documentation" -name "*.html" | wc -l)
246+ echo " Found ${DOC_COUNT} HTML documentation pages"
247+ else
248+ echo " ✗ documentation/ directory MISSING"
249+ fi
250+
251+ if [ -d "${DOCS_DIR}/data" ]; then
252+ echo " ✓ data/ directory exists"
253+ JSON_COUNT=$(find "${DOCS_DIR}/data" -name "*.json" | wc -l)
254+ echo " Found ${JSON_COUNT} JSON data files"
255+ else
256+ echo " ✗ data/ directory MISSING"
257+ fi
258+
259+ echo ""
260+ done
261+
262+ - name : Cleanup Build Artifacts
263+ shell : bash
264+ if : always()
265+ run : |
266+ rm -rf .deriveddata
267+ rm -rf .build
268+ rm -rf *.doccarchive
0 commit comments