turbo-editors/turbo-golopublic Fork 0
v1.0.0
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.

📦 Turbo Golo d710c1b · on v1.0.0 · k33g · 11h ago
install.sh · 305 lines · 9.9 KBBash Blame HistoryRaw
  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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#!/usr/bin/env bash
#
# Build turbo-golo and install it where your shell can find it.
#
#   scripts/install.sh                 # install into GOBIN, or GOPATH/bin
#   scripts/install.sh --prefix ~/bin  # install somewhere else
#   scripts/install.sh --with-server   # build and install GoloScript too
#   scripts/install.sh --uninstall     # remove it again
#
# The build goes to a temporary file first, so a failed build never replaces a
# working installation, and the install itself is a rename rather than a write
# over the binary that is already there. The version is stamped in by the
# linker, so `turbo-golo -version` names the commit it was built from.

set -euo pipefail

readonly BINARY=turbo-golo

# --- output -----------------------------------------------------------------

if [ -t 1 ]; then
	readonly BOLD=$'\033[1m' DIM=$'\033[2m' RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' RESET=$'\033[0m'
else
	readonly BOLD='' DIM='' RED='' GREEN='' YELLOW='' RESET=''
fi

info() { printf '%s\n' "$*"; }
step() { printf '%s==>%s %s\n' "$BOLD" "$RESET" "$*"; }
ok() { printf '%s  ✓%s %s\n' "$GREEN" "$RESET" "$*"; }
warn() { printf '%s  !%s %s\n' "$YELLOW" "$RESET" "$*"; }
die() {
	printf '%s  ✗%s %s\n' "$RED" "$RESET" "$*" >&2
	exit 1
}

usage() {
	cat <<EOF
${BOLD}$BINARY installer${RESET}

  scripts/install.sh [options]

Options:
  -p, --prefix DIR   install into DIR (default: GOBIN, or GOPATH/bin)
      --with-server  also build and install GoloScript — golo, gogolo and
                     wagolo — from source, which completion needs
      --uninstall    remove an installed $BINARY and stop
  -h, --help         show this and stop
EOF
}

# --- arguments --------------------------------------------------------------

prefix=""
with_server=false
uninstall=false

while [ $# -gt 0 ]; do
	case "$1" in
	-p | --prefix)
		[ $# -ge 2 ] || die "--prefix needs a directory"
		prefix="$2"
		shift 2
		;;
	--with-server)
		with_server=true
		shift
		;;
	--uninstall)
		uninstall=true
		shift
		;;
	-h | --help)
		usage
		exit 0
		;;
	*) die "unknown option: $1 (try --help)" ;;
	esac
done

# --- where things are -------------------------------------------------------

cd "$(dirname "${BASH_SOURCE[0]}")/.."
readonly REPO="$PWD"

command -v go >/dev/null 2>&1 || die "go is not installed: https://go.dev/dl/"

# default_prefix returns where "go install" would put a binary: GOBIN when it
# is set, GOPATH/bin otherwise. That is the directory a Go developer is most
# likely to already have on PATH.
default_prefix() {
	local gobin
	gobin="$(go env GOBIN)"
	if [ -n "$gobin" ]; then
		printf '%s\n' "$gobin"
	else
		printf '%s/bin\n' "$(go env GOPATH)"
	fi
}

[ -n "$prefix" ] || prefix="$(default_prefix)"
[ -n "$prefix" ] || die "cannot work out where to install; use --prefix DIR"
readonly TARGET="$prefix/$BINARY"

# --- uninstall --------------------------------------------------------------

if $uninstall; then
	step "Removing $TARGET"
	if [ -e "$TARGET" ]; then
		rm -f "$TARGET"
		ok "removed"
	else
		warn "nothing installed at $TARGET"
	fi
	exit 0
fi

# --- toolchain --------------------------------------------------------------

step "Checking the Go toolchain"

# The requirement lives in go.mod, so this check cannot drift from the build.
required="$(awk '/^go /{print $2; exit}' "$REPO/go.mod")"
installed="$(go env GOVERSION)"
installed="${installed#go}"

if [ "$(printf '%s\n%s\n' "$required" "$installed" | sort -V | head -1)" != "$required" ]; then
	die "Go $required or later is needed, but $installed is installed"
fi
ok "go $installed (go.mod asks for $required or later)"

# --- build ------------------------------------------------------------------

step "Building $BINARY"

readonly STAGING="$(mktemp -d)"
trap 'rm -rf "$STAGING"' EXIT

# The version the binary reports is stamped in by the linker, so that an
# installed editor names the commit it was actually built from rather than a
# constant somebody forgot to bump. Outside a git checkout — installed from a
# tarball, say — there is nothing to describe and the binary works the version
# out from its own build information instead.
version_pkg="rickub.com/turbo-editors/turbo-core/version"
if describe="$(git -C "$REPO" describe --tags --dirty 2>/dev/null)"; then
	commit="$(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || true)"
	built="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
	ldflags="-X '$version_pkg.stamp=$describe' -X '$version_pkg.commit=$commit' -X '$version_pkg.built=$built'"
else
	ldflags=""
fi

if ! go build -ldflags "$ldflags" -o "$STAGING/$BINARY" "$REPO" 2>"$STAGING/build.log"; then
	cat "$STAGING/build.log" >&2
	info ""
	warn "A failure in the repository root is often a stray .go file that has"
	warn "landed in package main. 'go vet .' names it."
	die "build failed; nothing was installed"
fi

# Running the staged binary is the only proof that the flags above reached the
# linker rather than merely looking right. It happens before the install, so a
# binary that cannot name its own version never replaces a working one.
if ! stamped="$("$REPO/scripts/check-version.sh" "$STAGING/$BINARY" "${describe:-}" "${commit:-}" 2>&1)"; then
	info "$stamped"
	die "the build did not carry its version; nothing was installed"
fi
ok "built — $stamped"

# --- install ----------------------------------------------------------------

step "Installing into $prefix"

mkdir -p "$prefix" || die "cannot create $prefix"

# Install by renaming a complete file over the target, never by writing into
# the one that is there.
#
# macOS caches a binary's code signature against its inode. cp writes new bytes
# into the existing inode, so the cached signature ends up describing something
# else and the kernel refuses to execute the result — a reinstall that builds,
# installs, and then will not run. A rename gives the name a fresh inode, so
# there is nothing stale to cache. It is atomic besides: no moment at which a
# half-written turbo-golo is on the PATH.
#
# The temporary has to sit in $prefix, because a rename only works within one
# filesystem and $STAGING is somewhere else entirely.
readonly INCOMING="$prefix/.$BINARY.incoming.$$"
trap 'rm -rf "$STAGING"; rm -f "$INCOMING"' EXIT

cp "$STAGING/$BINARY" "$INCOMING" || die "cannot write into $prefix"
chmod 0755 "$INCOMING"
mv -f "$INCOMING" "$TARGET" || die "cannot replace $TARGET"

# Whatever the system said is the useful part: "does not run" on its own tells
# nobody anything they can act on.
if ! verify="$("$TARGET" -version 2>&1)"; then
	info "$verify"
	die "the installed binary does not run"
fi
version="$verify"
ok "$version$TARGET"

# --- PATH -------------------------------------------------------------------

# on_path reports whether a directory is one the shell searches.
on_path() {
	case ":${PATH:-}:" in
	*":$1:"*) return 0 ;;
	*) return 1 ;;
	esac
}

# shell_profile guesses the file that sets PATH for the user's shell.
shell_profile() {
	case "${SHELL:-}" in
	*/zsh) printf '~/.zshrc\n' ;;
	*/fish) printf '~/.config/fish/config.fish\n' ;;
	*) printf '~/.bashrc\n' ;;
	esac
}

step "Checking your PATH"
if on_path "$prefix"; then
	ok "$prefix is on your PATH"
else
	warn "$prefix is not on your PATH. Add it:"
	info ""
	info "    echo 'export PATH=\"\$PATH:$prefix\"' >> $(shell_profile)"
	info "    exec \$SHELL"
fi

# --- the language server ----------------------------------------------------

# The language server is the interpreter itself. `golo lsp` puts the same
# binary that runs a script into language-server mode, so a machine that can
# run Golo can complete Golo, and there is nothing separate to install.
readonly SERVER=golo
readonly GOLOSCRIPT_REPO=https://codeberg.org/TypeUnsafe/golo-script
readonly GOLOSCRIPT_RELEASES="$GOLOSCRIPT_REPO/releases"

# find_server looks where the editor itself looks: PATH, then /usr/local/bin,
# which is where GoloScript's own install.sh writes.
#
# Finding it is not the same as its working: a file left behind by a half-undone
# installation sits on PATH and fails only when started. So this asks it for its
# version rather than trusting the file's existence, which is the difference
# between "you have completion" and "you will find out you have not when you
# press Ctrl-Space".
find_server() {
	local candidate
	for candidate in \
		"$(command -v "$SERVER" 2>/dev/null || true)" \
		"/usr/local/bin/$SERVER"; do
		[ -n "$candidate" ] && [ -x "$candidate" ] || continue
		"$candidate" --version >/dev/null 2>&1 || continue
		printf '%s\n' "$candidate"
		return 0
	done
	return 1
}

# install_server builds GoloScript from source and runs its own installer,
# which puts golo, gogolo and wagolo into /usr/local/bin. It needs git and go,
# and the installer asks for sudo when it copies.
install_server() {
	command -v git >/dev/null 2>&1 || die "git is needed to fetch GoloScript"
	local checkout
	checkout="$(mktemp -d)"
	git clone --depth 1 "$GOLOSCRIPT_REPO.git" "$checkout" ||
		die "cloning GoloScript failed; precompiled binaries are at $GOLOSCRIPT_RELEASES"
	(cd "$checkout" && ./install.sh) ||
		die "GoloScript's installer failed; precompiled binaries are at $GOLOSCRIPT_RELEASES"
	rm -rf "$checkout"
}

step "Checking the language server"

if $with_server && ! find_server >/dev/null; then
	info "  building GoloScript from source…"
	install_server
fi

if server_path="$(find_server)"; then
	ok "$SERVER at $server_path — completion comes from \`golo lsp\`"
else
	warn "$SERVER is not installed, so there will be no completion."
	warn "Editing, colouring and themes all work without it."
	info ""
	info "    a binary for your platform: $GOLOSCRIPT_RELEASES"
	info "    ${DIM}or re-run this script with --with-server to build it from source${RESET}"
fi

# --- what to do next --------------------------------------------------------

info ""
step "Ready"
info ""
info "  Open a Golo script:"
info ""
info "      cd /path/to/your/scripts"
info "      $BINARY main.golo"
info ""
info "  ${DIM}F10 menu · F2 save · Alt-X exit · Ctrl-Space for completion${RESET}"
info "  ${DIM}themes: $BINARY -list-themes${RESET}"
info ""