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
50 changes: 36 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# The release. T-18.
#
# ⛔ NOTHING PUBLISHES ON A RED SUITE. The evidence table runs first, on both
# architectures, on real silicon for aarch64, and the build does not start
# until it is green.
# ⛔ NOTHING PUBLISHES ON A RED SUITE. The evidence table runs first, on the
# two architectures that have runners, on real silicon for aarch64, and the
# build does not start until it is green.
#
# ⚠ The evidence table covers x86_64 and aarch64 and NOTHING ELSE: the four
# cross-compiled architectures have no runner and no demo AppImages, so their
# artefacts are released on the strength of the cross build plus
# scripts/verify-artifacts.sh, with the trampolines run under qemu-user and
# nothing else. That is recorded, not assumed, in
# docs/report/10-measured-versus-assumed.md 10 and docs/limits.md. A red
# evidence table still blocks them: it blocks the whole release.
#
# ⛔ NOTHING PUBLISHES FROM A BRANCH THAT HAS NOT BEEN THROUGH A PULL REQUEST.
# A tag can be pushed at any commit, so that rule is checked rather than
Expand All @@ -27,7 +35,7 @@ name: release
# first release is a release workflow nobody has tested, and workflow_dispatch
# cannot reach a workflow that is not yet on the default branch, so a pull
# request is the only place this can be proven before it matters.
# ⚠ `paths` keeps it off every unrelated pull request: it is a two-architecture
# ⚠ `paths` keeps it off every unrelated pull request: it is a six-architecture
# build and it is not a fast gate.
on:
push:
Expand Down Expand Up @@ -78,9 +86,11 @@ jobs:
build:
name: build and package ${{ matrix.arch }}
needs: gate
# ⛔ BOTH architectures build on ubuntu-latest, and that is deliberate.
# scripts/build.sh CROSS-compiles aarch64 inside the x86-64 floor image.
# `podman run --platform linux/arm64` would pull a tag for another
# ⛔ EVERY architecture builds on ubuntu-latest, and that is deliberate.
# scripts/build.sh CROSS-compiles aarch64 and the four newer targets
# inside an x86-64 floor image (trixie for loongarch64, whose gcc-14 cross
# compiler is the first to exist).
# `podman run --platform linux/<arch>` would pull a tag for another
# platform, REPLACE the cached image for that tag, and leave the next
# x86-64 job with "Exec format error". docs/building.md says what it cost.
runs-on: ubuntu-latest
Expand All @@ -93,7 +103,7 @@ jobs:
strategy:
fail-fast: false
matrix:
arch: [x86_64, aarch64]
arch: [x86_64, aarch64, riscv64, ppc64, ppc64le, loongarch64]
variant: [default, portable]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -177,15 +187,27 @@ jobs:
path: staged

# download-artifact writes one directory per uploaded artifact, so the
# two architectures arrive as staged/release-x86_64/... and
# staged/release-aarch64/.... Flatten them into the layout the two
# scripts expect, and fail if either is missing rather than publishing
# half a release.
# six architectures arrive as staged/release-x86_64/... through
# staged/release-loongarch64/.... Flatten them into the layout the two
# scripts expect, and fail if ANY is missing rather than publishing
# half a release. The first loop is the other direction: a matrix row
# added above without widening this list would ship silently as a
# release without that architecture, which is exactly the defect this
# step exists to refuse.
- name: collect every architecture and variant
run: |
set -eu
arches="x86_64 aarch64 riscv64 ppc64 ppc64le loongarch64"
for d in staged/release-*; do
[ -d "$d" ] || { echo "no build artifacts were downloaded"; exit 1; }
a=${d#staged/release-}; a=${a%-*}
case " $arches " in
*" $a "*) ;;
*) echo "$a produced artifacts but is not in the collect list"; exit 1 ;;
esac
done
mkdir -p dist build
for a in x86_64 aarch64; do
for a in $arches; do
for v in default portable; do
src=staged/release-$a-$v
[ -d "$src/dist" ] || { echo "no dist/ for $a/$v"; exit 1; }
Expand All @@ -210,7 +232,7 @@ jobs:
# ⚠ Only the DEFAULT variant ships loose files to check this way.
# The portable set ships as archives, and package-release.sh already
# checked every object in it against its manifest before archiving.
for a in x86_64 aarch64; do
for a in x86_64 aarch64 riscv64 ppc64 ppc64le loongarch64; do
man=build/$a/build-manifest.json
for f in $(jq -r '.artifacts | keys[]' "$man"); do
want=$(jq -r --arg f "$f" '.artifacts[$f].sha256' "$man")
Expand Down
54 changes: 40 additions & 14 deletions scripts/release-notes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,54 @@ for d in "$BUILDS"/*; do
done
[ -n "$dirs" ] || { echo "release-notes: no build-manifest.json under $BUILDS" >&2; exit 2; }

# The floor is a property of the build environment and every build here uses
# the same one. If two manifests disagree, that is a finding and not something
# to paper over with the first value.
floor=""
# The floor is a property of the build environment, and it is per
# architecture: loongarch64 cannot exist below glibc 2.36, so its floor is
# 2.36 while everything else here is built on 2.31. The manifests are grouped
# by architecture, and the two VARIANTS of one architecture must agree: a
# disagreement there is a finding and not something to paper over with the
# first value. The table below is what the body states instead of the single
# floor it used to state, which refused the moment a second floor appeared.
arches=""
for d in $dirs; do
f=$(jq -r '.floor_glibc' "$BUILDS/$d/build-manifest.json")
if [ -z "$floor" ]; then floor=$f
elif [ "$floor" != "$f" ]; then
echo "release-notes: manifests disagree about the floor: $floor and $f" >&2
exit 2
fi
m=$BUILDS/$d/build-manifest.json
a=$(jq -r '.arch' "$m")
f=$(jq -r '.floor_glibc' "$m")
[ "$f" != unknown ] && [ -n "$f" ] ||
{ echo "release-notes: $d records no glibc floor. A release states its floor or it is not made." >&2; exit 2; }
case " $arches " in *" $a "*) ;; *) arches="$arches $a" ;; esac
done

floor_of() { # floor_of <arch>: the one floor its variants agree on
a=$1; found=""
for d in $dirs; do
m=$BUILDS/$d/build-manifest.json
[ "$(jq -r '.arch' "$m")" = "$a" ] || continue
f=$(jq -r '.floor_glibc' "$m")
if [ -z "$found" ]; then found=$f
elif [ "$found" != "$f" ]; then
echo "release-notes: the $a builds disagree about the floor: $found and $f" >&2
exit 2
fi
done
printf '%s' "$found"
}

printf '## cross-libc `dlopen` %s\n\n' "$TAG"
printf 'Load the host'"'"'s GPU drivers into a process that carries its own libc.\n'
printf 'See the [README](https://github.com/pkgforge-dev/cross-libc-dlopen#readme)\n'
printf 'for what the two gaps are and which one your symptom is.\n\n'

printf '**Built on glibc %s**, which is the floor every artefact here is held\n' "$floor"
printf 'to. An object needing a symbol version above it would fail to load inside\n'
printf 'a bundle whose glibc is older, so the build refuses to produce one and\n'
printf 'the packaging step refuses to publish one.\n\n'
printf '**Every artefact is held to its own build'"'"'s glibc floor**, read out of\n'
printf '`build-manifest.json` rather than stated here. An object needing a symbol\n'
printf 'version above its floor would fail to load inside a bundle whose glibc is\n'
printf 'older, so the build refuses to produce one and the packaging step refuses\n'
printf 'to publish one.\n\n'
printf '| architecture | floor glibc |\n|---|---|\n'
for a in $arches; do
f=$(floor_of "$a") || exit 2
printf '| %s | %s |\n' "$a" "$f"
done
printf '\n'

# ------------------------------------------------------------- the changelog --
printf '### Changes\n\n'
Expand Down