From e5aed37c9801d533775dd374f30020cfe52bc633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 22 Jul 2026 09:38:11 +0300 Subject: [PATCH 01/10] ci: add package version diff comment on PRs Adds a version-diff job that compares every legacyPackages. derivation between a PR's head and base commit and posts the result as a collapsed comment (one
per system, folded into its summary line), using nvd for the actual version/added/removed diff. Enumerates legacyPackages directly (via a small linkFarm collector expression) rather than relying on nix-eval's packages_matrix, since that matrix only lists not-yet-cached packages and goes stale/empty once a PR's packages are built and cached -- the wrong signal for "did anything change vs base". Uses a GitHub App token via actions/create-github-app-token so the comment step isn't blocked by the org's "Actions can't create/approve PRs" policy, and grants pull-requests: write only to the comment job. --- .github/workflows/nix-build.yml | 221 ++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index 16e2c0bc68..edb821ecd2 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -259,3 +259,224 @@ 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: + - nix-build-packages-x86_64-linux + - nix-build-packages-aarch64-linux + - nix-build-packages-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 + with: + push-to-cache: 'true' + env: + DEV_AWS_ROLE: ${{ secrets.DEV_AWS_ROLE }} + NIX_SIGN_SECRET_KEY: ${{ secrets.NIX_SIGN_SECRET_KEY }} + + - name: Install nix (self-hosted) + if: matrix.installer == 'self-hosted' + uses: ./head/.github/actions/nix-install-self-hosted + + # nix-eval's packages_matrix only lists *uncached* packages, so it + # cannot be used to decide what to diff. Instead, collect every + # derivation under legacyPackages. (recursing into attrsets + # like nix-eval-jobs --force-recurse does) into one linkFarm root, + # whose closure nvd can diff against the other side's root. + - name: Write root collector expression + run: | + cat > /tmp/version-diff-root.nix <<'EOF' + { dir, system }: + let + flake = builtins.getFlake dir; + pkgs = flake.inputs.nixpkgs.legacyPackages.${system}; + 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; + path = v; + } + ] + else if builtins.isAttrs v then + collect (prefix + n + ".") v + else + [ ] + ) (builtins.attrNames set) + ); + in + pkgs.linkFarm "version-diff-root" (collect "" flake.legacyPackages.${system}) + EOF + + # Evaluation only -- no store paths are realized yet. + - name: Evaluate head root + id: head + working-directory: head + run: | + path=$(nix eval --impure --raw --accept-flake-config \ + --expr '(import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }).outPath') + echo "path=$path" >> "$GITHUB_OUTPUT" + + - name: Evaluate base root + id: base + working-directory: base + run: | + path=$(nix eval --impure --raw --accept-flake-config \ + --expr '(import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }).outPath') + echo "path=$path" >> "$GITHUB_OUTPUT" + + # Both roots' closures were already built and pushed to the shared + # cache (head by nix-build-packages-* above, base by CI on the base + # branch), so building them only substitutes -- no rebuild. + - name: Diff versions with nvd + run: | + if [ "${{ steps.head.outputs.path }}" = "${{ steps.base.outputs.path }}" ]; then + echo "No \`legacyPackages.${{ matrix.system }}\` version changes in this PR." > /tmp/diff.txt + else + (cd head && nix build --impure --accept-flake-config --no-link \ + --expr 'import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }') + (cd base && nix build --impure --accept-flake-config --no-link \ + --expr 'import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }') + nix run --accept-flake-config nixpkgs#nvd -- diff \ + "${{ steps.base.outputs.path }}" "${{ steps.head.outputs.path }}" > /tmp/diff.txt + fi + cat /tmp/diff.txt + + - 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: Download diff artifacts + uses: actions/download-artifact@v4 + with: + pattern: diff-* + path: diffs + merge-multiple: false + + - name: Assemble collapsed comment body + run: | + 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', ') + { + echo "" + echo "## Package version diff: none" + echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." + echo + echo "### No Package Differences" + echo "All packages are hash-identical on all systems (${systems})." + } > /tmp/comment.md + else + { + echo "" + echo "## Package version diff" + echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." + 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 (see below)}" + 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 + + - 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 From 21057d7c760aaa1bf3e7bf9406c9be7612cd36f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Thu, 23 Jul 2026 01:28:28 +0300 Subject: [PATCH 02/10] ci: scope version-diff to changed attrs via drvPath comparison Building the entire legacyPackages closure on both sides just to nvd-diff two roots meant realizing ~700+ store paths per side per arch even when only one package changed -- most of that time was substituter narinfo lookups and downloads, not building anything. Evaluate each attr's drvPath (pure evaluation, no network) on both sides first. Equal maps mean no changes, decided without touching the store at all. Otherwise only the attrs whose drvPath actually differs get built and nvd-diffed; added/removed attrs are reported directly. --- .github/workflows/nix-build.yml | 85 +++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index edb821ecd2..3cc400ced8 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -309,17 +309,18 @@ jobs: uses: ./head/.github/actions/nix-install-self-hosted # nix-eval's packages_matrix only lists *uncached* packages, so it - # cannot be used to decide what to diff. Instead, collect every - # derivation under legacyPackages. (recursing into attrsets - # like nix-eval-jobs --force-recurse does) into one linkFarm root, - # whose closure nvd can diff against the other side's root. - - name: Write root collector expression + # cannot be used to decide what to diff. Instead, evaluate every + # derivation's drvPath under legacyPackages. (recursing into + # attrsets like nix-eval-jobs --force-recurse does) on both sides. + # drvPath captures any input change (source, patches, deps, build + # steps), not just a bumped version string, and evaluating it is pure + # -- no substituter queries, no downloads, no builds. + - name: Write drvPath collector expression run: | - cat > /tmp/version-diff-root.nix <<'EOF' + cat > /tmp/version-diff-drvpaths.nix <<'EOF' { dir, system }: let flake = builtins.getFlake dir; - pkgs = flake.inputs.nixpkgs.legacyPackages.${system}; isDrv = v: (v.type or "") == "derivation"; collect = prefix: set: @@ -333,7 +334,7 @@ jobs: [ { name = prefix + n; - path = v; + drvPath = v.drvPath; } ] else if builtins.isAttrs v then @@ -343,40 +344,64 @@ jobs: ) (builtins.attrNames set) ); in - pkgs.linkFarm "version-diff-root" (collect "" flake.legacyPackages.${system}) + builtins.listToAttrs ( + map (e: { + inherit (e) name; + value = e.drvPath; + }) (collect "" flake.legacyPackages.${system}) + ) EOF - # Evaluation only -- no store paths are realized yet. - - name: Evaluate head root - id: head + - name: Evaluate head drvPaths working-directory: head run: | - path=$(nix eval --impure --raw --accept-flake-config \ - --expr '(import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }).outPath') - echo "path=$path" >> "$GITHUB_OUTPUT" + nix eval --impure --json --accept-flake-config \ + --expr '(import /tmp/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ + > /tmp/head-drvpaths.json - - name: Evaluate base root - id: base + - name: Evaluate base drvPaths working-directory: base run: | - path=$(nix eval --impure --raw --accept-flake-config \ - --expr '(import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }).outPath') - echo "path=$path" >> "$GITHUB_OUTPUT" + nix eval --impure --json --accept-flake-config \ + --expr '(import /tmp/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ + > /tmp/base-drvpaths.json - # Both roots' closures were already built and pushed to the shared - # cache (head by nix-build-packages-* above, base by CI on the base - # branch), so building them only substitutes -- no rebuild. + # Only realize/diff attrs whose drvPath actually differs, instead of + # the whole legacyPackages closure -- most PRs change a handful of + # packages, not all ~700+ of them. - name: Diff versions with nvd run: | - if [ "${{ steps.head.outputs.path }}" = "${{ steps.base.outputs.path }}" ]; then + if diff -q <(jq -S . /tmp/head-drvpaths.json) <(jq -S . /tmp/base-drvpaths.json) > /dev/null; then echo "No \`legacyPackages.${{ matrix.system }}\` version changes in this PR." > /tmp/diff.txt else - (cd head && nix build --impure --accept-flake-config --no-link \ - --expr 'import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }') - (cd base && nix build --impure --accept-flake-config --no-link \ - --expr 'import /tmp/version-diff-root.nix { dir = toString ./.; system = "${{ matrix.system }}"; }') - nix run --accept-flake-config nixpkgs#nvd -- diff \ - "${{ steps.base.outputs.path }}" "${{ steps.head.outputs.path }}" > /tmp/diff.txt + changed=$(jq -r -n --slurpfile h /tmp/head-drvpaths.json --slurpfile b /tmp/base-drvpaths.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 /tmp/head-drvpaths.json --slurpfile b /tmp/base-drvpaths.json ' + (($h[0] | keys) - ($b[0] | keys))[] + ') + removed=$(jq -r -n --slurpfile h /tmp/head-drvpaths.json --slurpfile b /tmp/base-drvpaths.json ' + (($b[0] | keys) - ($h[0] | keys))[] + ') + + : > /tmp/diff.txt + for attr in $changed; do + head_path=$(cd head && nix build --accept-flake-config ".#legacyPackages.${{ matrix.system }}.$attr" --no-link --print-out-paths) + base_path=$(cd base && nix build --accept-flake-config ".#legacyPackages.${{ matrix.system }}.$attr" --no-link --print-out-paths) + nix run --accept-flake-config 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.${{ matrix.system }}\` version changes in this PR." > /tmp/diff.txt + fi fi cat /tmp/diff.txt From 15576606bf5cdbdb78f2679946357737f9318050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Thu, 23 Jul 2026 04:52:08 +0300 Subject: [PATCH 03/10] ci: drop redundant "(see below)" from version-diff summary fallback --- .github/workflows/nix-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index 3cc400ced8..6cce92aa63 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -474,7 +474,7 @@ jobs: fi summary=$(grep '^Closure size:' "$file" | tail -1) echo "
" - echo "${system}: ${summary:-changed (see below)}" + echo "${system}: ${summary:-changed}" echo echo '```' # Cap the visible diff so a single arch can't blow the comment size limit. From 0e74793c0fdd4861f1b4c18ad211041e03dfa709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Thu, 1 Oct 2026 02:58:44 +0300 Subject: [PATCH 04/10] ci: fix stale job names and stop double-evaluating version-diff's HEAD side needs: still referenced pre-rename job names (nix-build-packages-*), broken by develop's job rename merge. Also extracts the drvPath collector to ci/version-diff-drvpaths.nix and has eval upload HEAD's drvPaths as an artifact, so version-diff only evaluates BASE (per mmlb's review comment). Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 94 +++++++++++++-------------------- ci/version-diff-drvpaths.nix | 35 ++++++++++++ 2 files changed, 71 insertions(+), 58 deletions(-) create mode 100644 ci/version-diff-drvpaths.nix diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index 6cce92aa63..e9986afa6f 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 }} + # Reused by version-diff's HEAD side, avoiding a duplicate evaluation. + - 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 --impure --json --accept-flake-config \ + --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: | @@ -264,9 +283,10 @@ jobs: name: Package version diff if: github.event_name == 'pull_request' needs: - - nix-build-packages-x86_64-linux - - nix-build-packages-aarch64-linux - - nix-build-packages-aarch64-darwin + - eval + - builds-x86_64-linux + - builds-aarch64-linux + - builds-aarch64-darwin strategy: fail-fast: false matrix: @@ -308,62 +328,19 @@ jobs: if: matrix.installer == 'self-hosted' uses: ./head/.github/actions/nix-install-self-hosted - # nix-eval's packages_matrix only lists *uncached* packages, so it - # cannot be used to decide what to diff. Instead, evaluate every - # derivation's drvPath under legacyPackages. (recursing into - # attrsets like nix-eval-jobs --force-recurse does) on both sides. - # drvPath captures any input change (source, patches, deps, build - # steps), not just a bumped version string, and evaluating it is pure - # -- no substituter queries, no downloads, no builds. - - name: Write drvPath collector expression - run: | - cat > /tmp/version-diff-drvpaths.nix <<'EOF' - { 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}) - ) - EOF - - - name: Evaluate head drvPaths - working-directory: head - run: | - nix eval --impure --json --accept-flake-config \ - --expr '(import /tmp/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ - > /tmp/head-drvpaths.json + # eval already computed HEAD's drvPaths; only BASE needs it here. + - 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 --impure --json --accept-flake-config \ - --expr '(import /tmp/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ + --expr '(import ${{ github.workspace }}/head/ci/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ > /tmp/base-drvpaths.json # Only realize/diff attrs whose drvPath actually differs, instead of @@ -371,18 +348,19 @@ jobs: # packages, not all ~700+ of them. - name: Diff versions with nvd run: | - if diff -q <(jq -S . /tmp/head-drvpaths.json) <(jq -S . /tmp/base-drvpaths.json) > /dev/null; then + head_json=/tmp/head-drvpaths/${{ matrix.system }}.json + if diff -q <(jq -S . "$head_json") <(jq -S . /tmp/base-drvpaths.json) > /dev/null; then echo "No \`legacyPackages.${{ matrix.system }}\` version changes in this PR." > /tmp/diff.txt else - changed=$(jq -r -n --slurpfile h /tmp/head-drvpaths.json --slurpfile b /tmp/base-drvpaths.json ' + changed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b /tmp/base-drvpaths.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 /tmp/head-drvpaths.json --slurpfile b /tmp/base-drvpaths.json ' + added=$(jq -r -n --slurpfile h "$head_json" --slurpfile b /tmp/base-drvpaths.json ' (($h[0] | keys) - ($b[0] | keys))[] ') - removed=$(jq -r -n --slurpfile h /tmp/head-drvpaths.json --slurpfile b /tmp/base-drvpaths.json ' + removed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b /tmp/base-drvpaths.json ' (($b[0] | keys) - ($h[0] | keys))[] ') diff --git a/ci/version-diff-drvpaths.nix b/ci/version-diff-drvpaths.nix new file mode 100644 index 0000000000..cde2a7c0d2 --- /dev/null +++ b/ci/version-diff-drvpaths.nix @@ -0,0 +1,35 @@ +# drvPath captures any input change (source, patches, deps, build steps), +# not just a bumped version string, and evaluating it is pure -- no +# substituter queries, no downloads, no builds. +{ 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}) +) From f88c75ad04b762eecb1e5b6e6d91b80b9365a13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Thu, 1 Oct 2026 02:59:52 +0300 Subject: [PATCH 05/10] ci: shorten comments to one line Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 8 +++----- ci/version-diff-drvpaths.nix | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index e9986afa6f..e1a8c8428e 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -35,7 +35,7 @@ jobs: nix-signing-key: ${{ secrets.NIX_SIGN_SECRET_KEY }} role-to-assume: ${{ secrets.DEV_AWS_ROLE }} - # Reused by version-diff's HEAD side, avoiding a duplicate evaluation. + # shared with version-diff's HEAD side - name: Evaluate drvPaths for version-diff if: github.event_name == 'pull_request' run: | @@ -328,7 +328,7 @@ jobs: if: matrix.installer == 'self-hosted' uses: ./head/.github/actions/nix-install-self-hosted - # eval already computed HEAD's drvPaths; only BASE needs it here. + # HEAD side already done in eval - name: Download head drvPaths uses: actions/download-artifact@v4 with: @@ -343,9 +343,7 @@ jobs: --expr '(import ${{ github.workspace }}/head/ci/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ > /tmp/base-drvpaths.json - # Only realize/diff attrs whose drvPath actually differs, instead of - # the whole legacyPackages closure -- most PRs change a handful of - # packages, not all ~700+ of them. + # only realize/diff attrs whose drvPath differs - name: Diff versions with nvd run: | head_json=/tmp/head-drvpaths/${{ matrix.system }}.json diff --git a/ci/version-diff-drvpaths.nix b/ci/version-diff-drvpaths.nix index cde2a7c0d2..2d10e8137c 100644 --- a/ci/version-diff-drvpaths.nix +++ b/ci/version-diff-drvpaths.nix @@ -1,6 +1,4 @@ -# drvPath captures any input change (source, patches, deps, build steps), -# not just a bumped version string, and evaluating it is pure -- no -# substituter queries, no downloads, no builds. +# pure eval, no builds; drvPath catches any input change, not just a version bump { dir, system }: let flake = builtins.getFlake dir; From 7bdb2672e61729e091a2e6164a076695a7c9e08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Thu, 1 Oct 2026 05:32:37 +0300 Subject: [PATCH 06/10] ci: guard empty artifact glob in version-diff comment shopt -s nullglob so an unmatched diffs/diff-* glob (all matrix jobs failed before uploading) doesn't leak a literal glob string as a system name in the posted comment. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index e1a8c8428e..b616e24000 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -405,6 +405,7 @@ jobs: - name: Assemble collapsed comment body run: | + shopt -s nullglob all_no_diff=true for dir in diffs/diff-*; do file="$dir/diff.txt" From 80da499cc336ee6abdb552b9d9cc60ec5e48cbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 5 Oct 2026 17:35:55 +0300 Subject: [PATCH 07/10] ci: address review, drop dead push-to-cache env and extract shell to ci/version-diff/ Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 112 ++---------------- ci/version-diff/comment.sh | 64 ++++++++++ ci/version-diff/diff.sh | 42 +++++++ .../drvpaths.nix} | 2 +- 4 files changed, 114 insertions(+), 106 deletions(-) create mode 100755 ci/version-diff/comment.sh create mode 100755 ci/version-diff/diff.sh rename ci/{version-diff-drvpaths.nix => version-diff/drvpaths.nix} (88%) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index b616e24000..6e82dc456a 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -42,7 +42,7 @@ jobs: mkdir -p /tmp/drvpaths for system in x86_64-linux aarch64-linux aarch64-darwin; do nix eval --impure --json --accept-flake-config \ - --expr "(import $PWD/ci/version-diff-drvpaths.nix { dir = toString ./.; system = \"$system\"; })" \ + --expr "(import $PWD/ci/version-diff/drvpaths.nix { dir = toString ./.; system = \"$system\"; })" \ > "/tmp/drvpaths/$system.json" done @@ -318,11 +318,6 @@ jobs: - name: Install nix (ephemeral) if: matrix.installer == 'ephemeral' uses: ./head/.github/actions/nix-install-ephemeral - with: - push-to-cache: 'true' - env: - DEV_AWS_ROLE: ${{ secrets.DEV_AWS_ROLE }} - NIX_SIGN_SECRET_KEY: ${{ secrets.NIX_SIGN_SECRET_KEY }} - name: Install nix (self-hosted) if: matrix.installer == 'self-hosted' @@ -340,46 +335,11 @@ jobs: working-directory: base run: | nix eval --impure --json --accept-flake-config \ - --expr '(import ${{ github.workspace }}/head/ci/version-diff-drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ + --expr '(import ${{ github.workspace }}/head/ci/version-diff/drvpaths.nix { dir = toString ./.; system = "${{ matrix.system }}"; })' \ > /tmp/base-drvpaths.json - # only realize/diff attrs whose drvPath differs - name: Diff versions with nvd - run: | - head_json=/tmp/head-drvpaths/${{ matrix.system }}.json - if diff -q <(jq -S . "$head_json") <(jq -S . /tmp/base-drvpaths.json) > /dev/null; then - echo "No \`legacyPackages.${{ matrix.system }}\` version changes in this PR." > /tmp/diff.txt - else - changed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b /tmp/base-drvpaths.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 /tmp/base-drvpaths.json ' - (($h[0] | keys) - ($b[0] | keys))[] - ') - removed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b /tmp/base-drvpaths.json ' - (($b[0] | keys) - ($h[0] | keys))[] - ') - - : > /tmp/diff.txt - for attr in $changed; do - head_path=$(cd head && nix build --accept-flake-config ".#legacyPackages.${{ matrix.system }}.$attr" --no-link --print-out-paths) - base_path=$(cd base && nix build --accept-flake-config ".#legacyPackages.${{ matrix.system }}.$attr" --no-link --print-out-paths) - nix run --accept-flake-config 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.${{ matrix.system }}\` version changes in this PR." > /tmp/diff.txt - fi - fi - cat /tmp/diff.txt + run: ./head/ci/version-diff/diff.sh "${{ matrix.system }}" - name: Upload diff artifact uses: actions/upload-artifact@v4 @@ -396,6 +356,9 @@ jobs: 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: @@ -404,68 +367,7 @@ jobs: merge-multiple: false - name: Assemble collapsed comment body - run: | - shopt -s nullglob - 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', ') - { - echo "" - echo "## Package version diff: none" - echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." - echo - echo "### No Package Differences" - echo "All packages are hash-identical on all systems (${systems})." - } > /tmp/comment.md - else - { - echo "" - echo "## Package version diff" - echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." - 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 + run: ./ci/version-diff/comment.sh - name: Find existing version diff comment uses: peter-evans/find-comment@v3 diff --git a/ci/version-diff/comment.sh b/ci/version-diff/comment.sh new file mode 100755 index 0000000000..43d9e53c89 --- /dev/null +++ b/ci/version-diff/comment.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail +shopt -s nullglob + +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', ') + { + echo "" + echo "## Package version diff: none" + echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." + echo + echo "### No Package Differences" + echo "All packages are hash-identical on all systems (${systems})." + } >/tmp/comment.md +else + { + echo "" + echo "## Package version diff" + echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." + 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..a1418cbe5c --- /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 --accept-flake-config ".#legacyPackages.$system.$attr" --no-link --print-out-paths) + base_path=$(cd base && nix build --accept-flake-config ".#legacyPackages.$system.$attr" --no-link --print-out-paths) + nix run --accept-flake-config 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 similarity index 88% rename from ci/version-diff-drvpaths.nix rename to ci/version-diff/drvpaths.nix index 2d10e8137c..6ad6dd84ce 100644 --- a/ci/version-diff-drvpaths.nix +++ b/ci/version-diff/drvpaths.nix @@ -1,4 +1,4 @@ -# pure eval, no builds; drvPath catches any input change, not just a version bump +# dumps legacyPackages. -> drvPath, recursively; used to diff head vs base { dir, system }: let flake = builtins.getFlake dir; From f0ac2b7c9b896a28be06bf79bbaddf0baa08166f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 5 Oct 2026 20:42:05 +0300 Subject: [PATCH 08/10] Update .github/workflows/nix-build.yml Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com> --- .github/workflows/nix-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index 6e82dc456a..9d27781491 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -41,7 +41,7 @@ jobs: run: | mkdir -p /tmp/drvpaths for system in x86_64-linux aarch64-linux aarch64-darwin; do - nix eval --impure --json --accept-flake-config \ + nix eval --impure --json \ --expr "(import $PWD/ci/version-diff/drvpaths.nix { dir = toString ./.; system = \"$system\"; })" \ > "/tmp/drvpaths/$system.json" done From 433db03a757c81f39e23811e09b586fa242136c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 5 Oct 2026 22:23:29 +0300 Subject: [PATCH 09/10] ci: drop --accept-flake-config, use --no-pure-eval, fix treefmt Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 4 ++-- ci/version-diff/comment.sh | 4 ++-- ci/version-diff/diff.sh | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index 9d27781491..d41e1de8d5 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -41,7 +41,7 @@ jobs: run: | mkdir -p /tmp/drvpaths for system in x86_64-linux aarch64-linux aarch64-darwin; do - nix eval --impure --json \ + nix eval --no-pure-eval --json \ --expr "(import $PWD/ci/version-diff/drvpaths.nix { dir = toString ./.; system = \"$system\"; })" \ > "/tmp/drvpaths/$system.json" done @@ -334,7 +334,7 @@ jobs: - name: Evaluate base drvPaths working-directory: base run: | - nix eval --impure --json --accept-flake-config \ + 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 diff --git a/ci/version-diff/comment.sh b/ci/version-diff/comment.sh index 43d9e53c89..e3bc8e34e3 100755 --- a/ci/version-diff/comment.sh +++ b/ci/version-diff/comment.sh @@ -16,7 +16,7 @@ if [ "$all_no_diff" = "true" ]; then { echo "" echo "## Package version diff: none" - echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." + echo 'Compares built package versions in `legacyPackages` between this PR and its base commit, per system.' echo echo "### No Package Differences" echo "All packages are hash-identical on all systems (${systems})." @@ -25,7 +25,7 @@ else { echo "" echo "## Package version diff" - echo "Compares built package versions in \`legacyPackages\` between this PR and its base commit, per system." + echo 'Compares built package versions in `legacyPackages` between this PR and its base commit, per system.' echo for dir in diffs/diff-*; do system="${dir#diffs/diff-}" diff --git a/ci/version-diff/diff.sh b/ci/version-diff/diff.sh index a1418cbe5c..f6c35d7f81 100755 --- a/ci/version-diff/diff.sh +++ b/ci/version-diff/diff.sh @@ -25,9 +25,9 @@ removed=$(jq -r -n --slurpfile h "$head_json" --slurpfile b "$base_json" ' : >/tmp/diff.txt for attr in $changed; do - head_path=$(cd head && nix build --accept-flake-config ".#legacyPackages.$system.$attr" --no-link --print-out-paths) - base_path=$(cd base && nix build --accept-flake-config ".#legacyPackages.$system.$attr" --no-link --print-out-paths) - nix run --accept-flake-config nixpkgs#nvd -- diff "$base_path" "$head_path" >>/tmp/diff.txt + 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 From ffce43944d44fec779a92c38f494c3a90cb2d55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 04:26:49 +0300 Subject: [PATCH 10/10] ci: fix shellcheck SC2016 failure in version-diff comment.sh Co-Authored-By: Claude Sonnet 5 --- ci/version-diff/comment.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ci/version-diff/comment.sh b/ci/version-diff/comment.sh index e3bc8e34e3..2a43148675 100755 --- a/ci/version-diff/comment.sh +++ b/ci/version-diff/comment.sh @@ -2,6 +2,9 @@ set -euo pipefail shopt -s nullglob +# shellcheck disable=SC2016 # backtick is literal markdown, not a command substitution +desc='Compares built package versions in `legacyPackages` between this PR and its base commit, per system.' + all_no_diff=true for dir in diffs/diff-*; do file="$dir/diff.txt" @@ -16,7 +19,7 @@ if [ "$all_no_diff" = "true" ]; then { echo "" echo "## Package version diff: none" - echo 'Compares built package versions in `legacyPackages` between this PR and its base commit, per system.' + echo "$desc" echo echo "### No Package Differences" echo "All packages are hash-identical on all systems (${systems})." @@ -25,7 +28,7 @@ else { echo "" echo "## Package version diff" - echo 'Compares built package versions in `legacyPackages` between this PR and its base commit, per system.' + echo "$desc" echo for dir in diffs/diff-*; do system="${dir#diffs/diff-}"