| A Mesa nobody needed, and the AppImage that carried it 9db383c nandi 21h ago | 1 | #!/usr/bin/env bash |
| 2 | # The cosmic desktop build: stage a runtime, its objects and the source next |
| 3 | # to each other, and write a launcher that starts them. |
| 4 | # |
| 5 | # There is no compilation here of anything written in Jolt — jolt reads |
| 6 | # deps.edn and the source at startup, which is what `nix build .#frq` was also |
| 7 | # doing behind a wrapper script. What that flake output added was a closure: |
| 8 | # a Mesa, a nixGL to put the host's driver in front of it, and a store path |
| 9 | # per dependency. `.#appimage` then squashed the lot into one file so a |
| 10 | # machine without nix could run it. |
| 11 | # |
| 12 | # This builds the same program without any of that. The pieces arrive pinned |
| 13 | # from `tools/desktop-toolchain.sh`, the one thing that IS compiled is a |
| 14 | # single .c file, and what comes out is a directory that runs from wherever |
| 15 | # it is unpacked. |
| 16 | # |
| 17 | # tools/build-desktop.sh build build/desktop |
| 18 | # tools/build-desktop.sh tar ...and tar it up beside itself |
| 19 | # tools/build-desktop.sh run ...and start it |
| 20 | # |
| 21 | # The container in `.modal/frq/` runs this same file, the way |
| 22 | # `.modal/flutter-web/` runs tools/build-web.sh. |
| 23 | set -euo pipefail |
| 24 | |
| 25 | root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| 26 | action="${1:-build}" |
| 27 | |
| 28 | case "$action" in build|tar|run) ;; *) |
| 29 | echo "usage: build-desktop.sh [build|tar|run]" >&2; exit 1 ;; |
| 30 | esac |
| 31 | |
| 32 | eval "$("$root/tools/desktop-toolchain.sh" env)" |
| 33 | |
| 34 | out="${FRQ_DESKTOP_OUT:-$root/build/desktop}" |
| 35 | rm -rf "$out" |
| 36 | mkdir -p "$out/bin" "$out/lib" "$out/src" |
| 37 | |
| 38 | # The runtime. |
| 39 | install -m 0755 "$FRQ_DESKTOP_TOOLCHAIN/jolt/jolt" "$out/bin/jolt" |
| 40 | |
| 41 | # Every object in one directory, because JOLT_NATIVE_LIB is one directory: |
| 42 | # jolt resolves each `:jolt/native` name against it. They arrive from two |
| 43 | # places — the portable tarball and the moq-ffi release — which is exactly |
| 44 | # what `nativeAll` was a symlinkJoin for. |
| 45 | cp -a "$FRQ_DESKTOP_TOOLCHAIN/jolt-native/lib/." "$out/lib/" |
| 46 | install -m 0755 "$FRQ_DESKTOP_TOOLCHAIN/moq-ffi/lib/libmoq_ffi.so" "$out/lib/" |
| 47 | |
| 48 | # The calling-convention adapter, compiled here because it is one translation |
| 49 | # unit and because openh264's C API cannot be called from Jolt directly: |
| 50 | # `ISVCEncoder` is a `const ISVCEncoderVtbl*`, so `c/frq_h264.c` walks the |
| 51 | # vtable and exports five plain symbols. See src/frq/codec/h264.clj. |
| 52 | # |
| 53 | # This is the one place the build machine's own libraries get in, and the |
| 54 | # reason it is acceptable is the reason the portable tarball works at all: |
| 55 | # openh264, opus and alsa-lib are plain C libraries against glibc, and the |
| 56 | # copy below takes the .so the link actually resolved rather than trusting the |
| 57 | # runner to have the same one. |
| 58 | echo "desktop: compiling the h264 adapter" >&2 |
| 59 | cc -O2 -fPIC -shared "$root/c/frq_h264.c" -o "$out/lib/libfrqh264.so" \ |
| 60 | $(pkg-config --cflags --libs openh264) |
| 61 | |
| 62 | # openh264 itself, opus, and ALSA's client library, beside it. `ldd` on what |
| 63 | # was just linked names the file the loader chose, which is the one to take — |
| 64 | # a guess at a soname is a guess at the distro. |
| 65 | for soname in libopenh264 libopus libasound; do |
| 66 | lib=$(ldd "$out/lib/libfrqh264.so" 2>/dev/null | awk -v n="$soname" '$1 ~ "^"n"\\." {print $3; exit}') |
| 67 | # opus and alsa are opened by jolt rather than NEEDED by the adapter, so |
| 68 | # they are not in that ldd and have to be looked up. |
| 69 | [ -n "${lib:-}" ] || lib=$(ldconfig -p | awk -v n="$soname" '$1 ~ "^"n"\\.so" {print $NF; exit}') |
| 70 | if [ -z "${lib:-}" ]; then |
| 71 | echo "desktop: no $soname on this machine — the AV plane will not load" >&2 |
| 72 | continue |
| 73 | fi |
| 74 | install -m 0755 -T "$lib" "$out/lib/$(basename "$lib")" |
| 75 | done |
| 76 | |
| 77 | # $ORIGIN for everything staged here, for the reason jolt-native's |
| 78 | # libsPortable sets it: the consumer decides where this unpacks and only the |
| 79 | # loader knows where that turned out to be. The objects out of the portable |
| 80 | # tarball already have it; the ones added above do not. |
| 81 | if command -v patchelf >/dev/null 2>&1; then |
| 82 | for f in "$out/lib"/*.so*; do patchelf --set-rpath '$ORIGIN' "$f" 2>/dev/null || true; done |
| 83 | else |
| 84 | echo "desktop: no patchelf; the launcher's LD_LIBRARY_PATH covers this" >&2 |
| 85 | fi |
| 86 | |
| 87 | # The project as jolt sees it: source, deps.edn, nothing else — the same three |
| 88 | # things `frqSource` copied in the flake. |
| 89 | cp -a "$root/common" "$root/src" "$root/deps.edn" "$out/src/" |
| 90 | |
| 91 | # The two Jolt libraries the launcher names in -Sdeps. Copied in rather than |
| 92 | # referenced out of the toolchain, so the bundle is self-contained: a tarball |
| 93 | # that needs a directory from the machine that made it is not a bundle. |
| 94 | cp -a "$FRQ_GLIMMER" "$out/glimmer" |
| 95 | cp -a "$FRQ_GLIMMER_COSMIC" "$out/glimmer-cosmic" |
| 96 | rm -rf "$out/glimmer/.git" "$out/glimmer-cosmic/.git" |
| 97 | |
| 98 | # The launcher. `frqScript` from the flake, with the store paths replaced by |
| 99 | # $ORIGIN-relative ones and the nixGL branch deleted — off NixOS that existed |
| 100 | # to put the host's GL driver ahead of the closure's Mesa, and there is no |
| 101 | # Mesa in here to get ahead of. The driver is simply the host's. |
| 102 | cat > "$out/bin/frq" <<'LAUNCH' |
| 103 | #!/usr/bin/env bash |
| 104 | set -euo pipefail |
| 105 | here="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)" |
| 106 | |
| 107 | export JOLT_NATIVE_LIB="$here/lib" |
| 108 | export LD_LIBRARY_PATH="$here/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" |
| 109 | |
| 110 | # ALSA finds its plugins by directory rather than by soname, and `default` |
| 111 | # resolves to nothing without the PipeWire one. Unlike the flake, which named |
| 112 | # a store path, this defers to the host: every machine frq targets runs |
| 113 | # PipeWire and ships that plugin in the usual place. |
| 114 | for d in /usr/lib/x86_64-linux-gnu/alsa-lib /usr/lib/alsa-lib /usr/lib64/alsa-lib; do |
| 115 | [ -d "$d" ] && export ALSA_PLUGIN_DIR="$d" && break |
| 116 | done |
| 117 | |
| 118 | # jolt resolves deps.edn from the working directory. |
| 119 | cd "$here/src" |
| 120 | |
| 121 | exec "$here/bin/jolt" \ |
| 122 | -Sdeps "{:deps {jolt-lang/glimmer {:local/root \"$here/glimmer\"} |
| 123 | nandi/glimmer-cosmic {:local/root \"$here/glimmer-cosmic\"}}}" \ |
| 124 | -m frq.cosmic "$@" |
| 125 | LAUNCH |
| 126 | chmod +x "$out/bin/frq" |
| 127 | |
| 128 | echo "desktop: built $out" >&2 |
| 129 | du -sh "$out" >&2 |
| 130 | |
| 131 | case "$action" in |
| 132 | tar) |
| 133 | tarball="${FRQ_DESKTOP_TAR:-$root/build/frq-desktop-x86_64-linux.tar.gz}" |
| 134 | mkdir -p "$(dirname "$tarball")" |
| 135 | # Rooted at a directory of its own, because a tarball that unpacks |
| 136 | # `bin/` and `lib/` into the current directory is a tarball someone |
| 137 | # will one day unpack into their home. |
| 138 | tar czf "$tarball" -C "$(dirname "$out")" "$(basename "$out")" |
| 139 | echo "desktop: $tarball" >&2 |
| 140 | ls -la "$tarball" >&2 |
| 141 | ;; |
| 142 | run) |
| 143 | exec "$out/bin/frq" |
| 144 | ;; |
| 145 | esac |