#!/bin/bash : <<'COMMENT' Stage the release artefacts of turbo-core under release/${TAG}/ Usage: ./02-build-releases.sh # TAG and ABOUT come from release.env ./02-build-releases.sh v0.2.0 # override the tag for this run (what CI does) turbo-core is a library, so where an editor's build cross-compiles one binary per platform this stages the one artefact a Go module has: an archive of the source the tag is on. `go get` never downloads it — the module proxy serves the module straight from the tag, which is why 01 alone already publishes it. The archive is here so a release page has something to verify against, and so the library can be had by somebody who does not reach the proxy. Only the Go toolchain and git are needed. The same command works on a laptop and in a Rickub CI job. What ends up in release/${TAG}/: turbo-core-.tar.gz the tagged source, under turbo-core-/ SHA256SUMS checksum of the archive README.md the go get line, the packages, how to verify COMMENT set -euo pipefail # release.env carries TAG ("v0.2.0") and ABOUT (the one-line description). It # is git-ignored (*.env), so a CI job does not have it: there the tag comes # from the command line and ABOUT from the environment, or defaults to the # tag. A tag given on the command line always wins, so a test build never # edits the file. if [ -f release.env ]; then # shellcheck source=/dev/null source release.env fi TAG="${1:-${TAG:-}}" ABOUT="${ABOUT:-Turbo Core ${TAG}}" # A tag that is not vMAJOR.MINOR.PATCH[-prerelease] is a typo — and for a Go # module it is worse than a typo: the proxy will not serve a tag it cannot read # as a version, so `go get` would fail on a release that staged perfectly. if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then echo "❌ TAG must look like v1.2.3 or v1.2.3-rc.1, got '${TAG}' (check release.env)" exit 1 fi # 01 refuses this too, but CI runs *this* script on a tag that is already # pushed, without ever running 01. The proxy serves go.mod as written, so a # published library carrying a replace tells every consumer to look for # turbo-core in a directory that does not exist on their machine. if grep -qE '^[[:space:]]*replace[[:space:]]' go.mod; then echo "❌ go.mod has a replace directive, which a published module must not" grep -nE '^[[:space:]]*replace[[:space:]]' go.mod exit 1 fi # The tag is "v0.2.0"; the assets carry the bare version, "0.2.0". VERSION="${TAG#v}" RELEASES_DIR="release/${TAG}" ARCHIVE="turbo-core-${VERSION}.tar.gz" MODULE="$(go list -m)" echo "🚀 Staging turbo-core ${TAG} — ${ABOUT}" echo "🐹 $(go version)" # The archive comes from HEAD, not from the working directory: git archive # writes what is committed. A dirty tree is therefore not an error here, but it # does mean the archive is not what you are looking at — worth saying once. if ! git diff --quiet HEAD 2>/dev/null; then echo "⚠️ the working tree has uncommitted changes; the archive is HEAD, not what is on disk" fi rm -rf "${RELEASES_DIR}" mkdir -p "${RELEASES_DIR}" echo "" echo "🔨 Checking what ships..." # Every package has to compile and pass vet before it is archived. A library # has no binary whose start proves anything, so this is the equivalent: the # whole module, built the way a consumer's build will build it. if ! go build ./...; then echo " ❌ go build ./..." exit 1 fi echo " ✅ go build ./..." if ! go vet ./...; then echo " ❌ go vet ./..." exit 1 fi echo " ✅ go vet ./..." # The prefix is what the archive unpacks into, so extracting it beside other # downloads does not scatter a go.mod and eighteen directories into the # current one. git archive --format=tar.gz --prefix="turbo-core-${VERSION}/" -o "${RELEASES_DIR}/${ARCHIVE}" HEAD echo " ✅ ${ARCHIVE}" # Extracting the archive and building it is the one proof that what ships # builds on its own — that nothing needed was left untracked, gitignored, or # only present in this checkout. The dependencies come from the shared module # cache, which the build above has just filled. extracted="$(mktemp -d)" trap 'rm -rf "${extracted}"' EXIT tar -xzf "${RELEASES_DIR}/${ARCHIVE}" -C "${extracted}" if ! (cd "${extracted}/turbo-core-${VERSION}" && go build ./... >/dev/null); then echo " ❌ the extracted archive does not build on its own" exit 1 fi echo " ✅ the extracted archive builds on its own" # checksum runs whichever of the two tools this machine has: sha256sum on # Linux, shasum on macOS. checksum() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$@" else shasum -a 256 "$@" fi } # Names only (no directory), which is what `sha256sum -c` expects to read next # to the downloaded file. (cd "${RELEASES_DIR}" && checksum "${ARCHIVE}" >SHA256SUMS) echo " ✅ SHA256SUMS" # packageTable lists the library's packages with the first line of each one's # doc comment, so the README grows and shrinks with the module rather than # repeating it by hand. Packages with no doc comment — the module root, which # holds nothing but this tooling's tests — are left out, and a pipe in a # synopsis is escaped so it cannot break the table. packageTable() { printf '| Package | What it is |\n|---|---|\n' go list -f '{{.ImportPath}}|{{.Doc}}' ./... | while IFS='|' read -r path doc; do [ -z "${doc}" ] && continue printf '| `%s` | %s |\n' "${path#"${MODULE}"/}" "${doc//|/\\|}" done } cat >"${RELEASES_DIR}/README.md" <