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")