name: validate-pr on: pull_request: paths: - 'recipes/**' jobs: validate: runs-on: self-hosted steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # need full history for `git diff base...HEAD` # 1. Identify which recipes the PR touches. - name: detect changed packages id: changed run: | set -e base="${{ github.event.pull_request.base.sha }}" # act's actions/checkout@v4 doesn't reliably pull the base SHA # into the local history even with fetch-depth: 0. Fetch it # explicitly so the three-dot diff resolves. git fetch --depth=1 origin "$base" || git fetch --depth=1 origin changed=$(git diff --name-only "$base"...HEAD -- 'recipes/' \ | awk -F/ '{print $2}' | sort -u) if [[ -z "$changed" ]]; then echo "no recipe changes — nothing to validate" echo "packages=" >> "$GITHUB_OUTPUT" exit 0 fi echo "changed packages: $changed" echo "packages=$changed" >> "$GITHUB_OUTPUT" # 2. Schema check (placeholder — refine once a JSON schema lives in-tree). - name: schema check if: steps.changed.outputs.packages != '' run: | for pkg in ${{ steps.changed.outputs.packages }}; do for f in recipes/$pkg/versions/*.toml; do echo "checking $f" # TODO: validate against a schema definition once one exists. grep -q '^schema = 1$' "$f" || { echo "missing schema = 1"; exit 1; } grep -q '^name =' "$f" || { echo "missing name"; exit 1; } grep -q '^version =' "$f" || { echo "missing version"; exit 1; } grep -q '^\[source\]' "$f" || { echo "missing [source]"; exit 1; } grep -q '^commit =' "$f" || { echo "missing source.commit"; exit 1; } grep -q '^sha256 =' "$f" || { echo "missing source.sha256"; exit 1; } done done # 3. Source fixity — re-fetch and confirm the sha256 matches. - name: source fixity if: steps.changed.outputs.packages != '' run: | for pkg in ${{ steps.changed.outputs.packages }}; do for f in recipes/$pkg/versions/*.toml; do url=$(awk -F'"' '/^url =/ {print $2; exit}' "$f") rev=$(awk -F'"' '/^commit =/ {print $2; exit}' "$f") expected=$(awk -F'"' '/^sha256 =/ {print $2; exit}' "$f") actual=$(nix flake prefetch --extra-experimental-features 'nix-command flakes' \ "git+${url}?rev=${rev}" --json | jq -r .hash) if [[ "$actual" != "$expected" ]]; then echo "sha256 mismatch in $f: expected $expected, got $actual" exit 1 fi done done # 4. Build smoke — every changed package must build. - name: build smoke if: steps.changed.outputs.packages != '' run: | for pkg in ${{ steps.changed.outputs.packages }}; do nix build --extra-experimental-features 'nix-command flakes' \ .#${pkg} --no-link --print-out-paths done # 4b. Push the validated outputs to the binary cache. The runner's # config.yaml bind-mounts /srv/cargoxx-cache and the signing # key into every job container. - name: push to binary cache if: steps.changed.outputs.packages != '' run: | for pkg in ${{ steps.changed.outputs.packages }}; do nix copy --extra-experimental-features 'nix-command flakes' \ --to "file:///srv/cargoxx-cache/store?secret-key=/srv/cargoxx-cache/keys/cache.sec" \ .#${pkg} done # 5. Maintainer check — PR must come from someone listed in # recipes//maintainers.txt (auto-pass for new packages, # since the PR introduces the file in the same commit). - name: maintainer check if: steps.changed.outputs.packages != '' run: | author='${{ github.event.pull_request.user.login }}' for pkg in ${{ steps.changed.outputs.packages }}; do list="recipes/$pkg/maintainers.txt" if [[ ! -f "$list" ]]; then echo "new package $pkg — maintainers.txt added by this PR" continue fi if ! grep -E -q "^\s*${author}\s*(\#.*)?$" "$list"; then echo "PR author '$author' is not in $list" exit 1 fi done