diff --git a/.github/workflows/testpr.yaml b/.github/workflows/testpr.yaml
index e71f74ab..dd008b76 100644
--- a/.github/workflows/testpr.yaml
+++ b/.github/workflows/testpr.yaml
@@ -77,6 +77,9 @@ jobs:
run: pixi run test
build:
+ permissions:
+ actions: write # drop this pull request's superseded build caches
+ contents: read
needs: plan
if: needs.plan.outputs.distros != '[]'
strategy:
@@ -242,12 +245,31 @@ jobs:
if [ -n "$newest" ]; then echo "Removing $newest"; rm -f "$newest"; fi
- name: Save build cache
+ id: save-cache
if: ${{ always() && contains(fromJSON('["success","failure","cancelled"]'), steps.build-recipes.outcome) }}
uses: actions/cache/save@v5
with:
path: ${{ env.CACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.distro }}-${{ matrix.target.platform }}-pr-${{ github.event.pull_request.number }}-${{ github.run_id }}-${{ github.run_attempt }}
+ # Only the newest cache of a pull request/distribution/platform is restored; the ones
+ # of earlier runs and attempts only push others out of the repository's 10 GB, so
+ # full rebuilds lost their progress. (Read-only token on forks: then this is skipped.)
+ - name: Drop this pull request's superseded build caches
+ if: ${{ always() && steps.save-cache.outcome == 'success' && github.event.pull_request.number }}
+ continue-on-error: true
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ PREFIX: ${{ runner.os }}-${{ matrix.distro }}-${{ matrix.target.platform }}-pr-${{ github.event.pull_request.number }}-
+ KEEP: ${{ runner.os }}-${{ matrix.distro }}-${{ matrix.target.platform }}-pr-${{ github.event.pull_request.number }}-${{ github.run_id }}-${{ github.run_attempt }}
+ run: |
+ gh cache list --repo "$GITHUB_REPOSITORY" --key "$PREFIX" --limit 100 --json id,key \
+ --jq ".[] | select(.key != \"$KEEP\") | .id" |
+ while read -r id; do
+ gh cache delete "$id" --repo "$GITHUB_REPOSITORY" && echo "deleted cache $id"
+ done
+
- name: Generate the staged build workflows (catches post-merge problems)
shell: bash -l {0}
run: |
@@ -290,12 +312,16 @@ jobs:
echo "$marker"
echo "### ABI check (linux-64)"
echo
+ cat "${files[@]}"
+ echo "About this check
"
+ echo
echo "Each package this pull request built, compared with its newest released build (\`tools/abi_check.py\`, libabigail):"
echo "- **compatible** / **additions**: the package can be bumped on its own;"
echo "- **soname** / **incompatible**: the packages that depend on it need a rebuild (build number bump) in the same pull request."
echo
- cat "${files[@]}"
echo "Exported symbols are compared (the released packages are stripped, so struct layouts aren't); unchanged installed headers rule out layout and inline changes, changed ones are listed for review. Dependents to rebuild are the released ones linking to a changed library. A changed pin is part of the comparison."
+ echo
+ echo " "
} > body.md
if [ -n "$existing" ]; then
gh api -X PATCH "repos/$REPO/issues/comments/$existing" -F body=@body.md > /dev/null
diff --git a/tools/abi_check.py b/tools/abi_check.py
index f2860d93..3fcf15c3 100644
--- a/tools/abi_check.py
+++ b/tools/abi_check.py
@@ -505,9 +505,10 @@ def rebuild_snippet(results: dict, distro: str, build_number: int | None) -> lis
def pr_summary(results: dict, distro: str, platform: str, channel: str, build_number: int | None = None) -> list[str]:
- """Markdown for the pull-request comment: what needs attention as a table, the
- rest collapsed. Packages without a release are left out; a distribution with
- nothing to compare gets no section."""
+ """Markdown for the pull-request comment: one collapsed section per distribution,
+ with the counts in its summary line, open only when dependents need a rebuild.
+ Inside, what needs attention as a table, the rest collapsed again. Packages without
+ a release are left out; a distribution with nothing to compare gets no section."""
def worst_first(item):
verdict = item[1]["verdict"]
return (-RANK.index(verdict) if verdict in RANK else 1, item[0])
@@ -518,21 +519,24 @@ def worst_first(item):
if not attention and not rest:
return []
- lines = [f"#### {distro} {platform}", ""]
+ breaking = [n for n, r in attention if r["verdict"] in ("soname", "incompatible", "error")]
+ counts = [f"**{len(breaking)} need their dependents rebuilt**" if breaking else "",
+ f"{len(attention) - len(breaking)} to review (changed headers)" if len(attention) > len(breaking) else "",
+ f"{len(rest)} can be bumped on their own" if rest else ""]
+ lines = [f"{distro} {platform}: "
+ f"{', '.join(c for c in counts if c)}
", ""]
if attention:
lines += TABLE_HEADER + [_row(n, r) for n, r in attention] + [""]
lines += rebuild_snippet(dict(attention), distro, build_number)
if rest:
- counts: dict[str, int] = {}
+ verdicts: dict[str, int] = {}
for _, res in rest:
- counts[res["verdict"]] = counts.get(res["verdict"], 0) + 1
- summary = ", ".join(f"{v}: {c}" for v, c in sorted(counts.items()))
- if not attention:
- lines += ["Nothing that needs a rebuild of other packages.", ""]
+ verdicts[res["verdict"]] = verdicts.get(res["verdict"], 0) + 1
+ summary = ", ".join(f"{v}: {c}" for v, c in sorted(verdicts.items()))
lines += [f"{len(rest)} packages can be bumped on their own ({summary})
", "",
*TABLE_HEADER, *[_row(n, r) for n, r in rest], "", " ", ""]
where = " and ".join(f"{c}/{platform}" for c in ([channel] if isinstance(channel, str) else channel))
- return lines + [f"Compared with the newest builds on {where}.", ""]
+ return lines + [f"Compared with the newest builds on {where}.", "", " ", ""]
def main() -> int:
diff --git a/tools/tests/test_abi_check.py b/tools/tests/test_abi_check.py
index 7787897a..cf6ed388 100644
--- a/tools/tests/test_abi_check.py
+++ b/tools/tests/test_abi_check.py
@@ -46,6 +46,12 @@ def test_summary_puts_the_snippet_under_the_table():
text = "\n".join(abi_check.pr_summary(results, "jazzy", "linux-64", ["https://x/test", "https://x/jazzy"], 26))
assert text.index("| foo |") < text.index("bar:\n build_number: 26") < text.index("")
assert "https://x/test/linux-64 and https://x/jazzy/linux-64" in text
+ # one section per distribution, open (with the count in the summary) when dependents need a rebuild
+ assert text.startswith("jazzy linux-64: **1 need their dependents rebuilt**, "
+ "1 can be bumped on their own
")
+ assert text.rstrip().endswith(" ")
+ quiet = "\n".join(abi_check.pr_summary({"ok": results["ok"]}, "jazzy", "linux-64", "https://x/jazzy", 26))
+ assert quiet.startswith("jazzy linux-64: 1 can be bumped on their own
")
def test_next_build_number_is_above_vinca_and_every_published_build(tmp_path, monkeypatch):