Skip to content

Commit f99b257

Browse files
committed
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 <duhamelantoine1995@gmail.com>
1 parent 82940d6 commit f99b257

1 file changed

Lines changed: 93 additions & 18 deletions

File tree

‎.github/workflows/nix-changes-comment.yml‎

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
runs-on: ubuntu-slim
1717
permissions:
1818
pull-requests: write
19+
statuses: write
1920
steps:
2021
- name: Download artefacts
2122
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
@@ -41,30 +42,49 @@ jobs:
4142
set -x
4243
> requisites-before.list
4344
> requisites-after.list
44-
TABLE=$(
45+
{
4546
echo '| Platform | Number of requisites | Proportion of new derivations |'
4647
echo '| :-: | -: | -: |'
4748
TOTAL_BEFORE=0
4849
TOTAL_AFTER=0
4950
TOTAL_CHANGED=0
5051
QUOTE='`'
5152
for PLATFORM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do
53+
if [ ! -f "requisites-${PLATFORM}-before.list" ] || [ ! -f "requisites-${PLATFORM}-after.list" ]; then
54+
echo "| ${QUOTE}${PLATFORM}${QUOTE} | _missing data_ | _missing data_ |"
55+
continue
56+
fi
5257
BEFORE=$(wc -l < "requisites-${PLATFORM}-before.list")
5358
TOTAL_BEFORE=$(($TOTAL_BEFORE + $BEFORE))
5459
AFTER=$(wc -l < "requisites-${PLATFORM}-after.list")
5560
TOTAL_AFTER=$(($TOTAL_AFTER + $AFTER))
5661
DIFF="_no changes_"
5762
[ "$BEFORE" = "$AFTER" ] || DIFF="**$([ "$AFTER" -lt "$BEFORE" ] || echo '+')$(($AFTER - $BEFORE))**"
58-
CHANGED=$(git diff --no-index "requisites-${PLATFORM}-before.list" "requisites-${PLATFORM}-after.list" | grep -c '^+[^+]')
63+
CHANGED=$(git diff --no-index "requisites-${PLATFORM}-before.list" "requisites-${PLATFORM}-after.list" | grep -c '^+[^+]' || true)
5964
TOTAL_CHANGED=$(($TOTAL_CHANGED + $CHANGED))
6065
PERCENT=100
6166
[ "$CHANGED" = "$AFTER" ] || {
6267
PERMILLE=$((1000 * $CHANGED / $AFTER))
6368
PERCENT="$((PERMILLE / 10)).$((PERMILLE % 10))"
6469
}
6570
echo "| ${QUOTE}${PLATFORM}${QUOTE} | $BEFORE -> $AFTER => $DIFF | $CHANGED / $AFTER = $PERCENT% |"
66-
awk '{ print $0 " ('"$PLATFORM"')" }' "requisites-${PLATFORM}-before.list" >> requisites-before.list
67-
awk '{ print $0 " ('"$PLATFORM"')" }' "requisites-${PLATFORM}-after.list" >> requisites-after.list
71+
for STATE in before after; do
72+
# Split store paths into package name and version; non-default outputs are appended to the name (e.g. `brotli.lib`).
73+
awk -v OFS='\t' -v PLATFORM="$PLATFORM" '{
74+
name = substr($0, 45)
75+
pname = name
76+
version = "_unversioned_"
77+
if (match(name, /-[^A-Za-z]/)) {
78+
pname = substr(name, 1, RSTART - 1)
79+
version = substr(name, RSTART + 1)
80+
if (match(version, /-[a-z]+$/)) {
81+
pname = pname "." substr(version, RSTART + 1)
82+
version = substr(version, 1, RSTART - 1)
83+
}
84+
}
85+
print pname, version, PLATFORM, $0
86+
}' "requisites-${PLATFORM}-${STATE}.list" >> "requisites-${STATE}.list"
87+
done
6888
done
6989
7090
DIFF="_no changes_"
@@ -75,24 +95,79 @@ jobs:
7595
PERCENT="$((PERMILLE / 10)).$((PERMILLE % 10))"
7696
}
7797
echo "| **Total** | $TOTAL_BEFORE -> $TOTAL_AFTER => $DIFF | $TOTAL_CHANGED / $TOTAL_AFTER = $PERCENT% |"
78-
)
79-
sort -k1.45 requisites-before.list > requisites-before.sort
80-
sort -k1.45 requisites-after.list > requisites-after.sort
98+
} > comment.md
99+
sort requisites-before.list > requisites-before.sort
100+
sort requisites-after.list > requisites-after.sort
81101
if git diff -U0 --no-color --no-index requisites-before.sort requisites-after.sort > requisites.diff; then
82102
echo 'No changes detected.'
83103
else
84-
{
85-
echo "$TABLE"
86-
echo
87-
echo '<details><summary>Changelog</summary>'
88-
echo
89-
echo '```diff'
90-
tail -n +5 requisites.diff
91-
echo '```'
92-
echo
93-
echo '</details>'
94-
} | gh pr comment -R "$GITHUB_REPOSITORY" "$PR_NUMBER" -F -
104+
awk -F '\t' '
105+
function insert(list, v, n, a, i, out, done) {
106+
n = split(list, a, " ")
107+
for (i = 1; i <= n; i++) if (a[i] == v) return list
108+
out = ""; done = 0
109+
for (i = 1; i <= n; i++) {
110+
if (!done && v < a[i]) { out = out " " v; done = 1 }
111+
out = out " " a[i]
112+
}
113+
if (!done) out = out " " v
114+
return substr(out, 2)
115+
}
116+
function join(list, pre, post, s) {
117+
s = pre list post
118+
gsub(/ /, post ", " pre, s)
119+
return s
120+
}
121+
/^[-+]/ && NF == 4 {
122+
key = substr($1, 2) SUBSEP $3
123+
if (substr($1, 1, 1) == "-") removed[key] = insert(removed[key], $2)
124+
else added[key] = insert(added[key], $2)
125+
}
126+
END {
127+
for (key in removed) if (!(key in added)) added[key] = ""
128+
for (key in added) {
129+
split(key, k, SUBSEP)
130+
r = (key in removed) ? removed[key] : ""
131+
a = added[key]
132+
if (r == a) change = "_no version change_: " join(a)
133+
else if (r == "") change = "_added_: " join(a, "**", "**")
134+
else if (a == "") change = "_removed_: " join(r)
135+
else change = join(r) " -> " join(a, "**", "**")
136+
group[k[1] SUBSEP change] = insert(group[k[1] SUBSEP change], k[2])
137+
}
138+
for (g in group) {
139+
split(g, k, SUBSEP)
140+
rank = k[2] ~ /^_added_/ ? 0 : k[2] ~ /^_removed_/ ? 2 : k[2] ~ /^_no version change_/ ? 3 : 1
141+
print rank "\t| `" k[1] "` | " k[2] " | " join(group[g]) " |"
142+
}
143+
}
144+
' requisites.diff > versions.tsv
145+
{
146+
echo
147+
echo '<details><summary>Version changes</summary>'
148+
echo
149+
echo '| Package | Version | Platforms |'
150+
echo '| :- | :- | :- |'
151+
LC_ALL=C sort -t '`' -k1,1 -k2,2 -k3 versions.tsv | cut -f2-
152+
echo
153+
echo '</details>'
154+
} >> comment.md
155+
gh pr comment -R "$GITHUB_REPOSITORY" "$PR_NUMBER" -F comment.md
95156
fi
96157
env:
97158
GH_TOKEN: ${{ github.token }}
98159
PR_NUMBER: ${{ steps.pr-data.outputs.NUMBER }}
160+
161+
- name: Report status on the PR
162+
if: ${{ always() }}
163+
run: |
164+
gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${HEAD_SHA}" \
165+
-f state="$STATE" \
166+
-f context="$GITHUB_WORKFLOW" \
167+
-f description="$DESCRIPTION" \
168+
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
169+
env:
170+
GH_TOKEN: ${{ github.token }}
171+
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
172+
STATE: ${{ job.status == 'success' && 'success' || 'failure' }}
173+
DESCRIPTION: ${{ job.status == 'success' && 'Nix requisites changes aggregated' || 'Failed to aggregate Nix requisites changes' }}

0 commit comments

Comments
 (0)