From 80da7e62757307e9cbfd1cf020545d87643e6dc6 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sat, 5 Sep 2026 04:04:20 +0300 Subject: [PATCH] fix(release): per-artifact checksums so a release can span hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_artifacts wrote a single dist/SHA256SUMS covering whatever that host happened to build. But a release is assembled from more than one machine — the Linux runner cannot produce the darwin-arm64 toolchain — and forgejo_upload_assets deliberately skips an asset whose name is already attached. So the first host to publish wrote SHA256SUMS, and every artifact added later was silently left uncovered by it. Emit one .sha256 per tarball instead. The names are unique, so each host's contribution stands on its own and nothing goes stale. Co-Authored-By: Claude Opus 5 --- .gitignore | 6 ++++++ CONTRIBUTING.md | 17 ++++++++++++++--- changes/per-artifact-checksums.md | 9 +++++++++ tools/x/release.ludic | 15 ++++++++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 changes/per-artifact-checksums.md diff --git a/.gitignore b/.gitignore index 0bc83a34..591a37c6 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,9 @@ __pycache__/ # Release artifacts produced by `x release` /dist/ + +# Build/release tarballs anywhere in the tree. `git -C archive -o foo.tgz` +# resolves -o relative to the repo, not the caller's directory, so a stray +# archive lands in the root and a blanket `git add -A` will commit it. +*.tar.gz +*.tgz diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d51cfb1..eb4d23a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,17 +93,28 @@ git push origin main --follow-tags **Pushing the tag is what publishes.** The `release` workflow builds the toolchain from the IR seed, runs `x test`, `x test-tools` and `x bootstrap-cfree` against the tagged tree, and only then creates the Forgejo release — with the -source tarball, a Linux toolchain build, `SHA256SUMS`, and that version's +source tarball, a Linux toolchain build, a `.sha256` beside each, and that version's `CHANGELOG.md` section as the notes. It refuses to publish if the tag and `VERSION` disagree or the changelog has no section for it. -macOS artifacts cannot be produced on the Linux runner. To attach one, run the -same command CI runs from a Mac — it only adds assets the release is missing: +macOS artifacts cannot be produced on the Linux runner — a `darwin-arm64` build +needs a macOS host, and there is no cross-compile path (it would need the Xcode +SDK and a Mach-O linker). Attaching one therefore means either registering a +macOS runner and giving it a job, or running the same command CI runs from a +Mac. Either way it is `x publish`, which only adds assets the release is missing: ```bash FORGEJO_TOKEN=… x publish v0.4.0 ``` +Checksums are one `.sha256` file per artifact rather than a single `SHA256SUMS`, +precisely because a release can be assembled from more than one host and an +asset that already exists is never overwritten. Verify one with: + +```bash +shasum -a 256 -c ludic-0.4.0-src.tar.gz.sha256 +``` + The tag doubles as the reproducible bootstrap point: the source archive plus its checked-in seed rebuild that exact toolchain. diff --git a/changes/per-artifact-checksums.md b/changes/per-artifact-checksums.md new file mode 100644 index 00000000..a94f83e5 --- /dev/null +++ b/changes/per-artifact-checksums.md @@ -0,0 +1,9 @@ +bump: patch +type: fix +Release checksums are one `.sha256` file per artifact instead of a single +`SHA256SUMS`. A release is assembled from more than one host — a Linux runner +cannot build the macOS toolchain — and `x publish` never overwrites an asset that +is already attached, so a shared `SHA256SUMS` was written by whichever host +published first and then never covered anything added afterwards. Per-artifact +names compose across hosts. Verify one with +`shasum -a 256 -c ludic-X.Y.Z-src.tar.gz.sha256`. diff --git a/tools/x/release.ludic b/tools/x/release.ludic index 9965e5a5..295064bc 100644 --- a/tools/x/release.ludic +++ b/tools/x/release.ludic @@ -305,8 +305,17 @@ function sha256_cmd() -> pointer { } # Build the release artifacts into dist/: a reproducible source+seed tarball from -# the tag, the toolchain built on this host, and a SHA256SUMS covering both — a +# the tag, the toolchain built on this host, and one `.sha256` beside each — a # release without checksums asks everyone downstream to trust the transport. +# +# The checksums are per-artifact rather than a single SHA256SUMS on purpose. A +# release is assembled from more than one host (a Linux runner cannot build the +# macOS toolchain), and `forgejo_upload_assets` skips an asset whose name is +# already attached — so a shared SHA256SUMS would be written by whichever host +# published first and would then never cover anything added later. One file per +# artifact has a unique name, so each host's contribution stands on its own. +# +# shasum -a 256 -c ludic-X.Y.Z-src.tar.gz.sha256 function build_artifacts(ver: pointer) -> bool { run("rm -rf dist && mkdir -p dist") if not shq(`git archive --format=tar.gz --prefix=ludic-{ver}/ -o dist/ludic-{ver}-src.tar.gz v{ver}`) { @@ -321,8 +330,8 @@ function build_artifacts(ver: pointer) -> bool { return false } } - if not shq(`cd dist && {sha256_cmd()} *.tar.gz > SHA256SUMS`) { - err("release: writing dist/SHA256SUMS failed\n") + if not shq(`cd dist && for f in *.tar.gz; do {sha256_cmd()} "$f" > "$f.sha256" || exit 1; done`) { + err("release: writing the per-artifact .sha256 files failed\n") return false } run("ls -l dist")