#!/bin/bash : <<'COMMENT' Build the release binaries and stage them under release/${TAG}/ Usage: ./02-build-releases.sh # TAG and ABOUT come from release.env ./02-build-releases.sh v1.0.0 # override the tag for this run (what CI does) This is the script the Release workflow runs when ./01-release.tag.sh pushes a tag; the workflow then attaches everything staged here to the release page. Nothing here publishes anything, so it is also the way to see what a release will contain before cutting it, or to build the binaries by hand. 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-moonbit---[.exe] one binary per platform in PLATFORMS SHA256SUMS checksums of every binary README.md the downloads, how to run and verify them COMMENT set -euo pipefail # release.env carries TAG ("v1.0.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 MoonBit ${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 install` would fail on a release that built 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 editor carrying a replace tells `go install` to look for turbo-core # in a directory that does not exist on the installer's 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 platforms a release is built for. Add or remove a line and everything # below follows: the builds, the checksums and the README. PLATFORMS=( "darwin/arm64" "linux/amd64" "linux/arm64" "windows/amd64" "windows/arm64" ) echo "🚀 Building Turbo MoonBit ${TAG} — ${ABOUT}" echo "🐹 $(go version)" RELEASES_DIR="release/${TAG}" # The tag is "v1.0.0"; the assets carry the bare version, "1.0.0". VERSION="${TAG#v}" # Where the Makefile puts the binary for this machine. BUILT="bin/turbo-moonbit" # The release is ${TAG}, so ${TAG} is what every binary here reports. The # Makefile's own default comes from `git describe`, which answers a different # question — where HEAD is — and disagrees the moment you commit after tagging. # Overriding VERSION keeps the -X paths defined in one place all the same. LDFLAGS="$(make --no-print-directory ldflags VERSION="${TAG}")" # A fresh directory, so a binary left by an earlier run for a platform since # removed from PLATFORMS cannot end up on the release page. rm -rf "${RELEASES_DIR}" mkdir -p "${RELEASES_DIR}" # The host build comes first: it is the quickest way to find a compile error, # before spending five cross-compiles on it. make build VERSION="${TAG}" if [ ! -f "${BUILT}" ]; then echo "❌ make build produced no ${BUILT}" exit 1 fi # assetName returns what the binary for a platform is called once staged. # Windows executables carry .exe, or Windows will not run them. assetName() { local goos=$1 goarch=$2 local name="turbo-moonbit-${VERSION}-${goos}-${goarch}" if [ "${goos}" = "windows" ]; then name="${name}.exe" fi printf '%s\n' "${name}" } echo "" echo "🔨 Cross-compiling for ${#PLATFORMS[@]} platforms..." for platform in "${PLATFORMS[@]}"; do goos="${platform%/*}" goarch="${platform#*/}" asset="$(assetName "${goos}" "${goarch}")" # CGO_ENABLED=0 because there is nothing to link against on the other side # of a cross-compile, and this project needs no C at all: tcell and toml # are both pure Go. # # -trimpath keeps the paths of this machine out of a binary that goes to # strangers. # # -ldflags is what makes a downloaded binary agree with the release it came # from. Without it the Go build system names the build itself, and every # asset here would report "devel" while the release page says ${TAG}. if ! CGO_ENABLED=0 GOOS="${goos}" GOARCH="${goarch}" \ go build -trimpath -ldflags "${LDFLAGS}" -o "${RELEASES_DIR}/${asset}" .; then echo " ❌ ${platform}" exit 1 fi echo " ✅ ${asset}" done # The staged asset for this machine is the only one that can be run here, and # running it is the only proof that what ships carries the version rather than # that the flags looked right. # The number has to *equal* the tag, not merely appear in the output: "0.2.0" # is a substring of "10.2.0" and of a commit hash that happens to contain it, # and a stamp that is nearly right is the failure worth catching. HOST_ASSET="$(assetName "$(go env GOOS)" "$(go env GOARCH)")" if [ -x "${RELEASES_DIR}/${HOST_ASSET}" ]; then if ! reported="$(scripts/check-version.sh "${RELEASES_DIR}/${HOST_ASSET}" "${TAG}")"; then exit 1 fi echo " ✅ ${HOST_ASSET} reports ${reported}" fi # 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 } # The workflow attaches SHA256SUMS beside the binaries, so it is written here. # One file covers every platform, which is what "sha256sum -c" expects to # read, and the names carry no directory so it works next to the downloads. (cd "${RELEASES_DIR}" && checksum turbo-moonbit-"${VERSION}"-* >SHA256SUMS) echo " ✅ SHA256SUMS" # downloadTable lists the platforms as a Markdown table, so the README grows # and shrinks with PLATFORMS rather than repeating it by hand. downloadTable() { printf '| Platform | Download |\n|---|---|\n' for platform in "${PLATFORMS[@]}"; do local goos="${platform%/*}" goarch="${platform#*/}" printf '| %s | `%s` |\n' "${platform}" "$(assetName "${goos}" "${goarch}")" done } cat >"${RELEASES_DIR}/README.md" < ./turbo-moonbit-${VERSION}- main.mbt On macOS, an unsigned download is quarantined until you say otherwise: xattr -d com.apple.quarantine turbo-moonbit-${VERSION}-darwin-arm64 ## Installing from the module proxy instead go install $(go list -m)@${TAG} ## Verifying the download sha256sum -c SHA256SUMS --ignore-missing # shasum -a 256 -c on macOS EOM echo " ✅ README.md" echo "" echo "✨ Build complete!" ls -lh "${RELEASES_DIR}" echo "" echo "💡 Nothing was published. The Release workflow runs this same script when" echo " ./01-release.tag.sh pushes ${TAG}, and attaches release/${TAG}/ to the release page."