From f99b25796bdec9998efdadb813a47fe194a028f9 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 8 Nov 2026 16:41:15 +0100 Subject: [PATCH 1/2] tools: aggregate Nix changes in a table On large updates, the raw diff is not really human parsable. It can sometimes exceed the maximum length for comments, resulting in no diff being shown. To make reviews easier / more likely to spot something wrong, it now outputs a table, and report failures in PR checks. Signed-off-by: Antoine du Hamel --- .github/workflows/nix-changes-comment.yml | 111 ++++++++++++++++++---- 1 file changed, 93 insertions(+), 18 deletions(-) diff --git a/.github/workflows/nix-changes-comment.yml b/.github/workflows/nix-changes-comment.yml index 371ed669d1f1..97848ced0307 100644 --- a/.github/workflows/nix-changes-comment.yml +++ b/.github/workflows/nix-changes-comment.yml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-slim permissions: pull-requests: write + statuses: write steps: - name: Download artefacts uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 @@ -41,7 +42,7 @@ jobs: set -x > requisites-before.list > requisites-after.list - TABLE=$( + { echo '| Platform | Number of requisites | Proportion of new derivations |' echo '| :-: | -: | -: |' TOTAL_BEFORE=0 @@ -49,13 +50,17 @@ jobs: TOTAL_CHANGED=0 QUOTE='`' for PLATFORM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do + if [ ! -f "requisites-${PLATFORM}-before.list" ] || [ ! -f "requisites-${PLATFORM}-after.list" ]; then + echo "| ${QUOTE}${PLATFORM}${QUOTE} | _missing data_ | _missing data_ |" + continue + fi BEFORE=$(wc -l < "requisites-${PLATFORM}-before.list") TOTAL_BEFORE=$(($TOTAL_BEFORE + $BEFORE)) AFTER=$(wc -l < "requisites-${PLATFORM}-after.list") TOTAL_AFTER=$(($TOTAL_AFTER + $AFTER)) DIFF="_no changes_" [ "$BEFORE" = "$AFTER" ] || DIFF="**$([ "$AFTER" -lt "$BEFORE" ] || echo '+')$(($AFTER - $BEFORE))**" - CHANGED=$(git diff --no-index "requisites-${PLATFORM}-before.list" "requisites-${PLATFORM}-after.list" | grep -c '^+[^+]') + CHANGED=$(git diff --no-index "requisites-${PLATFORM}-before.list" "requisites-${PLATFORM}-after.list" | grep -c '^+[^+]' || true) TOTAL_CHANGED=$(($TOTAL_CHANGED + $CHANGED)) PERCENT=100 [ "$CHANGED" = "$AFTER" ] || { @@ -63,8 +68,23 @@ jobs: PERCENT="$((PERMILLE / 10)).$((PERMILLE % 10))" } echo "| ${QUOTE}${PLATFORM}${QUOTE} | $BEFORE -> $AFTER => $DIFF | $CHANGED / $AFTER = $PERCENT% |" - awk '{ print $0 " ('"$PLATFORM"')" }' "requisites-${PLATFORM}-before.list" >> requisites-before.list - awk '{ print $0 " ('"$PLATFORM"')" }' "requisites-${PLATFORM}-after.list" >> requisites-after.list + for STATE in before after; do + # Split store paths into package name and version; non-default outputs are appended to the name (e.g. `brotli.lib`). + awk -v OFS='\t' -v PLATFORM="$PLATFORM" '{ + name = substr($0, 45) + pname = name + version = "_unversioned_" + if (match(name, /-[^A-Za-z]/)) { + pname = substr(name, 1, RSTART - 1) + version = substr(name, RSTART + 1) + if (match(version, /-[a-z]+$/)) { + pname = pname "." substr(version, RSTART + 1) + version = substr(version, 1, RSTART - 1) + } + } + print pname, version, PLATFORM, $0 + }' "requisites-${PLATFORM}-${STATE}.list" >> "requisites-${STATE}.list" + done done DIFF="_no changes_" @@ -75,24 +95,79 @@ jobs: PERCENT="$((PERMILLE / 10)).$((PERMILLE % 10))" } echo "| **Total** | $TOTAL_BEFORE -> $TOTAL_AFTER => $DIFF | $TOTAL_CHANGED / $TOTAL_AFTER = $PERCENT% |" - ) - sort -k1.45 requisites-before.list > requisites-before.sort - sort -k1.45 requisites-after.list > requisites-after.sort + } > comment.md + sort requisites-before.list > requisites-before.sort + sort requisites-after.list > requisites-after.sort if git diff -U0 --no-color --no-index requisites-before.sort requisites-after.sort > requisites.diff; then echo 'No changes detected.' else - { - echo "$TABLE" - echo - echo '
Changelog' - echo - echo '```diff' - tail -n +5 requisites.diff - echo '```' - echo - echo '
' - } | gh pr comment -R "$GITHUB_REPOSITORY" "$PR_NUMBER" -F - + awk -F '\t' ' + function insert(list, v, n, a, i, out, done) { + n = split(list, a, " ") + for (i = 1; i <= n; i++) if (a[i] == v) return list + out = ""; done = 0 + for (i = 1; i <= n; i++) { + if (!done && v < a[i]) { out = out " " v; done = 1 } + out = out " " a[i] + } + if (!done) out = out " " v + return substr(out, 2) + } + function join(list, pre, post, s) { + s = pre list post + gsub(/ /, post ", " pre, s) + return s + } + /^[-+]/ && NF == 4 { + key = substr($1, 2) SUBSEP $3 + if (substr($1, 1, 1) == "-") removed[key] = insert(removed[key], $2) + else added[key] = insert(added[key], $2) + } + END { + for (key in removed) if (!(key in added)) added[key] = "" + for (key in added) { + split(key, k, SUBSEP) + r = (key in removed) ? removed[key] : "" + a = added[key] + if (r == a) change = "_no version change_: " join(a) + else if (r == "") change = "_added_: " join(a, "**", "**") + else if (a == "") change = "_removed_: " join(r) + else change = join(r) " -> " join(a, "**", "**") + group[k[1] SUBSEP change] = insert(group[k[1] SUBSEP change], k[2]) + } + for (g in group) { + split(g, k, SUBSEP) + rank = k[2] ~ /^_added_/ ? 0 : k[2] ~ /^_removed_/ ? 2 : k[2] ~ /^_no version change_/ ? 3 : 1 + print rank "\t| `" k[1] "` | " k[2] " | " join(group[g]) " |" + } + } + ' requisites.diff > versions.tsv + { + echo + echo '
Version changes' + echo + echo '| Package | Version | Platforms |' + echo '| :- | :- | :- |' + LC_ALL=C sort -t '`' -k1,1 -k2,2 -k3 versions.tsv | cut -f2- + echo + echo '
' + } >> comment.md + gh pr comment -R "$GITHUB_REPOSITORY" "$PR_NUMBER" -F comment.md fi env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ steps.pr-data.outputs.NUMBER }} + + - name: Report status on the PR + if: ${{ always() }} + run: | + gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${HEAD_SHA}" \ + -f state="$STATE" \ + -f context="$GITHUB_WORKFLOW" \ + -f description="$DESCRIPTION" \ + -f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + STATE: ${{ job.status == 'success' && 'success' || 'failure' }} + DESCRIPTION: ${{ job.status == 'success' && 'Nix requisites changes aggregated' || 'Failed to aggregate Nix requisites changes' }} From d06db5c02cb6e9faeabe2489b18c4e366c40e3b5 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 8 Nov 2026 20:19:57 +0100 Subject: [PATCH 2/2] fixup! tools: aggregate Nix changes in a table --- .github/workflows/nix-changes-comment.yml | 33 ++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/nix-changes-comment.yml b/.github/workflows/nix-changes-comment.yml index 97848ced0307..fc642dee29bc 100644 --- a/.github/workflows/nix-changes-comment.yml +++ b/.github/workflows/nix-changes-comment.yml @@ -69,20 +69,21 @@ jobs: } echo "| ${QUOTE}${PLATFORM}${QUOTE} | $BEFORE -> $AFTER => $DIFF | $CHANGED / $AFTER = $PERCENT% |" for STATE in before after; do - # Split store paths into package name and version; non-default outputs are appended to the name (e.g. `brotli.lib`). + # Split store paths into package name, output (or source archive extension), and version. awk -v OFS='\t' -v PLATFORM="$PLATFORM" '{ name = substr($0, 45) pname = name + output = "out" version = "_unversioned_" if (match(name, /-[^A-Za-z]/)) { pname = substr(name, 1, RSTART - 1) version = substr(name, RSTART + 1) - if (match(version, /-[a-z]+$/)) { - pname = pname "." substr(version, RSTART + 1) + if (match(version, /-[a-z]+$/) || match(version, /\.(tar(\.[a-z0-9]+)?|tgz|tbz2?|txz|zip|patch|diff)$/)) { + output = substr(version, RSTART + 1) version = substr(version, 1, RSTART - 1) } } - print pname, version, PLATFORM, $0 + print pname, output, version, PLATFORM, $0 }' "requisites-${PLATFORM}-${STATE}.list" >> "requisites-${STATE}.list" done done @@ -118,10 +119,10 @@ jobs: gsub(/ /, post ", " pre, s) return s } - /^[-+]/ && NF == 4 { - key = substr($1, 2) SUBSEP $3 - if (substr($1, 1, 1) == "-") removed[key] = insert(removed[key], $2) - else added[key] = insert(added[key], $2) + /^[-+]/ && NF == 5 { + key = substr($1, 2) SUBSEP $2 SUBSEP $4 + if (substr($1, 1, 1) == "-") removed[key] = insert(removed[key], $3) + else added[key] = insert(added[key], $3) } END { for (key in removed) if (!(key in added)) added[key] = "" @@ -133,12 +134,20 @@ jobs: else if (r == "") change = "_added_: " join(a, "**", "**") else if (a == "") change = "_removed_: " join(r) else change = join(r) " -> " join(a, "**", "**") - group[k[1] SUBSEP change] = insert(group[k[1] SUBSEP change], k[2]) + platforms[k[1] SUBSEP k[2] SUBSEP change] = insert(platforms[k[1] SUBSEP k[2] SUBSEP change], k[3]) } - for (g in group) { - split(g, k, SUBSEP) + for (key in platforms) { + split(key, k, SUBSEP) + row = k[1] SUBSEP k[3] SUBSEP join(platforms[key]) + outputs[row] = insert(outputs[row], k[2]) + } + for (row in outputs) { + split(row, k, SUBSEP) + n = split(outputs[row], o, " ") + name = "" + for (i = 1; i <= n; i++) name = name ", `" k[1] (o[i] == "out" ? "" : "." o[i]) "`" rank = k[2] ~ /^_added_/ ? 0 : k[2] ~ /^_removed_/ ? 2 : k[2] ~ /^_no version change_/ ? 3 : 1 - print rank "\t| `" k[1] "` | " k[2] " | " join(group[g]) " |" + print rank "\t| " substr(name, 3) " | " k[2] " | " k[3] " |" } } ' requisites.diff > versions.tsv