1+ name: Build and Deploy Documentation
2+
3+ "on":
4+ push:
5+ branches: [ "master", "main" ]
6+ pull_request:
7+ branches: [ "master", "main" ]
8+ types: [opened, synchronize]
9+ # Allow manual trigger
10+ workflow_dispatch: {}
11+
12+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13+ permissions:
14+ contents: read
15+ pages: write
16+ id-token: write
17+
18+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20+ concurrency:
21+ group: "pages"
22+ cancel-in-progress: false
23+
24+ jobs:
25+ build-docs:
26+ runs-on: ubuntu-latest
27+ name: Build Documentation
28+
29+ steps:
30+ - name: Checkout repository
31+ uses: actions/checkout@v4
32+ with:
33+ submodules: recursive # Include helios-core submodule for doc assets
34+ fetch-depth: 0 # Fetch full history including tags
35+
36+ - name: Set up Python 3.11
37+ uses: actions/setup-python@v4
38+ with:
39+ python-version: "3.11"
40+
41+ - name: Install system dependencies
42+ run: |
43+ sudo apt-get update
44+ sudo apt-get install -y graphviz wget
45+
46+ # Install Doxygen 1.13.2 from source to match local build
47+ # Different Doxygen versions generate different anchor hashes, breaking search links
48+ wget https://www.doxygen.nl/files/doxygen-1.13.2.linux.bin.tar.gz
49+ tar -xzf doxygen-1.13.2.linux.bin.tar.gz
50+ sudo cp doxygen-1.13.2/bin/doxygen /usr/local/bin/
51+ sudo chmod +x /usr/local/bin/doxygen
52+
53+ - name: Install Python dependencies
54+ run: |
55+ python -m pip install --upgrade pip
56+ pip install doxypypy
57+ pip install -r requirements.txt
58+ pip install -e .
59+
60+ - name: Verify Doxygen installation
61+ run: |
62+ doxygen --version
63+ doxypypy --help
64+
65+ - name: Sync version to Doxygen config
66+ run: |
67+ # Get version from git tags (remove 'v' prefix if present)
68+ VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "unknown")
69+ if [ "$VERSION" = "unknown" ]; then
70+ echo "Warning: Could not determine version from git tags, using fallback"
71+ # Fallback to setuptools-scm if available
72+ VERSION=$(python -c "import setuptools_scm; print(setuptools_scm.get_version())" 2>/dev/null | sed 's/^v//' || echo "dev")
73+ fi
74+ echo "Updating Doxygen PROJECT_NUMBER to: $VERSION"
75+ sed -i "s/^PROJECT_NUMBER.*$/PROJECT_NUMBER = $VERSION/" docs/Doxyfile.python
76+
77+ - name: Build documentation
78+ run: |
79+ # Create output directory
80+ mkdir -p docs/generated
81+
82+ # Build documentation with Doxygen
83+ doxygen docs/Doxyfile.python
84+
85+ # Create .nojekyll file to disable Jekyll processing on GitHub Pages
86+ # This is critical because Jekyll ignores files starting with underscores
87+ # which breaks Doxygen navigation (many generated files start with _)
88+ touch docs/generated/html/.nojekyll
89+
90+ # Verify documentation was generated
91+ ls -la docs/generated/html/
92+ echo "✓ Documentation built successfully"
93+
94+ - name: Validate Doxygen artifacts
95+ run: |
96+ echo "=== Doxygen Documentation Validation ==="
97+
98+ VALIDATION_FAILED=0
99+ DOC_DIR="docs/generated/html"
100+
101+ # 1. Check critical files
102+ echo ""
103+ echo "[1/5] Checking critical files..."
104+ REQUIRED_FILES=(
105+ "index.html"
106+ "doxygen-awesome.css"
107+ ".nojekyll"
108+ "search/search.js"
109+ )
110+
111+ for file in "${REQUIRED_FILES[@]}"; do
112+ if [ ! -f "$DOC_DIR/$file" ]; then
113+ echo " ERROR: MISSING: $file"
114+ VALIDATION_FAILED=1
115+ else
116+ size=$(stat -c%s "$DOC_DIR/$file" 2>/dev/null)
117+ echo " OK: $file (${size} bytes)"
118+ fi
119+ done
120+
121+ # 2. Validate index.html structure
122+ echo ""
123+ echo "[2/5] Validating index.html structure..."
124+ if [ -f "$DOC_DIR/index.html" ]; then
125+ if grep -q "<title>" "$DOC_DIR/index.html" && \
126+ grep -q "</body>" "$DOC_DIR/index.html"; then
127+ echo " OK: index.html appears well-formed"
128+ else
129+ echo " ERROR: index.html appears malformed"
130+ VALIDATION_FAILED=1
131+ fi
132+ fi
133+
134+ # 3. Check for underscore files (should work with .nojekyll)
135+ echo ""
136+ echo "[3/5] Checking for Doxygen underscore files..."
137+ UNDERSCORE_COUNT=$(find "$DOC_DIR" -name "_*.js" -o -name "_*.html" | wc -l)
138+ if [ "$UNDERSCORE_COUNT" -gt 0 ]; then
139+ echo " OK: Found $UNDERSCORE_COUNT underscore files (normal for Doxygen)"
140+ if [ ! -f "$DOC_DIR/.nojekyll" ]; then
141+ echo " ERROR: .nojekyll missing - these files will be ignored by Jekyll!"
142+ VALIDATION_FAILED=1
143+ fi
144+ fi
145+
146+ # 4. Verify directory structure
147+ echo ""
148+ echo "[4/5] Checking directory structure..."
149+ for dir in "search" "classes" "files"; do
150+ if [ -d "$DOC_DIR/$dir" ]; then
151+ count=$(find "$DOC_DIR/$dir" -type f | wc -l)
152+ echo " OK: $dir/ ($count files)"
153+ fi
154+ done
155+
156+ # 5. Size check (GitHub Pages limit: 1GB recommended, 10GB max)
157+ echo ""
158+ echo "[5/5] Checking total size..."
159+ TOTAL_SIZE=$(du -sk "$DOC_DIR" | cut -f1)
160+ TOTAL_MB=$((TOTAL_SIZE / 1024))
161+ echo " Total size: ${TOTAL_MB} MB"
162+
163+ if [ "$TOTAL_SIZE" -gt 1048576 ]; then # 1GB in KB
164+ echo " WARNING: Size exceeds 1GB (GitHub Pages recommended limit)"
165+ elif [ "$TOTAL_SIZE" -gt 10485760 ]; then # 10GB in KB
166+ echo " ERROR: Size exceeds 10GB (GitHub Pages absolute limit)"
167+ VALIDATION_FAILED=1
168+ else
169+ echo " OK: Size within limits"
170+ fi
171+
172+ # Final verdict
173+ echo ""
174+ echo "=== Validation Complete ==="
175+ if [ $VALIDATION_FAILED -eq 1 ]; then
176+ echo "VALIDATION FAILED"
177+ exit 1
178+ else
179+ echo "ALL CHECKS PASSED"
180+ fi
181+
182+ - name: Setup Pages
183+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
184+ uses: actions/configure-pages@v4
185+
186+ - name: Upload artifact
187+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
188+ uses: actions/upload-pages-artifact@v3
189+ with:
190+ path: docs/generated/html/
191+
192+ deploy-docs:
193+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
194+ outputs:
195+ page_url: ${{ steps.deployment.outputs.page_url }}
196+ environment:
197+ name: github-pages
198+ url: ${{ steps.deployment.outputs.page_url }}
199+ runs-on: ubuntu-latest
200+ needs: build-docs
201+
202+ steps:
203+ - name: Deploy to GitHub Pages
204+ id: deployment
205+ uses: actions/deploy-pages@v4
206+
207+ verify-deployment:
208+ name: Verify Deployment
209+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
210+ runs-on: ubuntu-latest
211+ needs: deploy-docs
212+
213+ steps:
214+ - name: Wait for GitHub Pages propagation and verify
215+ timeout-minutes: 10
216+ continue-on-error: true
217+ id: verify
218+ run: |
219+ SITE_URL="${{ needs.deploy-docs.outputs.page_url }}"
220+ echo "=== GitHub Pages Deployment Verification ==="
221+ echo "Target URL: $SITE_URL"
222+ echo ""
223+
224+ MAX_ATTEMPTS=15
225+ RETRY_DELAY=30
226+ ATTEMPT=0
227+
228+ echo "Note: GitHub Pages CDN propagation typically takes 1-10 minutes"
229+ echo "Will retry up to $MAX_ATTEMPTS times with ${RETRY_DELAY}s delays"
230+ echo ""
231+
232+ while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
233+ ATTEMPT=$((ATTEMPT + 1))
234+ echo "[$ATTEMPT/$MAX_ATTEMPTS] Checking site accessibility..."
235+
236+ # Attempt to fetch the site
237+ HTTP_CODE=$(curl -s -o /tmp/page.html -w "%{http_code}" \
238+ --connect-timeout 10 \
239+ --max-time 30 \
240+ "$SITE_URL" || echo "000")
241+
242+ case "$HTTP_CODE" in
243+ 200)
244+ echo " OK: Site returned HTTP 200"
245+
246+ # Verify it's actually our documentation
247+ if grep -q "doxygen" /tmp/page.html || grep -q "PyHelios" /tmp/page.html; then
248+ echo " OK: Content verified (PyHelios documentation detected)"
249+
250+ # Check for common Doxygen elements
251+ if grep -q "search" /tmp/page.html; then
252+ echo " OK: Search functionality present"
253+ fi
254+
255+ echo ""
256+ echo "DEPLOYMENT VERIFIED SUCCESSFULLY"
257+ exit 0
258+ else
259+ echo " WARNING: Page returned 200 but doesn't appear to be PyHelios docs"
260+ echo " Continuing retries..."
261+ fi
262+ ;;
263+ 404)
264+ echo " Site not found (HTTP 404) - waiting for propagation..."
265+ ;;
266+ 000)
267+ echo " Connection failed - waiting for deployment..."
268+ ;;
269+ *)
270+ echo " Received HTTP $HTTP_CODE - waiting..."
271+ ;;
272+ esac
273+
274+ if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
275+ echo " Waiting ${RETRY_DELAY}s before retry..."
276+ echo ""
277+ sleep $RETRY_DELAY
278+ fi
279+ done
280+
281+ echo ""
282+ echo "VERIFICATION FAILED"
283+ echo "Site did not become accessible after $MAX_ATTEMPTS attempts"
284+ echo ""
285+ echo "This may indicate:"
286+ echo " - Deployment is still propagating (can take up to 20-30 minutes)"
287+ echo " - GitHub Pages service issues"
288+ echo " - Intermittent CDN problems (try re-running the workflow)"
289+ echo ""
290+ echo "Manual verification recommended: $SITE_URL"
291+ exit 1
292+
293+ - name: Report verification result
294+ if: always()
295+ run: |
296+ if [ "${{ steps.verify.outcome }}" == "success" ]; then
297+ echo "::notice::Documentation successfully deployed and verified at ${{ needs.deploy-docs.outputs.page_url }}"
298+ else
299+ echo "::warning::Deployment verification failed or timed out. Site may still be propagating. Manual check: ${{ needs.deploy-docs.outputs.page_url }}"
300+ fi
0 commit comments