| 📦 Turbo Go 3d7798b k33g 10h ago | 1 | #!/bin/bash |
| 2 | : <<'COMMENT' |
| 3 | Build the release binaries and stage them under release/${TAG}/ |
| 4 | |
| 5 | Usage: |
| 6 | ./02-build-releases.sh # TAG and ABOUT come from release.env |
| 7 | ./02-build-releases.sh v1.0.0 # override the tag for this run (what CI does) |
| 8 | |
| 9 | This is the script the Release workflow runs when ./01-release.tag.sh pushes a |
| 10 | tag; the workflow then attaches everything staged here to the release page. |
| 11 | Nothing here publishes anything, so it is also the way to see what a release |
| 12 | will contain before cutting it, or to build the binaries by hand. |
| 13 | |
| 14 | Only the Go toolchain and git are needed. The same command works on a laptop |
| 15 | and in a Rickub CI job. |
| 16 | |
| 17 | What ends up in release/${TAG}/: |
| 18 | turbo-go-<version>-<os>-<arch>[.exe] one binary per platform in PLATFORMS |
| 19 | SHA256SUMS checksums of every binary |
| 20 | README.md the downloads, how to run and verify them |
| 21 | COMMENT |
| 22 | |
| 23 | set -euo pipefail |
| 24 | |
| 25 | # release.env carries TAG ("v1.0.0") and ABOUT (the one-line description). It |
| 26 | # is git-ignored (*.env), so a CI job does not have it: there the tag comes |
| 27 | # from the command line and ABOUT from the environment, or defaults to the |
| 28 | # tag. A tag given on the command line always wins, so a test build never |
| 29 | # edits the file. |
| 30 | if [ -f release.env ]; then |
| 31 | # shellcheck source=/dev/null |
| 32 | source release.env |
| 33 | fi |
| 34 | TAG="${1:-${TAG:-}}" |
| 35 | ABOUT="${ABOUT:-Turbo Go ${TAG}}" |
| 36 | |
| 37 | # A tag that is not vMAJOR.MINOR.PATCH[-prerelease] is a typo — and for a Go |
| 38 | # module it is worse than a typo: the proxy will not serve a tag it cannot read |
| 39 | # as a version, so `go install` would fail on a release that built perfectly. |
| 40 | if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then |
| 41 | echo "❌ TAG must look like v1.2.3 or v1.2.3-rc.1, got '${TAG}' (check release.env)" |
| 42 | exit 1 |
| 43 | fi |
| 44 | |
| 45 | # 01 refuses this too, but CI runs *this* script on a tag that is already |
| 46 | # pushed, without ever running 01. The proxy serves go.mod as written, so a |
| 47 | # published editor carrying a replace tells `go install` to look for turbo-core |
| 48 | # in a directory that does not exist on the installer's machine. |
| 49 | if grep -qE '^[[:space:]]*replace[[:space:]]' go.mod; then |
| 50 | echo "❌ go.mod has a replace directive, which a published module must not" |
| 51 | grep -nE '^[[:space:]]*replace[[:space:]]' go.mod |
| 52 | exit 1 |
| 53 | fi |
| 54 | |
| 55 | # The platforms a release is built for. Add or remove a line and everything |
| 56 | # below follows: the builds, the checksums and the README. |
| 57 | PLATFORMS=( |
| 58 | "darwin/arm64" |
| 59 | "linux/amd64" |
| 60 | "linux/arm64" |
| 61 | "windows/amd64" |
| 62 | "windows/arm64" |
| 63 | ) |
| 64 | |
| 65 | echo "🚀 Building Turbo Go ${TAG} — ${ABOUT}" |
| 66 | echo "🐹 $(go version)" |
| 67 | |
| 68 | RELEASES_DIR="release/${TAG}" |
| 69 | |
| 70 | # The tag is "v1.0.0"; the assets carry the bare version, "1.0.0". |
| 71 | VERSION="${TAG#v}" |
| 72 | |
| 73 | # Where the Makefile puts the binary for this machine. |
| 74 | BUILT="bin/turbo-go" |
| 75 | |
| 76 | # The release is ${TAG}, so ${TAG} is what every binary here reports. The |
| 77 | # Makefile's own default comes from `git describe`, which answers a different |
| 78 | # question — where HEAD is — and disagrees the moment you commit after tagging. |
| 79 | # Overriding VERSION keeps the -X paths defined in one place all the same. |
| 80 | LDFLAGS="$(make --no-print-directory ldflags VERSION="${TAG}")" |
| 81 | |
| 82 | # A fresh directory, so a binary left by an earlier run for a platform since |
| 83 | # removed from PLATFORMS cannot end up on the release page. |
| 84 | rm -rf "${RELEASES_DIR}" |
| 85 | mkdir -p "${RELEASES_DIR}" |
| 86 | |
| 87 | # The host build comes first: it is the quickest way to find a compile error, |
| 88 | # before spending five cross-compiles on it. |
| 89 | make build VERSION="${TAG}" |
| 90 | |
| 91 | if [ ! -f "${BUILT}" ]; then |
| 92 | echo "❌ make build produced no ${BUILT}" |
| 93 | exit 1 |
| 94 | fi |
| 95 | |
| 96 | # assetName returns what the binary for a platform is called once staged. |
| 97 | # Windows executables carry .exe, or Windows will not run them. |
| 98 | assetName() { |
| 99 | local goos=$1 goarch=$2 |
| 100 | local name="turbo-go-${VERSION}-${goos}-${goarch}" |
| 101 | |
| 102 | if [ "${goos}" = "windows" ]; then |
| 103 | name="${name}.exe" |
| 104 | fi |
| 105 | printf '%s\n' "${name}" |
| 106 | } |
| 107 | |
| 108 | echo "" |
| 109 | echo "🔨 Cross-compiling for ${#PLATFORMS[@]} platforms..." |
| 110 | |
| 111 | for platform in "${PLATFORMS[@]}"; do |
| 112 | goos="${platform%/*}" |
| 113 | goarch="${platform#*/}" |
| 114 | asset="$(assetName "${goos}" "${goarch}")" |
| 115 | |
| 116 | # CGO_ENABLED=0 because there is nothing to link against on the other side |
| 117 | # of a cross-compile, and this project needs no C at all: tcell and toml |
| 118 | # are both pure Go. |
| 119 | # |
| 120 | # -trimpath keeps the paths of this machine out of a binary that goes to |
| 121 | # strangers. |
| 122 | # |
| 123 | # -ldflags is what makes a downloaded binary agree with the release it came |
| 124 | # from. Without it the Go build system names the build itself, and every |
| 125 | # asset here would report "devel" while the release page says ${TAG}. |
| 126 | if ! CGO_ENABLED=0 GOOS="${goos}" GOARCH="${goarch}" \ |
| 127 | go build -trimpath -ldflags "${LDFLAGS}" -o "${RELEASES_DIR}/${asset}" .; then |
| 128 | echo " ❌ ${platform}" |
| 129 | exit 1 |
| 130 | fi |
| 131 | echo " ✅ ${asset}" |
| 132 | done |
| 133 | |
| 134 | # The staged asset for this machine is the only one that can be run here, and |
| 135 | # running it is the only proof that what ships carries the version rather than |
| 136 | # that the flags looked right. |
| 137 | # The number has to *equal* the tag, not merely appear in the output: "0.2.0" |
| 138 | # is a substring of "10.2.0" and of a commit hash that happens to contain it, |
| 139 | # and a stamp that is nearly right is the failure worth catching. |
| 140 | HOST_ASSET="$(assetName "$(go env GOOS)" "$(go env GOARCH)")" |
| 141 | if [ -x "${RELEASES_DIR}/${HOST_ASSET}" ]; then |
| 142 | if ! reported="$(scripts/check-version.sh "${RELEASES_DIR}/${HOST_ASSET}" "${TAG}")"; then |
| 143 | exit 1 |
| 144 | fi |
| 145 | echo " ✅ ${HOST_ASSET} reports ${reported}" |
| 146 | fi |
| 147 | |
| 148 | # checksum runs whichever of the two tools this machine has: sha256sum on |
| 149 | # Linux, shasum on macOS. |
| 150 | checksum() { |
| 151 | if command -v sha256sum >/dev/null 2>&1; then |
| 152 | sha256sum "$@" |
| 153 | else |
| 154 | shasum -a 256 "$@" |
| 155 | fi |
| 156 | } |
| 157 | |
| 158 | # The workflow attaches SHA256SUMS beside the binaries, so it is written here. |
| 159 | # One file covers every platform, which is what "sha256sum -c" expects to |
| 160 | # read, and the names carry no directory so it works next to the downloads. |
| 161 | (cd "${RELEASES_DIR}" && checksum turbo-go-"${VERSION}"-* >SHA256SUMS) |
| 162 | echo " ✅ SHA256SUMS" |
| 163 | |
| 164 | # downloadTable lists the platforms as a Markdown table, so the README grows |
| 165 | # and shrinks with PLATFORMS rather than repeating it by hand. |
| 166 | downloadTable() { |
| 167 | printf '| Platform | Download |\n|---|---|\n' |
| 168 | for platform in "${PLATFORMS[@]}"; do |
| 169 | local goos="${platform%/*}" goarch="${platform#*/}" |
| 170 | printf '| %s | `%s` |\n' "${platform}" "$(assetName "${goos}" "${goarch}")" |
| 171 | done |
| 172 | } |
| 173 | |
| 174 | cat >"${RELEASES_DIR}/README.md" <<EOM |
| 175 | # Turbo Go ${TAG} |
| 176 | |
| 177 | ${ABOUT} |
| 178 | |
| 179 | Built with $(go env GOVERSION). No runtime dependencies; \`gopls\` is optional and |
| 180 | only completion needs it. |
| 181 | |
| 182 | $(downloadTable) |
| 183 | |
| 184 | ## Running it |
| 185 | |
| 186 | chmod +x turbo-go-${VERSION}-<platform> |
| 187 | ./turbo-go-${VERSION}-<platform> main.go |
| 188 | |
| 189 | On macOS, an unsigned download is quarantined until you say otherwise: |
| 190 | |
| 191 | xattr -d com.apple.quarantine turbo-go-${VERSION}-darwin-arm64 |
| 192 | |
| 193 | ## Installing from the module proxy instead |
| 194 | |
| 195 | go install $(go list -m)@${TAG} |
| 196 | |
| 197 | ## Verifying the download |
| 198 | |
| 199 | sha256sum -c SHA256SUMS --ignore-missing # shasum -a 256 -c on macOS |
| 200 | |
| 201 | EOM |
| 202 | echo " ✅ README.md" |
| 203 | |
| 204 | echo "" |
| 205 | echo "✨ Build complete!" |
| 206 | ls -lh "${RELEASES_DIR}" |
| 207 | echo "" |
| 208 | echo "💡 Nothing was published. The Release workflow runs this same script when" |
| 209 | echo " ./01-release.tag.sh pushes ${TAG}, and attaches release/${TAG}/ to the release page." |