| A Mesa nobody needed, and the AppImage that carried it 9db383c nandi 20h ago | 1 | #!/usr/bin/env bash |
| 2 | # The cosmic desktop build's toolchain: a jolt binary, the native objects, and |
| 3 | # the two Jolt libraries the launcher hands to `-Sdeps`. Each pinned, fetched |
| 4 | # into a directory, and that is the whole of it. No nix. |
| 5 | # |
| 6 | # This is `tools/toolchain.sh` for the other target. That one fetches a |
| 7 | # Flutter, a JDK and a Clojure CLI because the web build is a compiler run; |
| 8 | # this one fetches a runtime and its libraries because the cosmic build is not |
| 9 | # a compilation at all — jolt reads source at startup, so "building" frq for |
| 10 | # the desktop means putting the right files next to each other. |
| 11 | # |
| 12 | # What made this possible is that every piece is now published as something a |
| 13 | # machine without nix can use: |
| 14 | # |
| 15 | # jolt one binary, chez linked in statically. Stock /lib64 |
| 16 | # interpreter, NEEDED libc and libm and nothing else. |
| 17 | # jolt-native the `portable` tarball — the backends with their NEEDED |
| 18 | # closure beside them and RUNPATH $ORIGIN. The plain |
| 19 | # x86_64-linux tarball is NOT this: those objects resolve |
| 20 | # through the builder's /nix/store and are for nix consumers. |
| 21 | # libmoq_ffi an upstream release object, no RUNPATH, needs libgcc_s. |
| 22 | # glimmer Jolt source. Read, not linked. |
| 23 | # |
| 24 | # What is NOT here, and has to be on the machine that RUNS the result: the GL |
| 25 | # driver, and glibc. That is deliberate and it is what replaces the AppImage — |
| 26 | # nix-appimage carried a Mesa, which is why the launcher needed a nixGL to put |
| 27 | # the host's driver in front of it. Carrying no Mesa needs no nixGL. |
| 28 | # |
| 29 | # tools/desktop-toolchain.sh fetch whatever is missing |
| 30 | # eval "$(tools/desktop-toolchain.sh env)" ...and set this shell up |
| 31 | # tools/desktop-toolchain.sh exec -- jolt --version |
| 32 | # |
| 33 | # `FRQ_DESKTOP_TOOLCHAIN` says where it lives; the default is `.toolchain- |
| 34 | # desktop/` at the top of the checkout, and the container points it at a |
| 35 | # volume so the fetch happens once across runs rather than once across |
| 36 | # containers. |
| 37 | set -euo pipefail |
| 38 | |
| 39 | root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| 40 | TC="${FRQ_DESKTOP_TOOLCHAIN:-$root/.toolchain-desktop}" |
| 41 | |
| 42 | # The pins. A URL and a hash, and nothing resolved at run time — the same rule |
| 43 | # tools/toolchain.sh states and for the same reason. |
| 44 | |
| 45 | # The runtime. One binary out of the fork's release, and it really is one |
| 46 | # binary: `patchelf --print-needed` on it says libm and libc. |
| 47 | # |
| 48 | # To move it: tag a release in gitlab.com/nandithebull/jolt and take the URL |
| 49 | # and sha256 from its asset. The version string the binary prints is the tag, |
| 50 | # so a mismatch between this pin and the flake's `jolt-src` rev is visible in |
| 51 | # `jolt --version` rather than silent. |
| 52 | JOLT_VERSION="v0.7.28-1-g2b80d68d" |
| 53 | JOLT_URL="https://gitlab.com/-/project/85910549/uploads/c67e91d30934c404583c004710c249a6/jolt-${JOLT_VERSION}-x86_64-linux.tar.gz" |
| 54 | JOLT_SHA="aabb71f809aebd9d607b7a5036229f933c733ae90785cb29df899f116be9589f" |
| 55 | |
| 56 | # The native backends, and the Jolt source that binds them, at ONE revision. |
| 57 | # |
| 58 | # One variable for both on purpose. glimmer-cosmic talks to libjoltcosmic over |
| 59 | # a retained-tree ABI that is not versioned, and the flake's comment on the |
| 60 | # jolt-native input says what drift costs: the Jolt half sent a reaction |
| 61 | # pill's hover card to a backend with no handler for one, and the pill said |
| 62 | # nothing. The source and the object are the same commit here by construction. |
| 63 | JOLT_NATIVE_REV="65c27be020b52eb87d0c0718c8cfff1869e8d2f7" |
| 64 | JOLT_NATIVE_URL="https://gitlab.com/api/v4/projects/85910092/packages/generic/jolt-native/${JOLT_NATIVE_REV}/x86_64-linux-portable.tar.gz" |
| 65 | # Filled in from the first pipeline that publishes this revision's tarball: |
| 66 | # |
| 67 | # curl -fsSL "$JOLT_NATIVE_URL" | sha256sum |
| 68 | # |
| 69 | # Left as the placeholder deliberately rather than omitted — an unpinned |
| 70 | # fetch of a URL under a package registry that serves the NEWEST upload for a |
| 71 | # given name is exactly the moving target these pins exist to refuse. |
| 72 | JOLT_NATIVE_SHA="${FRQ_JOLT_NATIVE_SHA:-0000000000000000000000000000000000000000000000000000000000000000}" |
| 73 | |
| 74 | # MoQ over QUIC behind UniFFI's C ABI, from upstream's release rather than |
| 75 | # built. Same object and same version the flake fetches. |
| 76 | MOQ_FFI_VERSION="0.3.17" |
| 77 | # The triple is the Rust one and not the nix system name -- `x86_64-linux` |
| 78 | # gets a 404 from this URL, which is how that got noticed. |
| 79 | MOQ_FFI_TARGET="x86_64-unknown-linux-gnu" |
| 80 | MOQ_FFI_URL="https://github.com/kixelated/moq/releases/download/moq-ffi-v${MOQ_FFI_VERSION}/moq-ffi-${MOQ_FFI_VERSION}-${MOQ_FFI_TARGET}-libmoq_ffi.so" |
| 81 | MOQ_FFI_SHA="773417a55e0981db43fa0df7597e7514501075f0945e436cc75c4f6e86cf7d42" |
| 82 | |
| 83 | # glimmer, at the rev deps.edn pins. Source, so it is cloned rather than |
| 84 | # fetched as an archive: a git rev is immutable in a way a forge's generated |
| 85 | # tarball is not — those are re-compressed across forge versions, and a |
| 86 | # sha256 over one is a pin that breaks without anything having changed. |
| 87 | GLIMMER_REPO="https://gitlab.com/nandithebull/glimmer.git" |
| 88 | GLIMMER_REV="399df371c790d690fb6e4560c3d4d7f838502857" |
| 89 | |
| 90 | JOLT_NATIVE_REPO="https://gitlab.com/nandithebull/jolt-native.git" |
| 91 | |
| 92 | # What the host still has to bring. Small and boring, but a missing one fails |
| 93 | # further from here with a worse message. |
| 94 | require_host_tools() { |
| 95 | local missing=() |
| 96 | for t in curl tar git sha256sum; do |
| 97 | command -v "$t" >/dev/null 2>&1 || missing+=("$t") |
| 98 | done |
| 99 | if [ ${#missing[@]} -gt 0 ]; then |
| 100 | echo "desktop-toolchain: this needs ${missing[*]} on PATH and cannot fetch them" >&2 |
| 101 | exit 1 |
| 102 | fi |
| 103 | } |
| 104 | |
| 105 | # One archive, unpacked once. The stamp holds the hash rather than the |
| 106 | # version, and the work happens in `.tmp` so a half-finished unpack is never |
| 107 | # mistaken for a finished one — `tools/toolchain.sh` explains at length. |
| 108 | install_archive() { |
| 109 | local name=$1 url=$2 sha=$3 strip=$4 |
| 110 | local dest="$TC/$name" stamp="$TC/$name.sha256" |
| 111 | if [ -d "$dest" ] && [ "$(cat "$stamp" 2>/dev/null || true)" = "$sha" ]; then |
| 112 | return 0 |
| 113 | fi |
| 114 | echo "desktop-toolchain: fetching $name" >&2 |
| 115 | local dl="$TC/.download.$name" |
| 116 | rm -rf "$dest" "$dest.tmp" "$dl" |
| 117 | mkdir -p "$dest.tmp" |
| 118 | curl -fsSL --retry 3 -o "$dl" "$url" |
| 119 | echo "$sha $dl" | sha256sum -c - >/dev/null |
| 120 | tar -xf "$dl" -C "$dest.tmp" --strip-components="$strip" |
| 121 | rm -f "$dl" |
| 122 | mv "$dest.tmp" "$dest" |
| 123 | echo "$sha" > "$stamp" |
| 124 | } |
| 125 | |
| 126 | # The same, for something that is one file rather than an archive. |
| 127 | install_file() { |
| 128 | local name=$1 url=$2 sha=$3 into=$4 |
| 129 | local dest="$TC/$name" stamp="$TC/$name.sha256" |
| 130 | if [ -d "$dest" ] && [ "$(cat "$stamp" 2>/dev/null || true)" = "$sha" ]; then |
| 131 | return 0 |
| 132 | fi |
| 133 | echo "desktop-toolchain: fetching $name" >&2 |
| 134 | rm -rf "$dest" "$dest.tmp" |
| 135 | mkdir -p "$dest.tmp/$(dirname "$into")" |
| 136 | curl -fsSL --retry 3 -o "$dest.tmp/$into" "$url" |
| 137 | echo "$sha $dest.tmp/$into" | sha256sum -c - >/dev/null |
| 138 | mv "$dest.tmp" "$dest" |
| 139 | echo "$sha" > "$stamp" |
| 140 | } |
| 141 | |
| 142 | # Source, by revision. `git -c advice.detachedHead=false` because this is |
| 143 | # always a detached checkout and the advice is four lines of it per fetch. |
| 144 | install_source() { |
| 145 | local name=$1 repo=$2 rev=$3 |
| 146 | local dest="$TC/$name" stamp="$TC/$name.rev" |
| 147 | if [ -d "$dest" ] && [ "$(cat "$stamp" 2>/dev/null || true)" = "$rev" ]; then |
| 148 | return 0 |
| 149 | fi |
| 150 | echo "desktop-toolchain: cloning $name at ${rev:0:8}" >&2 |
| 151 | rm -rf "$dest" "$dest.tmp" |
| 152 | # A rev is not a ref, so this is init-fetch rather than clone --branch: |
| 153 | # `git clone --depth 1` cannot take a sha unless the server allows it, and |
| 154 | # `fetch --depth 1 <sha>` is the form that works everywhere. |
| 155 | mkdir -p "$dest.tmp" |
| 156 | git -C "$dest.tmp" init -q |
| 157 | git -C "$dest.tmp" remote add origin "$repo" |
| 158 | git -C "$dest.tmp" fetch -q --depth 1 origin "$rev" |
| 159 | git -C "$dest.tmp" -c advice.detachedHead=false checkout -q FETCH_HEAD |
| 160 | mv "$dest.tmp" "$dest" |
| 161 | echo "$rev" > "$stamp" |
| 162 | } |
| 163 | |
| 164 | check_pins() { |
| 165 | if [ "$JOLT_NATIVE_SHA" = "0000000000000000000000000000000000000000000000000000000000000000" ]; then |
| 166 | cat >&2 <<MSG |
| 167 | desktop-toolchain: the jolt-native portable tarball is not pinned yet. |
| 168 | |
| 169 | It is published by the first pipeline to run on jolt-native's main at |
| 170 | ${JOLT_NATIVE_REV:0:8}. Once it exists: |
| 171 | |
| 172 | curl -fsSL "$JOLT_NATIVE_URL" | sha256sum |
| 173 | |
| 174 | and put that in JOLT_NATIVE_SHA here, or pass it for one run as |
| 175 | FRQ_JOLT_NATIVE_SHA=<sha256>. |
| 176 | MSG |
| 177 | exit 1 |
| 178 | fi |
| 179 | } |
| 180 | |
| 181 | install_all() { |
| 182 | require_host_tools |
| 183 | check_pins |
| 184 | mkdir -p "$TC" |
| 185 | # strip 1: the jolt tarball is rooted at a versioned directory. |
| 186 | install_archive jolt "$JOLT_URL" "$JOLT_SHA" 1 |
| 187 | # strip 0: the portable tarball is rooted at lib/ and include/ already, |
| 188 | # which is the shape a consumer is meant to take it in. |
| 189 | install_archive jolt-native "$JOLT_NATIVE_URL" "$JOLT_NATIVE_SHA" 0 |
| 190 | install_file moq-ffi "$MOQ_FFI_URL" "$MOQ_FFI_SHA" lib/libmoq_ffi.so |
| 191 | install_source glimmer "$GLIMMER_REPO" "$GLIMMER_REV" |
| 192 | install_source jolt-native-src "$JOLT_NATIVE_REPO" "$JOLT_NATIVE_REV" |
| 193 | } |
| 194 | |
| 195 | # The environment, as shell. |
| 196 | # |
| 197 | # JOLT_NATIVE_LIB is how jolt resolves every `:jolt/native` name, and it wants |
| 198 | # ONE directory — the objects come from two places (the portable tarball and |
| 199 | # the moq-ffi release), so `tools/build-desktop.sh` stages them into one and |
| 200 | # this names where that landed. |
| 201 | print_env() { |
| 202 | cat <<ENV |
| 203 | export FRQ_DESKTOP_TOOLCHAIN="$TC" |
| 204 | export JOLT_NATIVE_LIB="$TC/native" |
| 205 | export LD_LIBRARY_PATH="$TC/native\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}" |
| 206 | export FRQ_GLIMMER="$TC/glimmer" |
| 207 | export FRQ_GLIMMER_COSMIC="$TC/jolt-native-src/glimmer-backends/glimmer-cosmic" |
| 208 | export PATH="$TC/jolt:\$PATH" |
| 209 | ENV |
| 210 | } |
| 211 | |
| 212 | case "${1:-install}" in |
| 213 | install) install_all ;; |
| 214 | env) install_all; print_env ;; |
| 215 | exec) install_all; eval "$(print_env)"; shift; [ "${1:-}" = "--" ] && shift; exec "$@" ;; |
| 216 | *) echo "usage: desktop-toolchain.sh [install|env|exec -- cmd...]" >&2; exit 1 ;; |
| 217 | esac |