turbo-editors/turbo-golopublic Fork 0
d710c1bba73df5d362f9462d12805186996ad134
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-golo.git
git clone ssh://git@rickub.com/turbo-editors/turbo-golo.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

install.sh · 305 lines · 9.9 KBBash Blame HistoryRaw
📦 Turbo Golo d710c1b k33g 16h ago1#!/usr/bin/env bash
2#
3# Build turbo-golo and install it where your shell can find it.
4#
5# scripts/install.sh # install into GOBIN, or GOPATH/bin
6# scripts/install.sh --prefix ~/bin # install somewhere else
7# scripts/install.sh --with-server # build and install GoloScript too
8# scripts/install.sh --uninstall # remove it again
9#
10# The build goes to a temporary file first, so a failed build never replaces a
11# working installation, and the install itself is a rename rather than a write
12# over the binary that is already there. The version is stamped in by the
13# linker, so `turbo-golo -version` names the commit it was built from.
14
15set -euo pipefail
16
17readonly BINARY=turbo-golo
18
19# --- output -----------------------------------------------------------------
20
21if [ -t 1 ]; then
22 readonly BOLD=$'\033[1m' DIM=$'\033[2m' RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' RESET=$'\033[0m'
23else
24 readonly BOLD='' DIM='' RED='' GREEN='' YELLOW='' RESET=''
25fi
26
27info() { printf '%s\n' "$*"; }
28step() { printf '%s==>%s %s\n' "$BOLD" "$RESET" "$*"; }
29ok() { printf '%s ✓%s %s\n' "$GREEN" "$RESET" "$*"; }
30warn() { printf '%s !%s %s\n' "$YELLOW" "$RESET" "$*"; }
31die() {
32 printf '%s ✗%s %s\n' "$RED" "$RESET" "$*" >&2
33 exit 1
34}
35
36usage() {
37 cat <<EOF
38${BOLD}$BINARY installer${RESET}
39
40 scripts/install.sh [options]
41
42Options:
43 -p, --prefix DIR install into DIR (default: GOBIN, or GOPATH/bin)
44 --with-server also build and install GoloScript — golo, gogolo and
45 wagolo — from source, which completion needs
46 --uninstall remove an installed $BINARY and stop
47 -h, --help show this and stop
48EOF
49}
50
51# --- arguments --------------------------------------------------------------
52
53prefix=""
54with_server=false
55uninstall=false
56
57while [ $# -gt 0 ]; do
58 case "$1" in
59 -p | --prefix)
60 [ $# -ge 2 ] || die "--prefix needs a directory"
61 prefix="$2"
62 shift 2
63 ;;
64 --with-server)
65 with_server=true
66 shift
67 ;;
68 --uninstall)
69 uninstall=true
70 shift
71 ;;
72 -h | --help)
73 usage
74 exit 0
75 ;;
76 *) die "unknown option: $1 (try --help)" ;;
77 esac
78done
79
80# --- where things are -------------------------------------------------------
81
82cd "$(dirname "${BASH_SOURCE[0]}")/.."
83readonly REPO="$PWD"
84
85command -v go >/dev/null 2>&1 || die "go is not installed: https://go.dev/dl/"
86
87# default_prefix returns where "go install" would put a binary: GOBIN when it
88# is set, GOPATH/bin otherwise. That is the directory a Go developer is most
89# likely to already have on PATH.
90default_prefix() {
91 local gobin
92 gobin="$(go env GOBIN)"
93 if [ -n "$gobin" ]; then
94 printf '%s\n' "$gobin"
95 else
96 printf '%s/bin\n' "$(go env GOPATH)"
97 fi
98}
99
100[ -n "$prefix" ] || prefix="$(default_prefix)"
101[ -n "$prefix" ] || die "cannot work out where to install; use --prefix DIR"
102readonly TARGET="$prefix/$BINARY"
103
104# --- uninstall --------------------------------------------------------------
105
106if $uninstall; then
107 step "Removing $TARGET"
108 if [ -e "$TARGET" ]; then
109 rm -f "$TARGET"
110 ok "removed"
111 else
112 warn "nothing installed at $TARGET"
113 fi
114 exit 0
115fi
116
117# --- toolchain --------------------------------------------------------------
118
119step "Checking the Go toolchain"
120
121# The requirement lives in go.mod, so this check cannot drift from the build.
122required="$(awk '/^go /{print $2; exit}' "$REPO/go.mod")"
123installed="$(go env GOVERSION)"
124installed="${installed#go}"
125
126if [ "$(printf '%s\n%s\n' "$required" "$installed" | sort -V | head -1)" != "$required" ]; then
127 die "Go $required or later is needed, but $installed is installed"
128fi
129ok "go $installed (go.mod asks for $required or later)"
130
131# --- build ------------------------------------------------------------------
132
133step "Building $BINARY"
134
135readonly STAGING="$(mktemp -d)"
136trap 'rm -rf "$STAGING"' EXIT
137
138# The version the binary reports is stamped in by the linker, so that an
139# installed editor names the commit it was actually built from rather than a
140# constant somebody forgot to bump. Outside a git checkout — installed from a
141# tarball, say — there is nothing to describe and the binary works the version
142# out from its own build information instead.
143version_pkg="rickub.com/turbo-editors/turbo-core/version"
144if describe="$(git -C "$REPO" describe --tags --dirty 2>/dev/null)"; then
145 commit="$(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || true)"
146 built="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
147 ldflags="-X '$version_pkg.stamp=$describe' -X '$version_pkg.commit=$commit' -X '$version_pkg.built=$built'"
148else
149 ldflags=""
150fi
151
152if ! go build -ldflags "$ldflags" -o "$STAGING/$BINARY" "$REPO" 2>"$STAGING/build.log"; then
153 cat "$STAGING/build.log" >&2
154 info ""
155 warn "A failure in the repository root is often a stray .go file that has"
156 warn "landed in package main. 'go vet .' names it."
157 die "build failed; nothing was installed"
158fi
159
160# Running the staged binary is the only proof that the flags above reached the
161# linker rather than merely looking right. It happens before the install, so a
162# binary that cannot name its own version never replaces a working one.
163if ! stamped="$("$REPO/scripts/check-version.sh" "$STAGING/$BINARY" "${describe:-}" "${commit:-}" 2>&1)"; then
164 info "$stamped"
165 die "the build did not carry its version; nothing was installed"
166fi
167ok "built — $stamped"
168
169# --- install ----------------------------------------------------------------
170
171step "Installing into $prefix"
172
173mkdir -p "$prefix" || die "cannot create $prefix"
174
175# Install by renaming a complete file over the target, never by writing into
176# the one that is there.
177#
178# macOS caches a binary's code signature against its inode. cp writes new bytes
179# into the existing inode, so the cached signature ends up describing something
180# else and the kernel refuses to execute the result — a reinstall that builds,
181# installs, and then will not run. A rename gives the name a fresh inode, so
182# there is nothing stale to cache. It is atomic besides: no moment at which a
183# half-written turbo-golo is on the PATH.
184#
185# The temporary has to sit in $prefix, because a rename only works within one
186# filesystem and $STAGING is somewhere else entirely.
187readonly INCOMING="$prefix/.$BINARY.incoming.$$"
188trap 'rm -rf "$STAGING"; rm -f "$INCOMING"' EXIT
189
190cp "$STAGING/$BINARY" "$INCOMING" || die "cannot write into $prefix"
191chmod 0755 "$INCOMING"
192mv -f "$INCOMING" "$TARGET" || die "cannot replace $TARGET"
193
194# Whatever the system said is the useful part: "does not run" on its own tells
195# nobody anything they can act on.
196if ! verify="$("$TARGET" -version 2>&1)"; then
197 info "$verify"
198 die "the installed binary does not run"
199fi
200version="$verify"
201ok "$version$TARGET"
202
203# --- PATH -------------------------------------------------------------------
204
205# on_path reports whether a directory is one the shell searches.
206on_path() {
207 case ":${PATH:-}:" in
208 *":$1:"*) return 0 ;;
209 *) return 1 ;;
210 esac
211}
212
213# shell_profile guesses the file that sets PATH for the user's shell.
214shell_profile() {
215 case "${SHELL:-}" in
216 */zsh) printf '~/.zshrc\n' ;;
217 */fish) printf '~/.config/fish/config.fish\n' ;;
218 *) printf '~/.bashrc\n' ;;
219 esac
220}
221
222step "Checking your PATH"
223if on_path "$prefix"; then
224 ok "$prefix is on your PATH"
225else
226 warn "$prefix is not on your PATH. Add it:"
227 info ""
228 info " echo 'export PATH=\"\$PATH:$prefix\"' >> $(shell_profile)"
229 info " exec \$SHELL"
230fi
231
232# --- the language server ----------------------------------------------------
233
234# The language server is the interpreter itself. `golo lsp` puts the same
235# binary that runs a script into language-server mode, so a machine that can
236# run Golo can complete Golo, and there is nothing separate to install.
237readonly SERVER=golo
238readonly GOLOSCRIPT_REPO=https://codeberg.org/TypeUnsafe/golo-script
239readonly GOLOSCRIPT_RELEASES="$GOLOSCRIPT_REPO/releases"
240
241# find_server looks where the editor itself looks: PATH, then /usr/local/bin,
242# which is where GoloScript's own install.sh writes.
243#
244# Finding it is not the same as its working: a file left behind by a half-undone
245# installation sits on PATH and fails only when started. So this asks it for its
246# version rather than trusting the file's existence, which is the difference
247# between "you have completion" and "you will find out you have not when you
248# press Ctrl-Space".
249find_server() {
250 local candidate
251 for candidate in \
252 "$(command -v "$SERVER" 2>/dev/null || true)" \
253 "/usr/local/bin/$SERVER"; do
254 [ -n "$candidate" ] && [ -x "$candidate" ] || continue
255 "$candidate" --version >/dev/null 2>&1 || continue
256 printf '%s\n' "$candidate"
257 return 0
258 done
259 return 1
260}
261
262# install_server builds GoloScript from source and runs its own installer,
263# which puts golo, gogolo and wagolo into /usr/local/bin. It needs git and go,
264# and the installer asks for sudo when it copies.
265install_server() {
266 command -v git >/dev/null 2>&1 || die "git is needed to fetch GoloScript"
267 local checkout
268 checkout="$(mktemp -d)"
269 git clone --depth 1 "$GOLOSCRIPT_REPO.git" "$checkout" ||
270 die "cloning GoloScript failed; precompiled binaries are at $GOLOSCRIPT_RELEASES"
271 (cd "$checkout" && ./install.sh) ||
272 die "GoloScript's installer failed; precompiled binaries are at $GOLOSCRIPT_RELEASES"
273 rm -rf "$checkout"
274}
275
276step "Checking the language server"
277
278if $with_server && ! find_server >/dev/null; then
279 info " building GoloScript from source…"
280 install_server
281fi
282
283if server_path="$(find_server)"; then
284 ok "$SERVER at $server_path — completion comes from \`golo lsp\`"
285else
286 warn "$SERVER is not installed, so there will be no completion."
287 warn "Editing, colouring and themes all work without it."
288 info ""
289 info " a binary for your platform: $GOLOSCRIPT_RELEASES"
290 info " ${DIM}or re-run this script with --with-server to build it from source${RESET}"
291fi
292
293# --- what to do next --------------------------------------------------------
294
295info ""
296step "Ready"
297info ""
298info " Open a Golo script:"
299info ""
300info " cd /path/to/your/scripts"
301info " $BINARY main.golo"
302info ""
303info " ${DIM}F10 menu · F2 save · Alt-X exit · Ctrl-Space for completion${RESET}"
304info " ${DIM}themes: $BINARY -list-themes${RESET}"
305info ""