1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
#!/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-golo-<version>-<os>-<arch>[.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 Golo ${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 Golo ${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-golo"
# 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-golo-${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-golo-"${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" <<EOM
# Turbo Golo ${TAG}
${ABOUT}
Built with $(go env GOVERSION). No runtime dependencies; \`golo\` (GoloScript) is optional and
only completion and error marks need it — the interpreter is also the language server.
$(downloadTable)
## Running it
chmod +x turbo-golo-${VERSION}-<platform>
./turbo-golo-${VERSION}-<platform> main.golo
On macOS, an unsigned download is quarantined until you say otherwise:
xattr -d com.apple.quarantine turbo-golo-${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."
|