Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion .github/workflows/testpr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -290,12 +312,16 @@ jobs:
echo "$marker"
echo "### ABI check (linux-64)"
echo
cat "${files[@]}"
echo "<details><summary>About this check</summary>"
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 "</details>"
} > body.md
if [ -n "$existing" ]; then
gh api -X PATCH "repos/$REPO/issues/comments/$existing" -F body=@body.md > /dev/null
Expand Down
24 changes: 14 additions & 10 deletions tools/abi_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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"<details{' open' if breaking else ''}><summary><b>{distro} {platform}</b>: "
f"{', '.join(c for c in counts if c)}</summary>", ""]
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"<details><summary>{len(rest)} packages can be bumped on their own ({summary})</summary>", "",
*TABLE_HEADER, *[_row(n, r) for n, r in rest], "", "</details>", ""]
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}.", "", "</details>", ""]


def main() -> int:
Expand Down
6 changes: 6 additions & 0 deletions tools/tests/test_abi_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("<details>")
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("<details open><summary><b>jazzy linux-64</b>: **1 need their dependents rebuilt**, "
"1 can be bumped on their own</summary>")
assert text.rstrip().endswith("</details>")
quiet = "\n".join(abi_check.pr_summary({"ok": results["ok"]}, "jazzy", "linux-64", "https://x/jazzy", 26))
assert quiet.startswith("<details><summary><b>jazzy linux-64</b>: 1 can be bumped on their own</summary>")


def test_next_build_number_is_above_vinca_and_every_published_build(tmp_path, monkeypatch):
Expand Down
Loading