nandi/frqpublic Fork 0
a32699e
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

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

Run this tree on a native half the builders made

`just run` fetched the release the dotslash pins name, which answers the
wrong question: a change to jolt-native is by definition not in a release
yet, so the one run that is meant to show it working was the one run that
could not. Everything under the frq source now comes from the flake and is
built — jolt, glimmer, glimmer-vidya and both objects, at the revs
flake.lock names, so a run says what it ran against and both halves of
glimmer-vidya move together.

The shell is where that is said, and entering it is the build. run.bb
re-execs itself inside `nix develop` and asks for it with --max-jobs 0:
left to the default nix prefers the local machine, and a cold jolt-native
is egui and openh264 on a laptop for a derivation nixbuild has likely
built already.

Which is also why this is the cargo build rather than jolt-native's own
buck2 graph, and that is a compromise: buck2 is what its CI runs and its
cpal has the pipewire feature this one does not, so device names in a call
come out as ALSA PCMs. But buck2 fetches its toolchain and every crate as
it goes and writes buck-out into the tree it builds, and a sandbox with no
network and a read-only store is the one place it cannot run. On a remote
builder it is not the slower option, it is not an option.

`just lib` and the pins are untouched — the APK and the buck2 build still
take the released bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-01T22:39:37-07:00 Browse files
a32699e parent: 4a372ae
modified flake.nix +79 -19
@@ -86,16 +86,39 @@
8686 systems = [ "x86_64-linux" "aarch64-linux" ];
8787 forEachSystem = f:
8888 nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
89+
90+ # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA
91+ # wrappers are the ones that need --impure (they read the host kernel
92+ # module's version), which is why this only ever reaches for Intel.
93+ nixGLFor = pkgs: nixgl.packages.${pkgs.stdenv.hostPlatform.system}.nixGLIntel;
94+
95+ # egui reaches for these with dlopen rather than linking them, so being
96+ # in the cdylib's buildInputs is not enough — whatever starts frq has to
97+ # put them on the loader path itself. Without libX11 here, vidya reports
98+ # "X11 unavailable", falls back to Wayland, and winit refuses to build a
99+ # second event loop after the failed first one.
100+ #
101+ # Out here rather than beside the package that first needed them: the
102+ # dev shell starts frq too, on this tree's source rather than the store's
103+ # copy of it, and a second copy of this list is a second chance for the
104+ # two ways of running to disagree about what the window needs.
105+ runtimeLibsFor = pkgs: with pkgs; [
106+ libGL
107+ libxkbcommon
108+ wayland
109+ xorg.libX11
110+ xorg.libXcursor
111+ xorg.libXi
112+ xorg.libXrandr
113+ vulkan-loader
114+ ];
89115 in
90116 {
91117 packages = forEachSystem (pkgs:
92118 let
93119 inherit (pkgs) lib;
94120
95- # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA
96- # wrappers are the ones that need --impure (they read the host kernel
97- # module's version), which is why this only ever reaches for Intel.
98- nixGL = nixgl.packages.${pkgs.stdenv.hostPlatform.system}.nixGLIntel;
121+ nixGL = nixGLFor pkgs;
99122
100123 # libvidya (the retained-tree ABI glimmer-vidya binds, on egui) and
101124 # libjoltmoq (the AV media plane). One workspace, two cdylibs.
@@ -202,21 +225,7 @@
202225 # answers for both.
203226 glimmerVidya = "${jolt-native}/jolt/glimmer-vidya";
204227
205- # egui reaches for these with dlopen rather than linking them, so
206- # being in the cdylib's buildInputs is not enough — the launcher has
207- # to put them on the loader path itself. Without libX11 here, vidya
208- # reports "X11 unavailable", falls back to Wayland, and winit refuses
209- # to build a second event loop after the failed first one.
210- runtimeLibs = with pkgs; [
211- libGL
212- libxkbcommon
213- wayland
214- xorg.libX11
215- xorg.libXcursor
216- xorg.libXi
217- xorg.libXrandr
218- vulkan-loader
219- ];
228+ runtimeLibs = runtimeLibsFor pkgs;
220229
221230 # The project as jolt sees it: source, deps.edn, nothing else.
222231 frqSource = pkgs.runCommand "frq-source" { } ''
@@ -304,6 +313,57 @@
304313 apk-unsigned = android.apk-unsigned;
305314 });
306315
316+ # Where `just run` runs, and because entering it realises what it
317+ # names what builds the half of frq that is not this working tree.
318+ #
319+ # The two halves, and the split is the whole point of the shell. The frq
320+ # source is the files on disk, uncommitted edits and all. Everything
321+ # under it jolt, glimmer, glimmer-vidya, both native objects is the
322+ # flake's, at the revs flake.lock names, so a run says what it ran
323+ # against and both halves of glimmer-vidya move together. That is the
324+ # drift the `jolt-native` input's comment is about, and a pin frq can
325+ # answer for is worth more here than the convenience of a checkout.
326+ #
327+ # `native` is the cargo build of that input rather than jolt-native's own
328+ # buck2 graph, which is a compromise and not a free one: buck2 is what
329+ # its CI runs and what makes its releases, and its cpal has the pipewire
330+ # feature this one does not, so device *names* in a call come out as ALSA
331+ # PCMs. What it buys is a derivation one thing nixbuild.net can be
332+ # handed. The buck2 build fetches its rustc, zig and every third-party
333+ # crate as it goes and writes buck-out into the tree it builds; a sandbox
334+ # with no network and a read-only store is the one place it cannot run,
335+ # so on a remote builder it is not a slower option but no option at all.
336+ #
337+ # Nothing here says "nixbuild", though: it is a plain derivation, and
338+ # where it gets built is the machine's business. scripts/run.bb asks for
339+ # the shell with --max-jobs 0, which is what sends it to the `builders`
340+ # entry rather than compiling egui on a laptop.
341+ devShells = forEachSystem (pkgs:
342+ let
343+ inherit (pkgs) lib;
344+ inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) jolt native;
345+ in
346+ {
347+ default = pkgs.mkShellNoCC {
348+ name = "frq";
349+
350+ # jolt, because the runtime frq is run by should be the flake's
351+ # too. nixGL for the same reason the launcher reaches for it see
352+ # frqScript.
353+ packages = [ jolt (nixGLFor pkgs) ];
354+
355+ # Read by scripts/run.bb rather than baked into a wrapper: the frq
356+ # source `just run` runs is the working tree, so the launcher has
357+ # to be a script in that tree and the shell has to hand it its
358+ # answers. Naming these is also what makes the shell build them.
359+ JOLT_NATIVE_LIB = "${native}/lib";
360+ GLIMMER_SRC = glimmer;
361+ GLIMMER_VIDYA_SRC = "${jolt-native}/jolt/glimmer-vidya";
362+ FRQ_LIB_PATH = lib.makeLibraryPath (runtimeLibsFor pkgs);
363+ NIXGL = "${nixGLFor pkgs}/bin/nixGLIntel";
364+ };
365+ });
366+
307367 apps = forEachSystem (pkgs: {
308368 default = {
309369 type = "app";
@@ -86,16 +86,39 @@
86 systems = [ "x86_64-linux" "aarch64-linux" ];86 systems = [ "x86_64-linux" "aarch64-linux" ];
87 forEachSystem = f:87 forEachSystem = f:
88 nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});88 nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
89+
90+ # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA
91+ # wrappers are the ones that need --impure (they read the host kernel
92+ # module's version), which is why this only ever reaches for Intel.
93+ nixGLFor = pkgs: nixgl.packages.${pkgs.stdenv.hostPlatform.system}.nixGLIntel;
94+
95+ # egui reaches for these with dlopen rather than linking them, so being
96+ # in the cdylib's buildInputs is not enough — whatever starts frq has to
97+ # put them on the loader path itself. Without libX11 here, vidya reports
98+ # "X11 unavailable", falls back to Wayland, and winit refuses to build a
99+ # second event loop after the failed first one.
100+ #
101+ # Out here rather than beside the package that first needed them: the
102+ # dev shell starts frq too, on this tree's source rather than the store's
103+ # copy of it, and a second copy of this list is a second chance for the
104+ # two ways of running to disagree about what the window needs.
105+ runtimeLibsFor = pkgs: with pkgs; [
106+ libGL
107+ libxkbcommon
108+ wayland
109+ xorg.libX11
110+ xorg.libXcursor
111+ xorg.libXi
112+ xorg.libXrandr
113+ vulkan-loader
114+ ];
89 in115 in
90 {116 {
91 packages = forEachSystem (pkgs:117 packages = forEachSystem (pkgs:
92 let118 let
93 inherit (pkgs) lib;119 inherit (pkgs) lib;
94 120
95- # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA121+ nixGL = nixGLFor pkgs;
96- # wrappers are the ones that need --impure (they read the host kernel
97- # module's version), which is why this only ever reaches for Intel.
98- nixGL = nixgl.packages.${pkgs.stdenv.hostPlatform.system}.nixGLIntel;
99 122
100 # libvidya (the retained-tree ABI glimmer-vidya binds, on egui) and123 # libvidya (the retained-tree ABI glimmer-vidya binds, on egui) and
101 # libjoltmoq (the AV media plane). One workspace, two cdylibs.124 # libjoltmoq (the AV media plane). One workspace, two cdylibs.
@@ -202,21 +225,7 @@
202 # answers for both.225 # answers for both.
203 glimmerVidya = "${jolt-native}/jolt/glimmer-vidya";226 glimmerVidya = "${jolt-native}/jolt/glimmer-vidya";
204 227
205- # egui reaches for these with dlopen rather than linking them, so228+ runtimeLibs = runtimeLibsFor pkgs;
206- # being in the cdylib's buildInputs is not enough — the launcher has
207- # to put them on the loader path itself. Without libX11 here, vidya
208- # reports "X11 unavailable", falls back to Wayland, and winit refuses
209- # to build a second event loop after the failed first one.
210- runtimeLibs = with pkgs; [
211- libGL
212- libxkbcommon
213- wayland
214- xorg.libX11
215- xorg.libXcursor
216- xorg.libXi
217- xorg.libXrandr
218- vulkan-loader
219- ];
220 229
221 # The project as jolt sees it: source, deps.edn, nothing else.230 # The project as jolt sees it: source, deps.edn, nothing else.
222 frqSource = pkgs.runCommand "frq-source" { } ''231 frqSource = pkgs.runCommand "frq-source" { } ''
@@ -304,6 +313,57 @@
304 apk-unsigned = android.apk-unsigned;313 apk-unsigned = android.apk-unsigned;
305 });314 });
306 315
316+ # Where `just run` runs, and because entering it realises what it
317+ # names what builds the half of frq that is not this working tree.
318+ #
319+ # The two halves, and the split is the whole point of the shell. The frq
320+ # source is the files on disk, uncommitted edits and all. Everything
321+ # under it jolt, glimmer, glimmer-vidya, both native objects is the
322+ # flake's, at the revs flake.lock names, so a run says what it ran
323+ # against and both halves of glimmer-vidya move together. That is the
324+ # drift the `jolt-native` input's comment is about, and a pin frq can
325+ # answer for is worth more here than the convenience of a checkout.
326+ #
327+ # `native` is the cargo build of that input rather than jolt-native's own
328+ # buck2 graph, which is a compromise and not a free one: buck2 is what
329+ # its CI runs and what makes its releases, and its cpal has the pipewire
330+ # feature this one does not, so device *names* in a call come out as ALSA
331+ # PCMs. What it buys is a derivation one thing nixbuild.net can be
332+ # handed. The buck2 build fetches its rustc, zig and every third-party
333+ # crate as it goes and writes buck-out into the tree it builds; a sandbox
334+ # with no network and a read-only store is the one place it cannot run,
335+ # so on a remote builder it is not a slower option but no option at all.
336+ #
337+ # Nothing here says "nixbuild", though: it is a plain derivation, and
338+ # where it gets built is the machine's business. scripts/run.bb asks for
339+ # the shell with --max-jobs 0, which is what sends it to the `builders`
340+ # entry rather than compiling egui on a laptop.
341+ devShells = forEachSystem (pkgs:
342+ let
343+ inherit (pkgs) lib;
344+ inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) jolt native;
345+ in
346+ {
347+ default = pkgs.mkShellNoCC {
348+ name = "frq";
349+
350+ # jolt, because the runtime frq is run by should be the flake's
351+ # too. nixGL for the same reason the launcher reaches for it see
352+ # frqScript.
353+ packages = [ jolt (nixGLFor pkgs) ];
354+
355+ # Read by scripts/run.bb rather than baked into a wrapper: the frq
356+ # source `just run` runs is the working tree, so the launcher has
357+ # to be a script in that tree and the shell has to hand it its
358+ # answers. Naming these is also what makes the shell build them.
359+ JOLT_NATIVE_LIB = "${native}/lib";
360+ GLIMMER_SRC = glimmer;
361+ GLIMMER_VIDYA_SRC = "${jolt-native}/jolt/glimmer-vidya";
362+ FRQ_LIB_PATH = lib.makeLibraryPath (runtimeLibsFor pkgs);
363+ NIXGL = "${nixGLFor pkgs}/bin/nixGLIntel";
364+ };
365+ });
366+
307 apps = forEachSystem (pkgs: {367 apps = forEachSystem (pkgs: {
308 default = {368 default = {
309 type = "app";369 type = "app";
modified justfile +2 -2
@@ -8,7 +8,7 @@ set shell := ["bash", "-euo", "pipefail", "-c"]
88 default:
99 @just --list
1010
11-# Both native libraries, cloning jolt-native at the pinned commit if needed.
11+# Both native libraries, out of the release pins. `run` builds them instead.
1212 lib:
1313 scripts/lib.bb
1414
@@ -28,6 +28,6 @@ bump tag="":
2828 sync-dist:
2929 scripts/dotslash-to-buck
3030
31-# The app.
31+# The app: this tree's source on the flake's everything-else, in the dev shell.
3232 run *args:
3333 scripts/run.bb {{args}}
@@ -8,7 +8,7 @@ set shell := ["bash", "-euo", "pipefail", "-c"]
8 default:8 default:
9 @just --list9 @just --list
10 10
11-# Both native libraries, cloning jolt-native at the pinned commit if needed.11+# Both native libraries, out of the release pins. `run` builds them instead.
12 lib:12 lib:
13 scripts/lib.bb13 scripts/lib.bb
14 14
@@ -28,6 +28,6 @@ bump tag="":
28 sync-dist:28 sync-dist:
29 scripts/dotslash-to-buck29 scripts/dotslash-to-buck
30 30
31-# The app.31+# The app: this tree's source on the flake's everything-else, in the dev shell.
32 run *args:32 run *args:
33 scripts/run.bb {{args}}33 scripts/run.bb {{args}}
modified scripts/lib.bb +4 -2
@@ -7,8 +7,10 @@ exec "$(dirname "$0")/bb" "$0" "$@"
77 ;;
88 ;; libvidya is the tree ABI glimmer-vidya binds; libjoltmoq is the AV media
99 ;; plane. They come out of one archive because they are built together, and
10-;; they land in build/lib because a loader wants one directory, which is where
11-;; run.bb points LD_LIBRARY_PATH.
10+;; they land in build/lib because a loader wants one directory. `just run` does
11+;; not come through here — running a change to jolt-native means building it,
12+;; which is what the dev shell is for — so this is the released half: what the
13+;; APK and the buck2 build take, and what a run of the last release takes.
1214 ;;
1315 ;; Nothing is compiled here. The pinned bytes are the bytes: this repo used to
1416 ;; clone jolt-native and cargo-build it, which meant a Rust toolchain, a build
@@ -7,8 +7,10 @@ exec "$(dirname "$0")/bb" "$0" "$@"
7 ;;7 ;;
8 ;; libvidya is the tree ABI glimmer-vidya binds; libjoltmoq is the AV media8 ;; libvidya is the tree ABI glimmer-vidya binds; libjoltmoq is the AV media
9 ;; plane. They come out of one archive because they are built together, and9 ;; plane. They come out of one archive because they are built together, and
10-;; they land in build/lib because a loader wants one directory, which is where10+;; they land in build/lib because a loader wants one directory. `just run` does
11-;; run.bb points LD_LIBRARY_PATH.11+;; not come through here — running a change to jolt-native means building it,
12+;; which is what the dev shell is for — so this is the released half: what the
13+;; APK and the buck2 build take, and what a run of the last release takes.
12 ;;14 ;;
13 ;; Nothing is compiled here. The pinned bytes are the bytes: this repo used to15 ;; Nothing is compiled here. The pinned bytes are the bytes: this repo used to
14 ;; clone jolt-native and cargo-build it, which meant a Rust toolchain, a build16 ;; clone jolt-native and cargo-build it, which meant a Rust toolchain, a build
modified scripts/run.bb +63 -18
@@ -7,37 +7,82 @@ exec "$(dirname "$0")/bb" "$0" "$@"
77 ;;
88 ;; run.bb [args...]
99 ;;
10-;; Everything but the source is pinned: jolt comes from scripts/jolt.dotslash
11-;; and the two native libraries from scripts/lib*.dotslash, fetched by digest
12-;; and linked into build/lib, which is the one directory the loader is pointed
13-;; at. `just lib` is done here rather than asked for — the pins say what those
14-;; bytes are, so there is nothing for a person to decide before running.
10+;; Two halves, and the split is the point. The frq source is the files on disk,
11+;; uncommitted edits and all. Everything under it — jolt, glimmer,
12+;; glimmer-vidya, both native objects — is the flake's, built rather than
13+;; fetched: this re-execs itself inside `nix develop`, and that shell is the
14+;; jolt-native input compiled and the Jolt halves that bind it, at the revs
15+;; flake.lock names.
1516 ;;
1617 ;; Deliberately not `nix run .#frq`. That builds the flake's own copy of the
1718 ;; source, which is the tree as git has it — so an edit that has not been
1819 ;; committed, or has been committed on a branch the command was not pointed at,
1920 ;; runs as whatever was there before, silently. A run that is meant to answer
2021 ;; "does my change work" has to be the files on disk.
21-(require '[babashka.fs :as fs]
22- '[babashka.process :as p])
22+;;
23+;; And deliberately not the scripts/*.dotslash pins either, which is what this
24+;; used to do. Those name a release; a change to jolt-native is by definition
25+;; not in one yet. `just lib` still fetches them — the APK and the buck2 build
26+;; take the released bytes — but a run does not.
27+(require '[babashka.classpath :as cp])
28+(cp/add-classpath (str (babashka.fs/parent *file*)))
29+(require '[frq.paths :as paths]
30+ '[babashka.fs :as fs]
31+ '[babashka.process :as p]
32+ '[clojure.string :as str])
2333
2434 (def root (str (fs/canonicalize (fs/path (fs/parent *file*) ".."))))
25-(def lib (str (fs/path root "build" "lib")))
35+(def self (str (fs/path root "scripts" "run.bb")))
36+
37+;; Outside the shell, get inside it and come back to this same script. The
38+;; shell is what sets JOLT_NATIVE_LIB, so its absence is the test — no flag to
39+;; forget, and `nix develop --command scripts/run.bb` by hand is not a second
40+;; code path.
41+;;
42+;; --max-jobs 0 is what sends the work to the `builders` entry rather than
43+;; compiling it here. Left to the default, nix prefers the local machine, and a
44+;; cold jolt-native is egui, openh264 and quinn on a laptop — for a derivation
45+;; a remote builder has likely built already. FRQ_MAX_JOBS is the way out on a
46+;; machine with no builder configured: FRQ_MAX_JOBS=auto.
47+(when-not (System/getenv "JOLT_NATIVE_LIB")
48+ (let [args ["nix" "develop" root "--max-jobs" (paths/env "FRQ_MAX_JOBS" "0")
49+ "--command" self]
50+ ;; nix is not on every host this runs on: on the machine this was
51+ ;; written for it lives in an Arch distrobox, at the same path — which
52+ ;; is why the container is entered rather than the tree copied into it.
53+ ;; See CLAUDE.md.
54+ cmd (if (fs/which "nix")
55+ (concat args *command-line-args*)
56+ (concat ["distrobox" "enter" "arch" "--" "bash" "-lc"
57+ (str "cd " root " && exec " (str/join " " args) " \"$@\"")
58+ "--"]
59+ *command-line-args*))]
60+ (System/exit (:exit @(apply p/process {:inherit true :dir root} cmd)))))
2661
27-;; The pinned jolt — scripts/jolt is a DotSlash script, so running it is
28-;; fetching it. Off linux-x86_64 the pin has no asset and the fallback is
29-;; whatever `jolt` is on PATH, which is what that manifest says to do.
30-(def jolt
31- (let [pin (fs/path root "scripts" "jolt")]
32- (if (and (fs/exists? pin) (fs/which "dotslash")) (str pin) "jolt")))
62+;; The Jolt halves that have to match those objects. glimmer-vidya lives inside
63+;; jolt-native and binds libvidya's ABI, so it comes out of the same input that
64+;; was built rather than deps.edn's git sha — the pin drifting from the library
65+;; is exactly what the flake input's comment describes. glimmer is the flake's
66+;; for the same reason.
67+(def overrides
68+ (str "{:deps {jolt-lang/glimmer {:local/root \"" (System/getenv "GLIMMER_SRC") "\"}"
69+ " nandi/glimmer-vidya {:local/root \"" (System/getenv "GLIMMER_VIDYA_SRC") "\"}}}"))
3370
34-(p/shell (str (fs/path root "scripts" "lib.bb")))
71+;; On NixOS the store's Mesa is the system's and the window opens. Anywhere
72+;; else — a bare host, or the distrobox above — the real driver is the host's,
73+;; so defer to nixGL, which prepends it. Same rule the flake's launcher uses.
74+(def runner
75+ (when-not (fs/exists? "/run/current-system")
76+ (some-> (System/getenv "NIXGL") not-empty vector)))
3577
3678 (System/exit
3779 (:exit @(apply p/process
3880 {:inherit true
3981 :dir root
4082 :extra-env {"LD_LIBRARY_PATH"
41- (str lib (when-let [p (System/getenv "LD_LIBRARY_PATH")]
42- (str ":" p)))}}
43- jolt "-M:frq" *command-line-args*)))
83+ (->> [(System/getenv "JOLT_NATIVE_LIB")
84+ (System/getenv "FRQ_LIB_PATH")
85+ (System/getenv "LD_LIBRARY_PATH")]
86+ (remove str/blank?)
87+ (str/join ":"))}}
88+ (concat runner ["jolt" "-Sdeps" overrides "-M:frq"] *command-line-args*))))
@@ -7,37 +7,82 @@ exec "$(dirname "$0")/bb" "$0" "$@"
7 ;;7 ;;
8 ;; run.bb [args...]8 ;; run.bb [args...]
9 ;;9 ;;
10-;; Everything but the source is pinned: jolt comes from scripts/jolt.dotslash10+;; Two halves, and the split is the point. The frq source is the files on disk,
11-;; and the two native libraries from scripts/lib*.dotslash, fetched by digest11+;; uncommitted edits and all. Everything under it — jolt, glimmer,
12-;; and linked into build/lib, which is the one directory the loader is pointed12+;; glimmer-vidya, both native objects — is the flake's, built rather than
13-;; at. `just lib` is done here rather than asked for — the pins say what those13+;; fetched: this re-execs itself inside `nix develop`, and that shell is the
14-;; bytes are, so there is nothing for a person to decide before running.14+;; jolt-native input compiled and the Jolt halves that bind it, at the revs
15+;; flake.lock names.
15 ;;16 ;;
16 ;; Deliberately not `nix run .#frq`. That builds the flake's own copy of the17 ;; Deliberately not `nix run .#frq`. That builds the flake's own copy of the
17 ;; source, which is the tree as git has it — so an edit that has not been18 ;; source, which is the tree as git has it — so an edit that has not been
18 ;; committed, or has been committed on a branch the command was not pointed at,19 ;; committed, or has been committed on a branch the command was not pointed at,
19 ;; runs as whatever was there before, silently. A run that is meant to answer20 ;; runs as whatever was there before, silently. A run that is meant to answer
20 ;; "does my change work" has to be the files on disk.21 ;; "does my change work" has to be the files on disk.
21-(require '[babashka.fs :as fs]22+;;
22- '[babashka.process :as p])23+;; And deliberately not the scripts/*.dotslash pins either, which is what this
24+;; used to do. Those name a release; a change to jolt-native is by definition
25+;; not in one yet. `just lib` still fetches them — the APK and the buck2 build
26+;; take the released bytes — but a run does not.
27+(require '[babashka.classpath :as cp])
28+(cp/add-classpath (str (babashka.fs/parent *file*)))
29+(require '[frq.paths :as paths]
30+ '[babashka.fs :as fs]
31+ '[babashka.process :as p]
32+ '[clojure.string :as str])
23 33
24 (def root (str (fs/canonicalize (fs/path (fs/parent *file*) ".."))))34 (def root (str (fs/canonicalize (fs/path (fs/parent *file*) ".."))))
25-(def lib (str (fs/path root "build" "lib")))35+(def self (str (fs/path root "scripts" "run.bb")))
36+
37+;; Outside the shell, get inside it and come back to this same script. The
38+;; shell is what sets JOLT_NATIVE_LIB, so its absence is the test — no flag to
39+;; forget, and `nix develop --command scripts/run.bb` by hand is not a second
40+;; code path.
41+;;
42+;; --max-jobs 0 is what sends the work to the `builders` entry rather than
43+;; compiling it here. Left to the default, nix prefers the local machine, and a
44+;; cold jolt-native is egui, openh264 and quinn on a laptop — for a derivation
45+;; a remote builder has likely built already. FRQ_MAX_JOBS is the way out on a
46+;; machine with no builder configured: FRQ_MAX_JOBS=auto.
47+(when-not (System/getenv "JOLT_NATIVE_LIB")
48+ (let [args ["nix" "develop" root "--max-jobs" (paths/env "FRQ_MAX_JOBS" "0")
49+ "--command" self]
50+ ;; nix is not on every host this runs on: on the machine this was
51+ ;; written for it lives in an Arch distrobox, at the same path — which
52+ ;; is why the container is entered rather than the tree copied into it.
53+ ;; See CLAUDE.md.
54+ cmd (if (fs/which "nix")
55+ (concat args *command-line-args*)
56+ (concat ["distrobox" "enter" "arch" "--" "bash" "-lc"
57+ (str "cd " root " && exec " (str/join " " args) " \"$@\"")
58+ "--"]
59+ *command-line-args*))]
60+ (System/exit (:exit @(apply p/process {:inherit true :dir root} cmd)))))
26 61
27-;; The pinned jolt — scripts/jolt is a DotSlash script, so running it is62+;; The Jolt halves that have to match those objects. glimmer-vidya lives inside
28-;; fetching it. Off linux-x86_64 the pin has no asset and the fallback is63+;; jolt-native and binds libvidya's ABI, so it comes out of the same input that
29-;; whatever `jolt` is on PATH, which is what that manifest says to do.64+;; was built rather than deps.edn's git sha — the pin drifting from the library
30-(def jolt65+;; is exactly what the flake input's comment describes. glimmer is the flake's
31- (let [pin (fs/path root "scripts" "jolt")]66+;; for the same reason.
32- (if (and (fs/exists? pin) (fs/which "dotslash")) (str pin) "jolt")))67+(def overrides
68+ (str "{:deps {jolt-lang/glimmer {:local/root \"" (System/getenv "GLIMMER_SRC") "\"}"
69+ " nandi/glimmer-vidya {:local/root \"" (System/getenv "GLIMMER_VIDYA_SRC") "\"}}}"))
33 70
34-(p/shell (str (fs/path root "scripts" "lib.bb")))71+;; On NixOS the store's Mesa is the system's and the window opens. Anywhere
72+;; else — a bare host, or the distrobox above — the real driver is the host's,
73+;; so defer to nixGL, which prepends it. Same rule the flake's launcher uses.
74+(def runner
75+ (when-not (fs/exists? "/run/current-system")
76+ (some-> (System/getenv "NIXGL") not-empty vector)))
35 77
36 (System/exit78 (System/exit
37 (:exit @(apply p/process79 (:exit @(apply p/process
38 {:inherit true80 {:inherit true
39 :dir root81 :dir root
40 :extra-env {"LD_LIBRARY_PATH"82 :extra-env {"LD_LIBRARY_PATH"
41- (str lib (when-let [p (System/getenv "LD_LIBRARY_PATH")]83+ (->> [(System/getenv "JOLT_NATIVE_LIB")
42- (str ":" p)))}}84+ (System/getenv "FRQ_LIB_PATH")
43- jolt "-M:frq" *command-line-args*)))85+ (System/getenv "LD_LIBRARY_PATH")]
86+ (remove str/blank?)
87+ (str/join ":"))}}
88+ (concat runner ["jolt" "-Sdeps" overrides "-M:frq"] *command-line-args*))))