| 1 | #!/usr/bin/env bash |
| 2 | # Installs every bakeoff toolchain under $HOME (no sudo), idempotently. |
| 3 | # scripts/toolchains.sh [section...] sections: base qemu zig rust swift ocaml haskell nim spinel arm64root |
| 4 | # Then `source scripts/env.sh` puts them on PATH. Versions land in docs/toolchains.md. |
| 5 | set -euo pipefail |
| 6 | export PIXI_NO_TELEMETRY=1 |
| 7 | here=$(cd "$(dirname "$0")" && pwd) |
| 8 | TC=${BAKEOFF_TOOLCHAINS:-$HOME/.local/share/bakeoff-toolchains} |
| 9 | mkdir -p "$TC" "$HOME/.local/bin" |
| 10 | dl=$(mktemp -d -p "${TMPDIR:-$HOME/.cache}") |
| 11 | trap 'rm -rf "$dl"' EXIT |
| 12 | source "$here/env.sh" |
| 13 | |
| 14 | have() { command -v "$1" >/dev/null 2>&1; } |
| 15 | |
| 16 | sec_base() { # pixi, plus conda-forge packages for headers and tools Debian lacks here |
| 17 | have pixi || curl -fsSL https://pixi.sh/install.sh | PIXI_NO_PATH_UPDATE=1 bash |
| 18 | pixi global install --environment bakeoff-base sqlite unzip pkg-config ruby gmp libffi yaml |
| 19 | } |
| 20 | |
| 21 | sec_qemu() { # user-mode arm64 emulator and proot, run explicitly (no binfmt, no root) |
| 22 | if ! [ -x "$TC/qemu/qemu-aarch64-static" ]; then |
| 23 | # Debian 13's qemu-user ships statically linked binaries (qemu-user-static is transitional) |
| 24 | (cd "$dl" && apt-get download qemu-user >/dev/null) |
| 25 | dpkg-deb -x "$dl"/qemu-user_*.deb "$dl/q" |
| 26 | mkdir -p "$TC/qemu" |
| 27 | cp "$dl/q/usr/bin/qemu-aarch64" "$TC/qemu/qemu-aarch64-static" |
| 28 | fi |
| 29 | ln -sf "$TC/qemu/qemu-aarch64-static" "$HOME/.local/bin/qemu-aarch64-static" |
| 30 | if ! [ -x "$TC/proot/proot" ]; then |
| 31 | mkdir -p "$TC/proot" |
| 32 | curl -fsSL -o "$TC/proot/proot" https://proot.gitlab.io/proot/bin/proot |
| 33 | chmod +x "$TC/proot/proot" |
| 34 | fi |
| 35 | ln -sf "$TC/proot/proot" "$HOME/.local/bin/proot" |
| 36 | } |
| 37 | |
| 38 | sec_zig() { |
| 39 | if ! [ -x "$TC/zig/zig" ]; then |
| 40 | url=$(curl -fsSL https://ziglang.org/download/index.json | |
| 41 | python3 -c 'import json,sys; d=json.load(sys.stdin); v=[k for k in d if k!="master"]; v.sort(key=lambda s:[int(x) for x in s.split(".")]); print(d[v[-1]]["x86_64-linux"]["tarball"])') |
| 42 | curl -fsSL "$url" | tar -xJ -C "$dl" |
| 43 | rm -rf "$TC/zig" && mv "$dl"/zig-* "$TC/zig" |
| 44 | fi |
| 45 | ln -sf "$TC/zig/zig" "$HOME/.local/bin/zig" |
| 46 | } |
| 47 | |
| 48 | sec_rust() { |
| 49 | have rustup || curl -fsSL https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal |
| 50 | rustup toolchain install stable --profile minimal |
| 51 | rustup target add aarch64-unknown-linux-musl x86_64-unknown-linux-musl |
| 52 | have cargo-zigbuild || cargo install --locked cargo-zigbuild |
| 53 | } |
| 54 | |
| 55 | sec_swift() { # Swift 6.2 is installed; add the Static Linux SDK (musl, x86_64 + aarch64) |
| 56 | swift sdk list 2>/dev/null | grep -q static-linux && return |
| 57 | v=$(swift --version 2>&1 | sed -n 's/.*Swift version \([0-9.]*\).*/\1/p') |
| 58 | idx=$(curl -fsSL "https://www.swift.org/api/v1/install/releases.json") |
| 59 | read -r url sum < <(python3 -c ' |
| 60 | import json,sys; v=sys.argv[1]; d=json.loads(sys.argv[2]) |
| 61 | r=[x for x in d if x["name"]==v][0] |
| 62 | s=[p for p in r["platforms"] if p.get("platform")=="static-sdk" or p.get("name","").lower().startswith("static")][0] |
| 63 | print("https://download.swift.org/swift-%s-release/static-sdk/swift-%s-RELEASE/swift-%s-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz"%(v,v,v), s["checksum"])' "$v" "$idx") |
| 64 | swift sdk install "$url" --checksum "$sum" |
| 65 | } |
| 66 | |
| 67 | sec_ocaml() { # opam + OCaml 5 for x86-64; arm64 builds run in the arm64 root (see arm64root) |
| 68 | if ! have opam; then |
| 69 | curl -fsSL -o "$HOME/.local/bin/opam" "$(curl -fsSL https://api.github.com/repos/ocaml/opam/releases/latest | |
| 70 | python3 -c 'import json,sys; print([a["browser_download_url"] for a in json.load(sys.stdin)["assets"] if a["name"].endswith("x86_64-linux")][0])')" |
| 71 | chmod +x "$HOME/.local/bin/opam" |
| 72 | fi |
| 73 | [ -d "$HOME/.opam" ] || opam init --bare -n --disable-sandboxing |
| 74 | opam switch list -s | grep -qx bakeoff || opam switch create bakeoff ocaml-base-compiler.5.3.0 -y |
| 75 | opam install --switch bakeoff -y dune |
| 76 | } |
| 77 | |
| 78 | sec_haskell() { # ghcup + recommended GHC and cabal for x86-64 |
| 79 | if ! [ -x "$HOME/.ghcup/bin/ghcup" ]; then |
| 80 | curl -fsSL https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh |
| 81 | fi |
| 82 | ghcup install ghc recommended --set |
| 83 | ghcup install cabal recommended --set |
| 84 | } |
| 85 | |
| 86 | sec_mojo() { # Mojo through pixi, as a global environment |
| 87 | pixi global install --environment mojo -c https://conda.modular.com/max -c conda-forge mojo |
| 88 | } |
| 89 | |
| 90 | sec_nim() { # Nim (prebuilt x86-64 release); arm64 via zig cc as the C compiler |
| 91 | if ! [ -x "$TC/nim/bin/nim" ]; then |
| 92 | v=$(curl -fsSL https://nim-lang.org/channels/stable | head -1) |
| 93 | curl -fsSL "https://nim-lang.org/download/nim-$v-linux_x64.tar.xz" | tar -xJ -C "$dl" |
| 94 | rm -rf "$TC/nim" && mv "$dl/nim-$v" "$TC/nim" |
| 95 | fi |
| 96 | for b in nim nimble; do ln -sf "$TC/nim/bin/$b" "$HOME/.local/bin/$b"; done |
| 97 | } |
| 98 | |
| 99 | sec_spinel() { # spinel + tep + spinelkit at the pins lpay uses (paulca/lpay spinel/) |
| 100 | src="$TC/spinel-src"; mkdir -p "$src" |
| 101 | [ -d "$src/lpay" ] || gh repo clone paulca/lpay "$src/lpay" -- --depth 1 |
| 102 | [ -d "$src/tep" ] || git clone https://github.com/OriPekelman/tep "$src/tep" |
| 103 | [ -d "$src/spinelkit" ] || git clone https://github.com/OriPekelman/spinelkit "$src/spinelkit" |
| 104 | [ -d "$src/spinel" ] || git clone https://github.com/matz/spinel "$src/spinel" |
| 105 | pin=$(head -1 "$src/tep/SPINEL_PIN") |
| 106 | (cd "$src/spinel" && git fetch -q origin && git checkout -q "$pin" && make deps && make) |
| 107 | ln -sf "$src/spinel/bin/spinel" "$HOME/.local/bin/spinel" |
| 108 | ln -sf "$src/tep/bin/tep" "$HOME/.local/bin/tep" |
| 109 | } |
| 110 | |
| 111 | sec_arm64root() { # a Debian arm64 root filesystem, run through proot + qemu (no root needed) |
| 112 | root="$TC/arm64root" |
| 113 | if ! [ -x "$root/bin/sh" ]; then |
| 114 | url=$(curl -fsSL https://images.linuxcontainers.org/meta/1.0/index-system | |
| 115 | awk -F';' '$1=="debian" && $2=="trixie" && $3=="arm64" && $4=="default" {print "https://images.linuxcontainers.org"$6"rootfs.tar.xz"}' | tail -1) |
| 116 | mkdir -p "$root" |
| 117 | curl -fsSL "$url" | tar -xJ -C "$root" --exclude='dev/*' |
| 118 | fi |
| 119 | rm -f "$root/etc/resolv.conf"; cp /etc/resolv.conf "$root/etc/resolv.conf" |
| 120 | "$here/arm64.sh" sh -c 'apt-get update -qq && apt-get install -y -qq build-essential curl git ca-certificates libgmp-dev libsqlite3-dev zlib1g-dev libffi-dev pkg-config xz-utils file ocaml-nox ocaml-findlib opam ghc cabal-install libghc-network-dev >/dev/null' |
| 121 | # Mojo for arm64, natively inside the root (pixi's linux-aarch64 build) |
| 122 | "$here/arm64.sh" sh -c '[ -x /root/.pixi/bin/mojo ] || { export PIXI_NO_TELEMETRY=1; curl -fsSL https://pixi.sh/install.sh | PIXI_NO_PATH_UPDATE=1 bash >/dev/null; /root/.pixi/bin/pixi global install --environment mojo -c https://conda.modular.com/max -c conda-forge mojo; }' |
| 123 | } |
| 124 | |
| 125 | sections=("$@") |
| 126 | [ ${#sections[@]} -gt 0 ] || sections=(base qemu zig rust swift ocaml haskell nim spinel arm64root) |
| 127 | for s in "${sections[@]}"; do |
| 128 | echo "== $s" |
| 129 | "sec_$s" |
| 130 | done |
| 131 | |