Files
cs249r_book/.github/workflows/book-validate-dev.yml
Vijay Janapa Reddi 4f44655224 Grant actions read permission to dev validation workflow.
Allow reusable container workflow status queries from the caller and fix a stylesheet spelling issue required by pre-commit checks.
2026-03-05 17:35:07 -05:00

430 lines
18 KiB
YAML

name: '📚 Book · ✅ Validate (Dev)'
# Cancel any running validation and start fresh when new one is triggered
# Concurrency disabled - allow unlimited parallel builds
# Triggers: dev branch pushes (book/ changes only) and manual testing
on:
push:
branches: [dev] # Auto-trigger for dev branch
paths:
- 'book/**'
- '.github/workflows/book-*.yml'
- '!tinytorch/**' # Exclude TinyTorch changes
workflow_dispatch: # Manual trigger for testing
inputs:
test_branch:
description: 'Branch to test (for feature branch testing)'
required: false
default: ''
type: string
target:
description: 'Target branch for build artifacts (dev/main)'
required: false
type: choice
default: 'dev'
options:
- dev
- main
# Format & OS selection
build_formats:
description: '📦 Build formats (all, html, pdf, epub)'
required: true
default: 'all'
type: choice
options:
- all
- html
- pdf
- epub
build_os:
description: '⚙️ Build OS (both, linux, windows)'
required: true
default: 'both'
type: choice
options:
- both
- linux
- windows
# Container validation
run_health_check:
description: '💊 Run container health check'
required: false
default: false
type: boolean
# Container configuration (consistent with other workflows)
container_registry:
description: 'Container registry URL'
required: false
default: 'ghcr.io'
type: string
container_tag:
description: 'Container tag to use'
required: false
default: 'latest'
type: string
permissions:
contents: write
pages: write
packages: read
actions: read
jobs:
# Step 1: Pre-commit validation
pre-commit:
name: '🔍 Pre-commit Checks'
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ inputs.test_branch || github.ref }}
fetch-depth: 0
- name: 🐍 Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: 🔍 Run pre-commit checks
run: |
echo "🔍 Installing and running pre-commit..."
python -m pip install --upgrade pip
pip install pre-commit
echo "📦 Installing project dependencies for custom hooks..."
pip install -r requirements.txt || echo "⚠️ Could not install all requirements, continuing..."
echo "📦 Installing fallback hook dependencies..."
pip install rich requests pillow pint pandas || echo "⚠️ Could not install one or more fallback deps, continuing..."
echo "🔧 Installing pre-commit hooks..."
pre-commit install --install-hooks
echo "🔄 Running pre-commit on all files..."
echo "Python version: $(python --version)"
if pre-commit run --all-files --verbose; then
echo "✅ Pre-commit checks: PASSED"
else
echo "❌ Pre-commit checks: FAILED"
echo "📋 Showing detailed output:"
pre-commit run --all-files --verbose || true
exit 1
fi
# Step 2: Container Health Check (validate containers before builds)
container-health-check:
name: '💊 Container Health Check'
needs: [build-config]
if: inputs.run_health_check == true
uses: ./.github/workflows/infra-health-check.yml # Shared workflow - no book- prefix
with:
test_linux: true
test_windows: true
container_registry: 'ghcr.io'
container_tag: 'latest'
# Step 3: Update contributors (after validation passes, before build matrix)
update-contributors:
name: '👥 Update Contributors'
needs: [pre-commit]
if: needs.pre-commit.result == 'success' && github.event_name == 'push'
uses: ./.github/workflows/update-contributors.yml
# Step 4: Determine build configuration (shared logic)
build-config:
name: '🎯 Build Configuration'
needs: [pre-commit, update-contributors]
if: always() && needs.pre-commit.result == 'success' && (needs.update-contributors.result == 'success' || needs.update-contributors.result == 'skipped')
runs-on: ubuntu-latest
outputs:
build_html: ${{ steps.config.outputs.build_html }}
build_pdf: ${{ steps.config.outputs.build_pdf }}
build_epub: ${{ steps.config.outputs.build_epub }}
build_linux: ${{ steps.config.outputs.build_linux }}
build_windows: ${{ steps.config.outputs.build_windows }}
container_registry: ${{ steps.config.outputs.container_registry }}
container_tag: ${{ steps.config.outputs.container_tag }}
target: ${{ steps.config.outputs.target }}
steps:
- name: 🎯 Determine build configuration
id: config
run: |
echo "🎯 === BUILD CONFIGURATION ==="
# Determine what to build based on inputs (individual format flags)
if [ "${{ github.event_name }}" = "push" ]; then
# Auto-trigger: build everything
BUILD_HTML="true"
BUILD_PDF="true"
BUILD_EPUB="true"
BUILD_LINUX="true"
BUILD_WINDOWS="true"
TARGET="dev" # For push events, always target dev
echo "📊 Trigger: Automatic (dev branch push)"
echo "📊 HTML: $BUILD_HTML (mandatory)"
echo "📊 PDF: $BUILD_PDF (mandatory)"
echo "📊 EPUB: $BUILD_EPUB (mandatory)"
echo "📊 Linux: $BUILD_LINUX (mandatory)"
echo "📊 Windows: $BUILD_WINDOWS (enabled by default)"
echo "📊 Method: container-only"
echo "📊 Target: $TARGET"
else
# Manual trigger: respect choices
echo "📊 Trigger: Manual workflow dispatch"
BUILD_HTML="false"
if [[ "${{ inputs.build_formats }}" == "all" || "${{ inputs.build_formats }}" == "html" ]]; then
BUILD_HTML="true"
fi
BUILD_PDF="false"
if [[ "${{ inputs.build_formats }}" == "all" || "${{ inputs.build_formats }}" == "pdf" ]]; then
BUILD_PDF="true"
fi
BUILD_EPUB="false"
if [[ "${{ inputs.build_formats }}" == "all" || "${{ inputs.build_formats }}" == "epub" ]]; then
BUILD_EPUB="true"
fi
BUILD_LINUX="false"
if [[ "${{ inputs.build_os }}" == "both" || "${{ inputs.build_os }}" == "linux" ]]; then
BUILD_LINUX="true"
fi
BUILD_WINDOWS="false"
if [[ "${{ inputs.build_os }}" == "both" || "${{ inputs.build_os }}" == "windows" ]]; then
BUILD_WINDOWS="true"
fi
TARGET="${{ inputs.target }}"
echo "📊 Formats: ${{ inputs.build_formats }} (HTML: $BUILD_HTML, PDF: $BUILD_PDF)"
echo "📊 OS: ${{ inputs.build_os }} (Linux: $BUILD_LINUX, Windows: $BUILD_WINDOWS)"
echo "📊 Method: container-only"
echo "📊 Target: $TARGET"
fi
# Container configuration
CONTAINER_REGISTRY="${{ inputs.container_registry || 'ghcr.io' }}"
CONTAINER_TAG="${{ inputs.container_tag || 'latest' }}"
echo "📊 Final Configuration:"
echo " HTML: $BUILD_HTML"
echo " PDF: $BUILD_PDF"
echo " Linux: $BUILD_LINUX"
echo " Windows: $BUILD_WINDOWS"
echo " Method: container"
echo " Registry: $CONTAINER_REGISTRY"
echo " Tag: $CONTAINER_TAG"
echo " Target: $TARGET"
# Set outputs (individual format flags for matrix-driven approach)
echo "build_html=$BUILD_HTML" >> $GITHUB_OUTPUT
echo "build_pdf=$BUILD_PDF" >> $GITHUB_OUTPUT
echo "build_epub=$BUILD_EPUB" >> $GITHUB_OUTPUT
echo "build_linux=$BUILD_LINUX" >> $GITHUB_OUTPUT
echo "build_windows=$BUILD_WINDOWS" >> $GITHUB_OUTPUT
echo "container_registry=$CONTAINER_REGISTRY" >> $GITHUB_OUTPUT
echo "container_tag=$CONTAINER_TAG" >> $GITHUB_OUTPUT
echo "target=$TARGET" >> $GITHUB_OUTPUT
# Step 4: Matrix-driven build calls (clean and simple!)
build-container:
name: '🐳 Container Build Matrix'
needs: [build-config]
if: |
always() &&
(needs.build-config.result == 'success')
uses: ./.github/workflows/book-build-container.yml
with:
build_linux: ${{ needs.build-config.outputs.build_linux == 'true' }}
build_windows: ${{ needs.build-config.outputs.build_windows == 'true' }}
build_html: ${{ needs.build-config.outputs.build_html == 'true' }}
build_pdf: ${{ needs.build-config.outputs.build_pdf == 'true' }}
build_epub: ${{ needs.build-config.outputs.build_epub == 'true' }}
build_target: all
target: ${{ needs.build-config.outputs.target }}
container_registry: ${{ needs.build-config.outputs.container_registry }}
container_tag: ${{ needs.build-config.outputs.container_tag }}
# Step 4: Collect build results (needed for outputs)
collect-results:
name: '📊 Collect Results'
needs: [build-config, build-container]
runs-on: ubuntu-latest
if: always()
outputs:
linux_success: ${{ steps.collect.outputs.linux_success }}
windows_success: ${{ steps.collect.outputs.windows_success }}
linux_html_vol1_artifact: ${{ steps.collect.outputs.linux_html_vol1_artifact }}
linux_html_vol2_artifact: ${{ steps.collect.outputs.linux_html_vol2_artifact }}
linux_pdf_vol1_artifact: ${{ steps.collect.outputs.linux_pdf_vol1_artifact }}
linux_pdf_vol2_artifact: ${{ steps.collect.outputs.linux_pdf_vol2_artifact }}
linux_epub_vol1_artifact: ${{ steps.collect.outputs.linux_epub_vol1_artifact }}
linux_epub_vol2_artifact: ${{ steps.collect.outputs.linux_epub_vol2_artifact }}
steps:
- name: 📊 Collect build results
id: collect
run: |
echo "📊 === COLLECTING BUILD RESULTS ==="
# These will hold the final results, preferring container builds
OVERALL_SUCCESS="false"
LINUX_HTML_VOL1=""
LINUX_HTML_VOL2=""
LINUX_PDF_VOL1=""
LINUX_PDF_VOL2=""
LINUX_EPUB_VOL1=""
LINUX_EPUB_VOL2=""
# Container-only results
if [[ "${{ needs.build-container.result }}" == "success" ]]; then
echo "✅ Container builds completed successfully."
OVERALL_SUCCESS="${{ needs.build-container.outputs.build_success }}"
LINUX_HTML_VOL1="${{ needs.build-container.outputs.linux_html_vol1_artifact }}"
LINUX_HTML_VOL2="${{ needs.build-container.outputs.linux_html_vol2_artifact }}"
LINUX_PDF_VOL1="${{ needs.build-container.outputs.linux_pdf_vol1_artifact }}"
LINUX_PDF_VOL2="${{ needs.build-container.outputs.linux_pdf_vol2_artifact }}"
LINUX_EPUB_VOL1="${{ needs.build-container.outputs.linux_epub_vol1_artifact }}"
LINUX_EPUB_VOL2="${{ needs.build-container.outputs.linux_epub_vol2_artifact }}"
else
echo "❌ Container builds failed or were skipped."
OVERALL_SUCCESS="false"
fi
# Determine per-platform success based on overall success and which platforms were built
LINUX_SUCCESS="false"
if [[ "${{ needs.build-config.outputs.build_linux }}" == "true" && "$OVERALL_SUCCESS" == "true" ]]; then
LINUX_SUCCESS="true"
fi
WINDOWS_SUCCESS="false"
if [[ "${{ needs.build-config.outputs.build_windows }}" == "true" && "$OVERALL_SUCCESS" == "true" ]]; then
WINDOWS_SUCCESS="true"
fi
echo "Final Success Status: Linux -> $LINUX_SUCCESS, Windows -> $WINDOWS_SUCCESS"
# Set outputs
echo "linux_success=$LINUX_SUCCESS" >> $GITHUB_OUTPUT
echo "windows_success=$WINDOWS_SUCCESS" >> $GITHUB_OUTPUT
echo "linux_html_vol1_artifact=$LINUX_HTML_VOL1" >> $GITHUB_OUTPUT
echo "linux_html_vol2_artifact=$LINUX_HTML_VOL2" >> $GITHUB_OUTPUT
echo "linux_pdf_vol1_artifact=$LINUX_PDF_VOL1" >> $GITHUB_OUTPUT
echo "linux_pdf_vol2_artifact=$LINUX_PDF_VOL2" >> $GITHUB_OUTPUT
echo "linux_epub_vol1_artifact=$LINUX_EPUB_VOL1" >> $GITHUB_OUTPUT
echo "linux_epub_vol2_artifact=$LINUX_EPUB_VOL2" >> $GITHUB_OUTPUT
# Step 5: Final results and summary
validation-summary:
name: '🎯 Validation Summary'
if: always()
needs: [pre-commit, build-config, collect-results]
runs-on: ubuntu-latest
steps:
- name: 🎯 Generate validation summary
run: |
echo "🎯 === VALIDATION SUMMARY ==="
echo "=============================="
echo ""
# Visual status indicators
echo "🔍 RESULTS:"
echo "----------"
if [ "${{ needs.pre-commit.result }}" = "success" ]; then
echo "✅ Pre-commit checks: PASSED"
else
echo "❌ Pre-commit checks: FAILED"
fi
if [ "${{ needs.build-config.outputs.build_linux }}" = "true" ]; then
if [ "${{ needs.collect-results.outputs.linux_success }}" = "true" ]; then
echo "✅ Linux build: PASSED"
else
echo "❌ Linux build: FAILED"
fi
else
echo "⏭️ Linux build: SKIPPED"
fi
if [ "${{ needs.build-config.outputs.build_windows }}" = "true" ]; then
if [ "${{ needs.collect-results.outputs.windows_success }}" = "true" ]; then
echo "✅ Windows build: PASSED"
else
echo "❌ Windows build: FAILED"
fi
else
echo "⏭️ Windows build: SKIPPED"
fi
echo ""
echo "🎯 OVERALL STATUS:"
echo "-----------------"
# Determine overall success
PRECOMMIT_OK="${{ needs.pre-commit.result == 'success' }}"
LINUX_OK="${{ needs.collect-results.outputs.linux_success == 'true' || needs.build-config.outputs.build_linux == 'false' }}"
WINDOWS_OK="${{ needs.collect-results.outputs.windows_success == 'true' || needs.build-config.outputs.build_windows == 'false' }}"
if [ "$PRECOMMIT_OK" = "true" ] && [ "$LINUX_OK" = "true" ] && [ "$WINDOWS_OK" = "true" ]; then
echo "🟢 VALIDATION: FULLY OPERATIONAL ✅"
echo " All enabled checks passed successfully!"
echo "🚀 PREVIEW: DEPLOYMENT TRIGGERED ✅"
echo " A separate workflow will deploy the preview to the staging site."
else
echo "🔴 VALIDATION: ISSUES DETECTED ❌"
echo " Check the logs above for details"
fi
echo "=============================="
- name: 📋 Generate GitHub summary
run: |
echo "# 🎯 Enhanced Dev Validation Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 📊 Build Results" >> $GITHUB_STEP_SUMMARY
echo "- **Pre-commit**: ${{ needs.pre-commit.result }}" >> $GITHUB_STEP_SUMMARY
echo "- **Linux Build**: ${{ needs.collect-results.outputs.linux_success == 'true' && '✅ success' || '❌ failed' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Windows Build**: ${{ needs.collect-results.outputs.windows_success == 'true' && '✅ success' || '❌ failed' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🔧 Build Configuration" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event_name }}" = "push" ]; then
echo "- **Trigger**: Automatic (dev branch push)" >> $GITHUB_STEP_SUMMARY
echo "- **Format**: HTML + PDF (mandatory)" >> $GITHUB_STEP_SUMMARY
echo "- **OS**: Linux + Windows (mandatory)" >> $GITHUB_STEP_SUMMARY
echo "- **Method**: ✅ Container builds only" >> $GITHUB_STEP_SUMMARY
else
echo "- **Trigger**: Manual workflow dispatch" >> $GITHUB_STEP_SUMMARY
echo "- **Formats**: ${{ inputs.build_formats }}" >> $GITHUB_STEP_SUMMARY
echo "- **OS**: ${{ inputs.build_os }}" >> $GITHUB_STEP_SUMMARY
echo "- **Method**: ✅ Container builds only" >> $GITHUB_STEP_SUMMARY
echo "- **Registry**: ${{ needs.build-config.outputs.container_registry }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tag**: ${{ needs.build-config.outputs.container_tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Target**: ${{ needs.build-config.outputs.target }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🚀 Preview Deployment" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.collect-results.outputs.linux_success }}" = "true" ]; then
echo "Deployment will be handled by a separate workflow." >> $GITHUB_STEP_SUMMARY
echo "Check the 'Actions' tab for the '🚀 Deploy Preview' workflow run." >> $GITHUB_STEP_SUMMARY
else
echo "Deployment skipped due to build failure." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🎯 Summary" >> $GITHUB_STEP_SUMMARY
LINUX_OK="${{ needs.collect-results.outputs.linux_success == 'true' || needs.build-config.outputs.build_linux == 'false' }}"
WINDOWS_OK="${{ needs.collect-results.outputs.windows_success == 'true' || needs.build-config.outputs.build_windows == 'false' }}"
if [ "${{ needs.pre-commit.result }}" = "success" ] && [ "$LINUX_OK" = "true" ] && [ "$WINDOWS_OK" = "true" ]; then
echo "✅ **Validation Passed** - All enabled builds successful" >> $GITHUB_STEP_SUMMARY
echo "🚀 **Preview Deployment Triggered** - A separate workflow is handling the deployment." >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Validation Failed** - Check the logs above" >> $GITHUB_STEP_SUMMARY
fi