turbo-editors/turbo-moonbitpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-moonbit.git
git clone ssh://git@rickub.com/turbo-editors/turbo-moonbit.git

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

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