Build Changed Packages #20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Changed Packages | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'binaries/**/*.yaml' | |
| - 'packages/**/*.yaml' | |
| pull_request: | |
| types: [closed] | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| recipe_path: | |
| description: 'Specific recipe path to build (e.g., binaries/hello/static.yaml)' | |
| type: string | |
| default: '' | |
| force_rebuild: | |
| description: 'Force rebuild even if hash unchanged' | |
| type: boolean | |
| default: true | |
| permissions: | |
| attestations: write | |
| contents: write | |
| id-token: write | |
| packages: write | |
| concurrency: | |
| group: build-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| detect-changes: | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| changed_recipes: ${{ steps.detect.outputs.changed_recipes }} | |
| has_changes: ${{ steps.detect.outputs.has_changes }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 | |
| - name: Detect changed recipes | |
| id: detect | |
| run: | | |
| mkdir -p /tmp/changes | |
| CHANGED_RECIPES="[]" | |
| if [ -n "${{ inputs.recipe_path }}" ]; then | |
| # Manual trigger with specific recipe | |
| if [ -f "${{ inputs.recipe_path }}" ]; then | |
| RECIPE="${{ inputs.recipe_path }}" | |
| CHANGED_RECIPES=$(jq -n --arg path "$RECIPE" '[{"path": $path}]') | |
| else | |
| echo "::error::Recipe not found: ${{ inputs.recipe_path }}" | |
| exit 1 | |
| fi | |
| else | |
| # Detect changes from git diff or find all recipes | |
| if [ "${{ github.event_name }}" == "push" ]; then | |
| CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD -- 'binaries/**/*.yaml' 'packages/**/*.yaml' 2>/dev/null || true) | |
| elif [ "${{ github.event_name }}" == "pull_request" ]; then | |
| CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'binaries/**/*.yaml' 'packages/**/*.yaml' 2>/dev/null || true) | |
| elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| # No specific recipe, build all recipes | |
| echo "::notice::No recipe specified, building all recipes" | |
| CHANGED_FILES=$(find binaries packages -name '*.yaml' -type f 2>/dev/null || true) | |
| else | |
| CHANGED_FILES="" | |
| fi | |
| echo "Changed files:" | |
| echo "$CHANGED_FILES" | |
| # Build JSON array of changed recipes | |
| for file in $CHANGED_FILES; do | |
| if [ -f "$file" ]; then | |
| CHANGED_RECIPES=$(echo "$CHANGED_RECIPES" | jq --arg path "$file" '. + [{"path": $path}]') | |
| fi | |
| done | |
| fi | |
| # Output results | |
| RECIPE_COUNT=$(echo "$CHANGED_RECIPES" | jq 'length') | |
| echo "Found $RECIPE_COUNT recipes to build" | |
| echo "$CHANGED_RECIPES" | jq . | |
| # Save to outputs | |
| echo "changed_recipes=$(echo "$CHANGED_RECIPES" | jq -c .)" >> $GITHUB_OUTPUT | |
| if [ "$RECIPE_COUNT" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| build: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.has_changes == 'true' | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 4 | |
| matrix: | |
| recipe: ${{ fromJson(needs.detect-changes.outputs.changed_recipes) }} | |
| uses: ./.github/workflows/matrix_builds.yaml | |
| with: | |
| sbuild-url: "https://raw.githubusercontent.com/${{ github.repository }}/refs/heads/main/${{ matrix.recipe.path }}" | |
| ghcr-url: ${{ contains(matrix.recipe.path, 'packages/') && format('ghcr.io/{0}/pkgcache', github.repository_owner) || format('ghcr.io/{0}/bincache', github.repository_owner) }} | |
| pkg-family: ${{ github.event.repository.name }} | |
| rebuild: true | |
| logs: true | |
| metadata-release: false | |
| secrets: inherit | |
| update-cache: | |
| needs: [detect-changes, build] | |
| if: always() && needs.detect-changes.outputs.has_changes == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download sbuild-cache | |
| run: | | |
| curl -fsSL "https://github.com/pkgforge/sbuilder/releases/download/latest/sbuild-cache-x86_64-linux" \ | |
| -o /usr/local/bin/sbuild-cache || exit 0 | |
| chmod +x /usr/local/bin/sbuild-cache | |
| - name: Download existing cache | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release download cache-latest -p build_cache.sdb -D /tmp/ --repo "${{ github.repository }}" || \ | |
| sbuild-cache --cache /tmp/build_cache.sdb init | |
| - name: Download build status artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: build-status-* | |
| path: /tmp/build-statuses | |
| merge-multiple: true | |
| continue-on-error: true | |
| - name: Update cache with build results | |
| run: | | |
| RECIPES='${{ needs.detect-changes.outputs.changed_recipes }}' | |
| echo "$RECIPES" | jq -c '.[]' | while read -r recipe; do | |
| path=$(echo "$recipe" | jq -r '.path') | |
| # Extract package name from path (e.g., binaries/hello/static.yaml -> hello) | |
| pkg_name=$(basename "$(dirname "$path")") | |
| # Find build status from artifacts | |
| # The recipe_url in the status JSON contains the path | |
| status="unknown" | |
| if [ -d "/tmp/build-statuses" ]; then | |
| for status_file in /tmp/build-statuses/build-status.json /tmp/build-statuses/*/build-status.json; do | |
| [ -f "$status_file" ] || continue | |
| recipe_url=$(jq -r '.recipe_url // ""' "$status_file" 2>/dev/null || echo "") | |
| if echo "$recipe_url" | grep -q "$path"; then | |
| file_status=$(jq -r '.status // "unknown"' "$status_file" 2>/dev/null || echo "unknown") | |
| # If any host failed, mark as failure | |
| if [ "$file_status" = "failure" ]; then | |
| status="failure" | |
| break | |
| elif [ "$file_status" = "success" ]; then | |
| status="success" | |
| fi | |
| fi | |
| done | |
| fi | |
| # Fallback: use overall build job result if no artifact found | |
| if [ "$status" = "unknown" ]; then | |
| status="${{ needs.build.result }}" | |
| fi | |
| echo "Package: $pkg_name, Status: $status" | |
| sbuild-cache --cache /tmp/build_cache.sdb update \ | |
| --package "$pkg_name" \ | |
| --version "latest" \ | |
| --status "$status" || true | |
| done | |
| - name: Generate build summary | |
| run: | | |
| sbuild-cache --cache /tmp/build_cache.sdb gh-summary \ | |
| --title "Build Results" \ | |
| --host x86_64-Linux || true | |
| - name: Upload updated cache | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| if [ -f "/tmp/build_cache.sdb" ]; then | |
| gh release upload cache-latest /tmp/build_cache.sdb --clobber --repo "${{ github.repository }}" || { | |
| gh release create cache-latest \ | |
| --title "Build Cache" \ | |
| --notes "Build cache for CI" \ | |
| --repo "${{ github.repository }}" \ | |
| /tmp/build_cache.sdb | |
| } | |
| fi |