diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index 16e2c0bc68..d41e1de8d5 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -35,6 +35,25 @@ jobs: nix-signing-key: ${{ secrets.NIX_SIGN_SECRET_KEY }} role-to-assume: ${{ secrets.DEV_AWS_ROLE }} + # shared with version-diff's HEAD side + - name: Evaluate drvPaths for version-diff + if: github.event_name == 'pull_request' + run: | + mkdir -p /tmp/drvpaths + for system in x86_64-linux aarch64-linux aarch64-darwin; do + nix eval --no-pure-eval --json \ + --expr "(import $PWD/ci/version-diff/drvpaths.nix { dir = toString ./.; system = \"$system\"; })" \ + > "/tmp/drvpaths/$system.json" + done + + - name: Upload drvPaths artifact + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: head-drvpaths + path: /tmp/drvpaths/*.json + retention-days: 1 + - name: Generate Nix Matrix id: gen-matrix run: | @@ -259,3 +278,109 @@ jobs: run: | jq -rn --argjson needs '${{ toJSON(needs) }}' '$needs|to_entries[]|select(.value.result!="success")|"::error::"+.key+": "+.value.result' exit 1 + + version-diff: + name: Package version diff + if: github.event_name == 'pull_request' + needs: + - eval + - builds-x86_64-linux + - builds-aarch64-linux + - builds-aarch64-darwin + strategy: + fail-fast: false + matrix: + include: + - system: x86_64-linux + runs_on: blacksmith-32vcpu-ubuntu-2404 + installer: ephemeral + - system: aarch64-linux + runs_on: blacksmith-4vcpu-ubuntu-2404-arm + installer: ephemeral + - system: aarch64-darwin + runs_on: + group: self-hosted-runners-nix + labels: [aarch64-darwin] + installer: self-hosted + runs-on: ${{ matrix.runs_on }} + steps: + - name: Checkout PR head + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + path: head + + - name: Checkout PR base + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ github.event.pull_request.base.sha }} + path: base + + - name: Install nix (ephemeral) + if: matrix.installer == 'ephemeral' + uses: ./head/.github/actions/nix-install-ephemeral + + - name: Install nix (self-hosted) + if: matrix.installer == 'self-hosted' + uses: ./head/.github/actions/nix-install-self-hosted + + # HEAD side already done in eval + - name: Download head drvPaths + uses: actions/download-artifact@v4 + with: + name: head-drvpaths + path: /tmp/head-drvpaths + + # use HEAD's script, base may predate it + - name: Evaluate base drvPaths + working-directory: base + run: | + nix eval --no-pure-eval --json \ + --expr '(import ${{ github.workspace }}/head/ci/version-diff/drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ + > /tmp/base-drvpaths.json + + - name: Diff versions with nvd + run: ./head/ci/version-diff/diff.sh "${{ matrix.system }}" + + - name: Upload diff artifact + uses: actions/upload-artifact@v4 + with: + name: diff-${{ matrix.system }} + path: /tmp/diff.txt + retention-days: 7 + + version-diff-comment: + name: Post package version diff comment + if: always() && github.event_name == 'pull_request' + needs: version-diff + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout Repo + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Download diff artifacts + uses: actions/download-artifact@v4 + with: + pattern: diff-* + path: diffs + merge-multiple: false + + - name: Assemble collapsed comment body + run: ./ci/version-diff/comment.sh + + - name: Find existing version diff comment + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "" + + - name: Comment on PR + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: /tmp/comment.md + edit-mode: replace diff --git a/ci/version-diff/comment.sh b/ci/version-diff/comment.sh new file mode 100755 index 0000000000..5ca07b3af2 --- /dev/null +++ b/ci/version-diff/comment.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail +shopt -s nullglob + +desc='Compares built package versions in legacyPackages between this PR and the tip of the base branch, per system.' + +all_no_diff=true +for dir in diffs/diff-*; do + file="$dir/diff.txt" + if [ ! -f "$file" ] || ! grep -q '^No `legacyPackages' "$file"; then + all_no_diff=false + break + fi +done + +if [ "$all_no_diff" = "true" ]; then + systems=$(for dir in diffs/diff-*; do echo "${dir#diffs/diff-}"; done | paste -sd, - | sed 's/,/, /g') + { + echo "" + echo "## Package version diff: none (${systems})" + } >/tmp/comment.md +else + { + echo "" + echo "## Package version diff" + echo "$desc" + echo + for dir in diffs/diff-*; do + system="${dir#diffs/diff-}" + file="$dir/diff.txt" + if [ ! -f "$file" ]; then + echo "
" + echo "${system}: diff unavailable (job failed or skipped)" + echo "
" + echo + continue + fi + if grep -q '^No `legacyPackages' "$file"; then + # short, no-op case: summary alone, nothing to expand + echo "
" + echo "${system}: $(cat "$file")" + echo "
" + echo + continue + fi + summary=$(grep '^Closure size:' "$file" | tail -1) + echo "
" + echo "${system}: ${summary:-changed}" + echo + echo '```' + # cap the visible diff so a single arch can't blow the comment size limit + head -c 20000 "$file" + if [ "$(wc -c <"$file")" -gt 20000 ]; then + echo + echo "... truncated, see workflow run for full output ..." + fi + echo '```' + echo "
" + echo + done + } >/tmp/comment.md +fi diff --git a/ci/version-diff/diff.sh b/ci/version-diff/diff.sh new file mode 100755 index 0000000000..f6c35d7f81 --- /dev/null +++ b/ci/version-diff/diff.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +system="$1" +head_json="/tmp/head-drvpaths/$system.json" +base_json="/tmp/base-drvpaths.json" + +if diff -q <(jq -S . "$head_json") <(jq -S . "$base_json") >/dev/null; then + echo "No \`legacyPackages.$system\` version changes in this PR." >/tmp/diff.txt + cat /tmp/diff.txt + exit 0 +fi + +changed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b "$base_json" ' + ($h[0]) as $H | ($b[0]) as $B | + ($H | keys) as $hk | ($B | keys) as $bk | + ($hk - ($hk - $bk))[] | select($H[.] != $B[.]) +') +added=$(jq -r -n --slurpfile h "$head_json" --slurpfile b "$base_json" ' + (($h[0] | keys) - ($b[0] | keys))[] +') +removed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b "$base_json" ' + (($b[0] | keys) - ($h[0] | keys))[] +') + +: >/tmp/diff.txt +for attr in $changed; do + head_path=$(cd head && nix build ".#legacyPackages.$system.$attr" --no-link --print-out-paths) + base_path=$(cd base && nix build ".#legacyPackages.$system.$attr" --no-link --print-out-paths) + nix run nixpkgs#nvd -- diff "$base_path" "$head_path" >>/tmp/diff.txt +done +for attr in $added; do + echo "+ $attr added" >>/tmp/diff.txt +done +for attr in $removed; do + echo "- $attr removed" >>/tmp/diff.txt +done + +if [ ! -s /tmp/diff.txt ]; then + echo "No \`legacyPackages.$system\` version changes in this PR." >/tmp/diff.txt +fi +cat /tmp/diff.txt diff --git a/ci/version-diff/drvpaths.nix b/ci/version-diff/drvpaths.nix new file mode 100644 index 0000000000..6ad6dd84ce --- /dev/null +++ b/ci/version-diff/drvpaths.nix @@ -0,0 +1,33 @@ +# dumps legacyPackages. -> drvPath, recursively; used to diff head vs base +{ dir, system }: +let + flake = builtins.getFlake dir; + isDrv = v: (v.type or "") == "derivation"; + collect = + prefix: set: + builtins.concatLists ( + map ( + n: + let + v = set.${n}; + in + if isDrv v then + [ + { + name = prefix + n; + drvPath = v.drvPath; + } + ] + else if builtins.isAttrs v then + collect (prefix + n + ".") v + else + [ ] + ) (builtins.attrNames set) + ); +in +builtins.listToAttrs ( + map (e: { + inherit (e) name; + value = e.drvPath; + }) (collect "" flake.legacyPackages.${system}) +)