nandi/frqpublic Fork 0
b4172e953d70d5164f0837f6a88b0b92448c32f6
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.

flake.nix · 1058 lines · 54.6 KBNix Blame HistoryRaw
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago1{
2 # frq is Jolt source, so "building" it is three things, not one:
3 #
4 # jolt the runtime that reads it (github:jolt-lang/jolt)
5 # jolt-native libvidya and libjoltmoq, in Rust (gitlab:nandithebull/jolt-native)
6 # frq this tree, with its deps resolved to store paths
7 #
8 # Jolt resolves deps.edn by running git at startup, which a build sandbox has
9 # no network for — so every dep is fetched by Nix instead and handed back as
10 # a :local/root through -Sdeps.
11 #
12 # nix build .#frq && ./result/bin/frq
13 #
14 # On a machine that is not NixOS the GL driver is the host's and the loader
15 # will not find it, so the window never opens ("GL display: argument does not
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago16 # name a valid config"). The launcher handles that itself: off NixOS it hands
17 # the process to nixGL, which puts the host's driver ahead of the store's.
18 # Nothing extra to type, and a distrobox/container Arch is the same case as
19 # a bare one.
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago20 description = "frq a freeq client in jolt";
21
22 inputs = {
23 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago24 # `git+https` with `?submodules=1` rather than the github scheme: Jolt's
25 # own flake declares `self.submodules`, which this Nix rejects when the
26 # flake is fetched as `github:`. Its outputs are not what we take — the
27 # runtime is built here, by joltFrom — but it is a flake all the same, so
28 # its own inputs are locked with ours rather than left to float, and
29 # `vendor/` comes along as the submodule the build needs.
30 #
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago31 # The fork rather than jolt-lang/jolt, and unpinned: the desktop follows
32 # the fork's main. It used to be paired with a second, pinned input for
33 # the APK's boot image; there is no jolt APK now, so there is one runtime
34 # and one rev.
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago35 jolt-src = {
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago36 url = "git+https://gitlab.com/nandithebull/jolt?submodules=1";
37 inputs.nixpkgs.follows = "nixpkgs";
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago38 };
39
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago40 # The source half of jolt-native: the Jolt code under glimmer-backends/ that
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago41 # binds the native objects, and the flake that builds them. This input is
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago42 # what `just cosmic run` builds against.
Take the jolt-native ahead of the release, since a run can build it 36f6c77 nandi 17d ago43 #
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago44 # It carries both backends that are left — glimmer-cosmic over
45 # libjoltcosmic for the window, glimmer-tui over libjolttui for the
46 # terminal — and no longer jvui or vidya, which were experiments.
Stop building the media plane frq no longer loads 231b9d3 nandi 9d ago47 #
Pin jolt-native at the merge rather than at the branch 3d7c428 nandi 9d ago48 # Pinned all the same, and pinned to a rev, because an
Pin the native half to the release the rest of the tree names cb7f9f1 nandi 17d ago49 # unpinned `main` is a build whose native half is free to sit at a
50 # different commit from the tree that talks to it. It did, and what the
51 # drift cost was silence: the Jolt half sent a reaction pill's hover card
52 # to a libvidya with no handler for one, and the pill said nothing.
Merge origin/main, and let one jolt-native answer for both backends 1e8b590 nandi 16d ago53 # It also carries the terminal backend — crates/jolt-tui, the same tree ABI
54 # over a grid of cells, and jolt/glimmer-tui beside glimmer-vidya. That was
55 # a second input at a second rev while it lived on a branch, which is the
56 # drift this comment warns about wearing a different hat: one input, and
57 # the window and the terminal are the same library either way.
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago58 jolt-native = {
Take the jolt-native that opens a room where it means to f218f2a nandi 7d ago59 url = "git+https://gitlab.com/nandithebull/jolt-native?rev=8e8cd5192dc161b423c0ee5dd41a7a058b24b409";
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago60 inputs.nixpkgs.follows = "nixpkgs";
Paint the same screens into a terminal ab83b42 nandi 17d ago61 };
62
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago63 # The sha deps.edn pins, on the fork with the reconciler fixes.
64 glimmer = {
65 url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";
66 flake = false;
67 };
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago68
69 # Only ever used off NixOS, to put the host GL driver on the loader path.
70 nixgl = {
71 url = "github:nix-community/nixGL";
72 inputs.nixpkgs.follows = "nixpkgs";
73 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago74 };
75
Two outputs and an input that were all one library b4172e9 nandi 22h ago76 outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer, nixgl }:
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago77 let
78 systems = [ "x86_64-linux" "aarch64-linux" ];
79 forEachSystem = f:
80 nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
Run this tree on a native half the builders made a32699e nandi 17d ago81
82 # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA
83 # wrappers are the ones that need --impure (they read the host kernel
84 # module's version), which is why this only ever reaches for Intel.
Stop carrying the 32-bit GL stack and half of git 040f2c1 nandi 16d ago85 #
86 # Built from nixGL's default.nix rather than taken from its flake
87 # outputs, for the one argument the flake hardcodes on: `enable32bits`,
88 # which on x86_64 puts a second, i686 copy of mesa, its LLVM, and
89 # intel-media-driver into the wrapper. frq is 64-bit on both halves —
90 # the Rust cdylibs and the Chez runtime — so nothing here ever opens the
91 # 32-bit driver, and carrying it is most of the dev shell's closure.
92 nixGLFor = pkgs: (import nixgl {
93 inherit pkgs;
94 enable32bits = false;
95 }).nixGLIntel;
Run this tree on a native half the builders made a32699e nandi 17d ago96
Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago97 # The Android SDK wants two things `nixpkgs.legacyPackages` cannot give:
98 # `allowUnfree`, because the SDK's own licence is not free, and
99 # `android_sdk.accept_license`, which is how you say so in a file rather
100 # than at a prompt a build has no terminal for. Neither can be set on a
101 # legacyPackages attribute after the fact, so this is a second import of
102 # the same locked nixpkgs rather than a second nixpkgs.
103 #
104 # This used to live in `just apk` as a `nix build --impure --expr` with
105 # `builtins.getFlake "github:NixOS/nixpkgs/nixos-unstable"` inside it —
106 # which fetched whatever nixos-unstable was that morning, not what
107 # flake.lock pins, so the SDK under the APK and the nixpkgs under
108 # everything else were free to drift apart. Here they are the same rev.
109 androidPkgsFor = system: import nixpkgs {
110 inherit system;
111 config = {
112 allowUnfree = true;
113 android_sdk.accept_license = true;
114 };
115 };
116
117 # Only the floor Gradle stands on. It installs build-tools and a platform
118 # into ANDROID_HOME itself as it goes — see `just apk` for why that means
119 # a writable copy — so composing more of them here buys nothing.
120 #
121 # includeNDK = false deliberately: the app is Dart and path_provider is
122 # platform channels, so there is no native code to need one, and asking
123 # for it is a few hundred megabytes and a Gradle fetch of that exact NDK.
124 androidSdkFor = system:
125 let android = androidPkgsFor system; in
126 (android.androidenv.composeAndroidPackages {
127 cmdLineToolsVersion = "13.0";
128 buildToolsVersions = [ "34.0.0" ];
129 platformVersions = [ "35" "34" ];
130 includeNDK = false;
131 }).androidsdk;
132
Run this tree on a native half the builders made a32699e nandi 17d ago133 # egui reaches for these with dlopen rather than linking them, so being
134 # in the cdylib's buildInputs is not enough — whatever starts frq has to
Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 16d ago135 # put them on the loader path itself. Without libx11 here, vidya reports
Run this tree on a native half the builders made a32699e nandi 17d ago136 # "X11 unavailable", falls back to Wayland, and winit refuses to build a
137 # second event loop after the failed first one.
138 #
139 # Out here rather than beside the package that first needed them: the
140 # dev shell starts frq too, on this tree's source rather than the store's
141 # copy of it, and a second copy of this list is a second chance for the
142 # two ways of running to disagree about what the window needs.
143 runtimeLibsFor = pkgs: with pkgs; [
144 libGL
145 libxkbcommon
146 wayland
Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 16d ago147 libx11
148 libxcursor
149 libxi
150 libxrandr
Run this tree on a native half the builders made a32699e nandi 17d ago151 vulkan-loader
152 ];
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago153 in
154 {
155 packages = forEachSystem (pkgs:
156 let
157 inherit (pkgs) lib;
158
Run this tree on a native half the builders made a32699e nandi 17d ago159 nixGL = nixGLFor pkgs;
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago160
Take the native objects from their own flake rather than rebuilding them 0ee8bba nandi 13d ago161 # libvidya (the retained-tree ABI glimmer-vidya binds, on egui),
162 # libjolttui (the same tree over a grid of cells) and libjoltmoq (the
163 # AV media plane) — one workspace, three cdylibs, taken from
164 # jolt-native's own flake rather than rebuilt here.
165 #
166 # This used to be a rustPlatform.buildRustPackage over the same
167 # source, which meant restating upstream's build: the seven git deps
168 # hashed by hand in `cargoLock.outputHashes` and re-hashed whenever
169 # its Cargo.lock moved, the linuxHeaders path v4l2r's bindgen wants,
170 # and a postPatch dropping the .cargo/config.toml that pointed the
171 # build at DotSlash. Upstream's flake says all of that itself now,
172 # and says it once. It also builds cpal with the `pipewire` feature,
173 # which the restatement did not — so device names in a call are
174 # PipeWire's rather than raw ALSA PCMs.
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago175 # libjolttui only. Not libjoltmoq, whose job `frq.av.plane` does
176 # now, and no longer libvidya either: the window is jvui on SDL,
177 # so the only object left out of that Cargo workspace is the
178 # terminal backend, and only `just tui` loads it.
Switch frq.av onto the jolt media plane, and stop shipping libjoltmoq b33f288 nandi 9d ago179 #
180 # This makes the closure smaller and the APK smaller. It does NOT
181 # make the build shorter, and it is worth being exact about why:
182 # jolt-native compiles its external crates ONCE, in a
183 # `buildDepsOnly` derivation shared by all three objects, so
184 # asking for two of them still builds every dependency the third
185 # has — the 440 crates that are jolt-moq's alone. Getting those
186 # out of the build is a change in jolt-native, not here: either
187 # jolt-moq leaves that workspace, or its deps artifact stops
188 # being workspace-wide.
189 native =
190 let np = jolt-native.packages.${pkgs.stdenv.hostPlatform.system};
191 in pkgs.symlinkJoin {
192 name = "jolt-native-ui";
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago193 # Both backends there are. libjoltcosmic is the window —
194 # libcosmic behind the same retained-tree ABI — and libjolttui
195 # is the terminal. Neither is libvidya and neither is jvui:
196 # those were experiments and are gone from this tree entirely.
197 paths = [ np.libjolttui np.libjoltcosmic ];
Switch frq.av onto the jolt media plane, and stop shipping libjoltmoq b33f288 nandi 9d ago198 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago199
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago200 # libmoq_ffi — MoQ over QUIC behind UniFFI's C ABI, FETCHED rather
201 # than built. This is the object `frq.moq.raw` is generated from.
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago202 #
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago203 # Fetched because building it is the thing this whole exercise is
204 # about: moq-ffi pulls moq-native, iroh, quinn, rustls and aws-lc-sys
205 # behind it, which is 440 crates that nothing else in this tree
206 # needs. Upstream already publishes the object for both Linux
207 # architectures, so we take those bytes.
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago208 #
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago209 # Pinned to a release and to a hash, and the hashes below are
210 # upstream's own published .sha256 files rather than ones observed
211 # here — a `nix-prefetch` of whatever the URL serves today would
212 # record that it downloaded something, not that it downloaded the
213 # right thing.
214 #
215 # WHAT THIS BUILD IS NOT: moq-ffi's `audio` and `video` features are
216 # on by default upstream and are OFF in these artifacts, so there is
217 # no publish_audio/publish_video and no moqaudio*/moqvideo* here —
218 # 206 functions where the Apple artifact has 230. That is why the
219 # bindings are generated from the object (`just gen-moq`) and not
220 # from the C header the release ships, which describes the Apple one.
221 moqFfi =
222 let
223 version = "0.3.17";
224 target = {
225 "x86_64-linux" = "x86_64-unknown-linux-gnu";
226 "aarch64-linux" = "aarch64-unknown-linux-gnu";
227 }.${pkgs.stdenv.hostPlatform.system};
228 hash = {
229 "x86_64-linux" = "sha256-dzQXpV4JgdtD+g33WX51FFAQdfCUXkNsx1xPbobPfUI=";
230 "aarch64-linux" = "sha256-PdzRwbJFqOZWRgI0HHX2XUH+Ljh4V3jvQ9asfvCuIPA=";
231 }.${pkgs.stdenv.hostPlatform.system};
232 in
233 pkgs.stdenv.mkDerivation {
234 pname = "libmoq-ffi";
235 inherit version;
236 src = pkgs.fetchurl {
237 url = "https://github.com/kixelated/moq/releases/download/moq-ffi-v${version}/moq-ffi-${version}-${target}-libmoq_ffi.so";
238 inherit hash;
239 };
240 dontUnpack = true;
241 # It carries no RUNPATH and needs libgcc_s, libm and libc — the
242 # host's on an ordinary distro, and nothing at all on NixOS
243 # unless they are bound here.
244 nativeBuildInputs = [ pkgs.autoPatchelfHook ];
245 buildInputs = [ pkgs.stdenv.cc.cc.lib ];
246 installPhase = ''
247 mkdir -p $out/lib
248 cp $src $out/lib/libmoq_ffi.so
249 chmod +w $out/lib/libmoq_ffi.so
250 '';
251 };
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago252
253 # One directory for the loader to look in. jolt resolves every
254 # :jolt/native name against JOLT_NATIVE_LIB, and the objects now come
255 # from two places — jolt-native's flake, and the moq-ffi release — so
256 # they are joined rather than the path being made a list, which the
257 # loader does not take.
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago258 # The C codecs, from nixpkgs. libmoq_ffi carries the transport and
259 # nothing else — moq-ffi's `audio` and `video` features would have
260 # brought Opus and H.264 with them, at the price of compiling a
261 # 1062-crate workspace — so the codecs are linked here instead,
262 # where they have always lived.
263 #
264 # Named in :jolt/native, so the loader resolves them the same way it
265 # resolves libvidya: by name, out of one directory.
266 # A flat C face for openh264, because openh264 has none. Its
267 # `ISVCEncoder` is `const ISVCEncoderVtbl*` — every method is a
268 # function pointer in a vtable — and jolt.ffi cannot call one: Chez
269 # fixes a foreign procedure's types when it compiles it, and the
270 # target must be a literal C symbol name. So the vtable is walked in
271 # c/frq_h264.c and jolt binds the five plain symbols it exports.
272 #
273 # One translation unit against a library nixpkgs already has. It is
274 # a calling convention adapter, not a second media plane, and the
275 # distinction from the moq-ffi build it replaces is the whole point:
276 # this compiles one .c file, not a 1062-crate workspace.
277 frqH264 = pkgs.stdenv.mkDerivation {
278 pname = "frq-h264";
279 version = "0.1";
280 src = ./c;
281 nativeBuildInputs = [ pkgs.pkg-config ];
282 buildInputs = [ pkgs.openh264 ];
283 buildPhase = ''
284 $CC -O2 -fPIC -shared frq_h264.c -o libfrqh264.so \
285 $(pkg-config --cflags --libs openh264)
286 '';
287 installPhase = ''
288 mkdir -p $out/lib && cp libfrqh264.so $out/lib/
289 '';
290 };
291
292 # openh264 is here for frqH264's DT_NEEDED; alsa-lib for capture
293 # and playback. V4L2 needs nothing: it is ioctls against libc and
294 # the kernel, so there is no library to name.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago295 # No SDL any more: it was jvui's, declared in jvui's own
296 # :jolt/native and dlopened by soname. libcosmic paints through wgpu
297 # and takes what it needs from `runtimeLibs` instead.
298 codecs = [ pkgs.libopus pkgs.openh264 frqH264 pkgs.alsa-lib ];
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago299
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago300 # ALSA's PipeWire plugin, which is how `default` resolves to
301 # anything on a machine running PipeWire — and every machine frq
302 # targets does. Without it alsa-lib fails to dlopen
303 # libasound_module_pcm_pipewire.so and the only devices that open
304 # are raw hardware ones, which PipeWire is already holding.
305 #
306 # An environment variable rather than a library in the join:
307 # alsa-lib looks plugins up by directory, not by soname.
308 alsaPluginDir = "${pkgs.pipewire}/lib/alsa-lib";
309
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago310 nativeAll = pkgs.symlinkJoin {
311 name = "frq-native";
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago312 paths = [ native moqFfi ] ++ codecs;
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago313 };
314
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago315 # Jolt itself: Clojure on Chez, built the way its own flake builds it.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago316 #
317 # Still a function taking its source, though there is only one of
318 # them now: the second was the Bionic-addrinfo fork the APK's boot
319 # image carried, and there is no jolt APK any more — the phone is
320 # ClojureDart and Flutter, and jolt does not run there at all.
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago321 joltFrom = src: pkgs.stdenv.mkDerivation {
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago322 pname = "jolt";
323 version = "dev";
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago324 inherit src;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago325
326 strictDeps = true;
327 nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];
328 buildInputs = with pkgs; [ lz4 zlib ncurses openssl libuuid ];
329
330 JOLT_VERSION = "dev";
331 dontConfigure = true;
332
333 buildPhase = ''
334 runHook preBuild
335 scheme --script host/chez/build-jolt.ss release target/release/jolt
336 runHook postBuild
337 '';
338
339 installPhase = ''
340 runHook preInstall
341 mkdir -p "$out/bin"
342 install -m755 target/release/jolt "$out/bin/jolt"
343 runHook postInstall
344 '';
345
346 # jolt.deps shells out to git and unzip, and jolt.mvn-http dlopens
Stop carrying the 32-bit GL stack and half of git 040f2c1 nandi 16d ago347 # OpenSSL through the JOLT_OPENSSL_LIBDIR seam. gitMinimal rather
348 # than git: all jolt.deps asks for is clone/fetch/rev-parse, and
349 # the full package carries Perl and Python for the subcommands
350 # written in them — a quarter of a gigabyte for git-send-email.
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago351 #
352 # TZDIR so a zone *name* resolves wherever this runs: frq.clock
353 # hands one to tzset, and glibc then looks for the tzfile under
354 # /usr/share/zoneinfo unless told otherwise — which a NixOS host
355 # does not have. The store's own tzdata is there on both kinds of
356 # machine. --set-default, so a TZDIR the user set still wins.
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago357 postFixup = ''
358 wrapProgram "$out/bin/jolt" \
Stop carrying the 32-bit GL stack and half of git 040f2c1 nandi 16d ago359 --prefix PATH : "${pkgs.lib.makeBinPath [ pkgs.gitMinimal pkgs.unzip ]}" \
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago360 --set-default JOLT_OPENSSL_LIBDIR "${pkgs.lib.makeLibraryPath [ pkgs.openssl ]}" \
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago361 --set-default TZDIR "${pkgs.tzdata}/share/zoneinfo" \
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago362 --set-default SSL_CERT_FILE "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
363 '';
364 };
365
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago366 joltRuntime = joltFrom jolt-src;
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago367
368 # The backends' Clojure halves, which live inside the jolt-native
369 # checkout beside the objects they bind. Their own deps.edn asks for
370 # glimmer by git — the top-level override below answers for both.
371 glimmerCosmic = "${jolt-native}/glimmer-backends/glimmer-cosmic";
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago372 glimmerTui = "${jolt-native}/glimmer-backends/glimmer-tui";
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago373
Run this tree on a native half the builders made a32699e nandi 17d ago374 runtimeLibs = runtimeLibsFor pkgs;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago375
376 # The project as jolt sees it: source, deps.edn, nothing else.
377 frqSource = pkgs.runCommand "frq-source" { } ''
378 mkdir -p "$out"
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago379 cp -r ${self}/common ${self}/src ${self}/deps.edn "$out/"
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago380 '';
381
382 # Jolt resolves deps.edn from the working directory, so the launcher
383 # runs from the store copy. Its .jolt/cpcache write lands on a
384 # read-only directory and jolt treats that as a quiet cache miss, so
385 # the only cost is re-resolving the (already local) graph per start.
386 frqScript = pkgs.writeShellScript "frq" ''
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago387 export LD_LIBRARY_PATH="${nativeAll}/lib:${lib.makeLibraryPath runtimeLibs}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago388 export ALSA_PLUGIN_DIR="${alsaPluginDir}"
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago389 cd ${frqSource}
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago390
391 # On NixOS the store's Mesa is the system's and the window opens.
392 # Anywhere else the real driver is the host's, so defer to nixGL
393 # it prepends the host driver, which has to win over ours.
394 runner=""
395 [ -e /run/current-system ] || runner="${nixGL}/bin/nixGLIntel"
396
397 exec ''${runner} ${joltRuntime}/bin/jolt \
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago398 -Sdeps '{:deps {jolt-lang/glimmer {:local/root "${glimmer}"} nandi/glimmer-cosmic {:local/root "${glimmerCosmic}"}}}' \
399 -m frq.cosmic "$@"
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago400 '';
401
Paint the same screens into a terminal ab83b42 nandi 17d ago402 # The same source, the other backend. No GL, no nixGL and no X11 —
403 # a terminal is the one surface that needs nothing from the host but
404 # a terminal, which is the reason this output exists.
405 tuiScript = pkgs.writeShellScript "frq-tui" ''
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago406 export LD_LIBRARY_PATH="${nativeAll}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago407 export ALSA_PLUGIN_DIR="${alsaPluginDir}"
Paint the same screens into a terminal ab83b42 nandi 17d ago408 cd ${frqSource}
409
410 exec ${joltRuntime}/bin/jolt \
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago411 -Sdeps '{:deps {jolt-lang/glimmer {:local/root "${glimmer}"} nandi/glimmer-tui {:local/root "${glimmerTui}"}}}' \
Paint the same screens into a terminal ab83b42 nandi 17d ago412 -m frq.tui "$@"
413 '';
414
415 tui = pkgs.runCommand "frq-tui-0.1.0"
416 {
417 meta = {
418 description = "frq's screens in a terminal";
419 mainProgram = "frq-tui";
420 platforms = systems;
421 };
422 }
423 ''
424 mkdir -p "$out/bin"
425 ln -s ${tuiScript} "$out/bin/frq-tui"
426 '';
427
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago428 frq = pkgs.runCommand "frq-0.1.0"
429 {
430 meta = {
431 description = "A freeq client in jolt";
432 mainProgram = "frq";
433 platforms = systems;
434 };
435 }
436 ''
437 mkdir -p "$out/bin"
438 ln -s ${frqScript} "$out/bin/frq"
439 '';
440 in
441 {
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago442 inherit native moqFfi frqH264 nativeAll frq;
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago443 inherit (pkgs) pipewire;
Merge origin/main, and let one jolt-native answer for both backends 1e8b590 nandi 16d ago444 inherit tui;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago445 jolt = joltRuntime;
446 default = frq;
Bundle the closure into one file the AppImage runtime mounts f82b93f nandi 18d ago447
Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago448 # The Android SDK `just apk` copies into flutter/.home. A package
449 # rather than something the recipe evaluates inline, so that
450 # `nix build .#android-sdk` is how you pre-warm it and `nix flake
451 # show` admits it exists.
452 android-sdk = androidSdkFor pkgs.stdenv.hostPlatform.system;
453
Two outputs and an input that were all one library b4172e9 nandi 22h ago454 # There were two `appimage` outputs here — this GUI's and Flutter's —
455 # and what they were for was a host without Nix. They squashed the
456 # whole closure into one runnable file, Mesa included, and the Mesa
457 # was not waste: off NixOS the launcher goes through nixGL, which
458 # needs a store Mesa to put the host's driver in front of.
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago459 #
Two outputs and an input that were all one library b4172e9 nandi 22h ago460 # `tools/build-desktop.sh` answers that now, and answers it without
461 # a closure to carry: jolt ships as one static binary, the backends
462 # as jolt-native's `portable` tarball, and the GL driver is simply
463 # the host's. No Mesa to get in front of, so no nixGL, so nothing to
464 # squash. These outputs were the last thing evaluating nix-appimage,
465 # which is why that input is gone too.
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago466
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago467 # Everything `clojure -M:cljd compile` would otherwise reach the
468 # network for, fetched once and hashed.
469 #
470 # The compile needs three caches, and the reason this is one
471 # derivation rather than three is that only one of them is obvious.
472 # Maven and gitlibs are the ordinary tools.deps pair. The third is
473 # ClojureDart's own: `ensure-cljd-analyzer!` writes a *second*, whole
474 # pub project to `.clojuredart/cache/<cljd sha>/cljd_helper`, runs
475 # `pub add analyzer` in it, and then runs `bin/analyzer.dart` out of
476 # it for the duration of the compile — so a sandbox needs that
477 # project already resolved, not just the app's dependencies.
478 #
479 # Fixed-output, so it is allowed the network the rest of the build is
480 # not. What that costs is a hash to maintain, and the thing worth
481 # being exact about is *when*: this derivation never sees frq's
482 # source. It compiles a three-line throwaway project against the same
483 # `flutter/deps.edn` and the same `flutter/pubspec.yaml`, so the hash
484 # moves when a dependency moves and not when a screen changes. A
485 # stub, rather than `-P` and a hand-built analyzer dir, because
486 # running the real compiler once is the only way to be sure the
487 # caches are the ones it actually wants.
488 #
489 # PUB_CACHE lands in $out on purpose. The package_config.json inside
490 # cljd_helper carries absolute paths to whatever resolved it, so
491 # resolving into a build directory would bake in paths that stop
492 # existing the moment this derivation finishes. Pointed at $out they
493 # are store paths, and still true.
494 cljd-deps =
495 let
496 flutterPkg = pkgs.flutter;
497 in
498 pkgs.stdenvNoCC.mkDerivation {
499 name = "frq-cljd-deps";
500 dontUnpack = true;
501
502 nativeBuildInputs = [
503 pkgs.clojure
504 pkgs.jdk17
505 flutterPkg
506 pkgs.git
507 pkgs.cacert
508 ];
509
510 buildCommand = ''
511 export HOME="$NIX_BUILD_TOP/home"
512 # Resolved in the build directory and copied to $out at the
513 # end, never written there directly. A fixed-output derivation
514 # may not reference a store path and its own output is a store
515 # path, so pub writing its cache's absolute location into its
516 # own metadata is enough to fail the check.
517 cache="$NIX_BUILD_TOP/cache"
518 export PUB_CACHE="$cache/pub-cache"
519 export GITLIBS="$cache/gitlibs"
520 export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
521 mkdir -p "$HOME" "$PUB_CACHE" "$GITLIBS" "$cache/m2"
522
Make ../common a dependency rather than a path 95d91a2 nandi 7d ago523 # The stub: our dependency files, nothing of our source.
524 # ../common is a :local/root dependency now, so it has to exist
525 # *and* carry a deps.edn for tools.deps to resolve an empty
526 # directory with that one file in it is enough.
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago527 proj="$NIX_BUILD_TOP/stub"
528 mkdir -p "$proj/src/stub" "$NIX_BUILD_TOP/common"
Make ../common a dependency rather than a path 95d91a2 nandi 7d ago529 cp ${./common/deps.edn} "$NIX_BUILD_TOP/common/deps.edn"
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago530 cp ${./flutter/deps.edn} "$proj/deps.edn"
531 cp ${./flutter/pubspec.yaml} "$proj/pubspec.yaml"
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago532 # The lock, or `pub get` resolves against pub.dev and takes
533 # whatever satisfies the ranges today. Every build then fetches
534 # a slightly different set and the fixed-output hash is a
535 # promise nothing can keep.
536 cp ${./flutter/pubspec.lock} "$proj/pubspec.lock"
537 chmod u+w "$proj/deps.edn" "$proj/pubspec.yaml" "$proj/pubspec.lock"
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago538 cat > "$proj/src/stub/main.cljd" <<'EOF'
539 (ns stub.main)
540 (defn main [] nil)
541 EOF
542
543 cd "$proj"
544 # `:main` has to name the stub, or the compiler goes looking for
545 # frq.main in a tree that is not here.
546 sed -i 's/:main frq\.main/:main stub.main/' deps.edn
547
548 flutter config --no-analytics &>/dev/null || true
549 flutter config --enable-linux-desktop >/dev/null || true
550
551 clojure -Sdeps '{:mvn/local-repo "'"$cache"'/m2"}' -M:cljd compile
552
553 # What the compile left behind, and only that. The analyzer
554 # project is keyed by the ClojureDart sha, so the directory
555 # under cache/ is copied wholesale rather than named here.
556 mkdir -p "$out/clojuredart"
557 cp -r .clojuredart/cache "$out/clojuredart/cache"
558 cp -r "$cache/m2" "$out/m2"
559 cp -r "$cache/gitlibs" "$out/gitlibs"
560 cp -r "$PUB_CACHE" "$out/pub-cache"
561
562 # A fixed-output derivation may not reference a store path, and
563 # a resolved pub project is nothing but store paths:
564 # package_config.json names the Flutter SDK and every package
565 # in the cache by absolute path. So the analyzer project ships
566 # *unresolved* its pubspec and its analyzer.dart and nothing
567 # else and `flutter pub get --offline` re-resolves it against
568 # this cache at build time, where naming the store is allowed.
569 find "$out" \( -name '.dart_tool' -o -name '.flutter-plugins' \
570 -o -name '.flutter-plugins-dependencies' \) -prune -exec rm -rf {} +
571 find "$out" -name '.packages' -delete
572
573
574 # A fixed-output hash is a promise that two runs agree, so
575 # everything a tool writes *about* a run rather than about a
576 # dependency has to go: pub's log carries timestamps, Maven
577 # rewrites its resolution metadata on every resolve, and
578 # tools.gitlibs keeps bare clones it only needs in order to
579 # make a checkout. None of it is read offline.
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago580 #
581 # active_roots is the one that was actually breaking this. Pub
582 # records the project directories using the cache, sharded by
583 # a hash of the path, and $NIX_BUILD_TOP is different on every
584 # run -- so two builds whose hosted/ trees were byte-identical
585 # still disagreed, purely over which directory had asked. Four
586 # builds gave four hashes until this went.
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago587 rm -rf "$out/pub-cache/log" "$out/pub-cache/_temp" \
588 "$out/pub-cache/git" "$out/pub-cache/global_packages" \
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago589 "$out/pub-cache/bin" "$out/pub-cache/active_roots"
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago590
591 # tools.gitlibs keeps a bare clone per URL under _repos/, and a
592 # bare clone is packfiles which two runs of the same fetch do
593 # not have to produce byte for byte. It cannot simply be
594 # deleted, because `procure` calls `ensure-git-dir` before it
595 # looks at anything else and would clone it again, over a
596 # network this has and the build that uses it does not.
597 #
598 # It does not need the objects, though. `procure` finds the sha
599 # with `match-exact` against the checkout already in libs/, so
600 # the bare repo only has to exist. Emptied and re-initialised,
601 # it is a fixed handful of files from the pinned git and the
602 # same on every run.
603 find "$out/gitlibs/_repos" -name HEAD | while read -r head; do
604 repo="$(dirname "$head")"
605 rm -rf "$repo"
606 git init --bare -q "$repo"
607 # The sample hooks are shell scripts, so they carry a
608 # `#!/nix/store/.../bash` line which is exactly the kind of
609 # store reference a fixed-output derivation may not hold. An
610 # empty bare repo nothing ever runs has no use for them.
611 rm -rf "$repo/hooks"
612 done
613 # pub's version listings, which record when they were fetched.
614 # This is the one that actually moved between two runs of this
615 # derivation: the package sources under hosted/ were identical
616 # and the listings beside them were not. Nothing offline reads
617 # them a resolution that already has every package on disk
618 # never asks pub.dev what versions exist.
619 find "$out/pub-cache" -name '.cache' -type d -prune -exec rm -rf {} +
620 find "$out/m2" \( -name '*.lastUpdated' -o -name '_remote.repositories' \
621 -o -name 'resolver-status.properties' -o -name '*.part' \
622 -o -name 'maven-metadata-*.xml*' \) -delete
623 find "$out" \( -name '.DS_Store' -o -name '*.log' -o -name '.git' \) \
624 -prune -exec rm -rf {} +
625 find "$out" -type d -empty -delete
626 chmod -R u+w "$out"
627
628 # Last, after every cleanup above: anything still naming the
629 # store fails the fixed-output check, and the error names one
630 # path out of thousands of files. This names the files.
631 if refs="$(grep -rlI /nix/store "$out" 2>/dev/null)" && [ -n "$refs" ]; then
632 echo "cljd-deps: these still reference the store:" >&2
633 echo "$refs" | head -20 >&2
634 fi
635
636 # If two runs disagree, this says which half to look in. Cheap,
637 # and the alternative is a hash mismatch with nothing attached.
638 for d in "$out"/*; do
639 echo "cljd-deps subtree $(basename "$d") $( (cd "$d" && find . -type f \
640 -exec sha256sum {} + | sort -k2 | sha256sum) )" >&2
641 done
642 for d in "$out"/pub-cache/*/*; do
643 [ -d "$d" ] || continue
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago644 # The path relative to $out, not the basename: two runs that
645 # disagree here disagree about *which* directory exists, and
646 # a bare `09` names nothing you can go and look at.
647 echo "cljd-deps pub ''${d#$out/} $( (cd "$d" && find . -type f \
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago648 -exec sha256sum {} + | sort -k2 | sha256sum) )" >&2
649 done
650 '';
651
652 outputHashMode = "recursive";
653 outputHashAlgo = "sha256";
654 # Moves when flutter/deps.edn or flutter/pubspec.yaml move, and
655 # not when frq's own source does — see the stub above.
Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago656 outputHash = "sha256-qSGx7WFdVyV7yu4R+EjiQHZcLqwDjYSlohiZcXB43DY=";
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago657 };
658
659 # The Flutter desktop GUI, built rather than run out of the tree.
660 #
661 # `just flutter-desktop` is the working-tree loop and this is its
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago662 # opposite number, the same way `nix build .#frq` is `just cosmic
663 # run`'s: the source is the flake's, the output is a store path, and the build is
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago664 # a sandbox with no network. It is the first thing here that builds
665 # purely — the APK cannot, because Gradle fetches as it goes.
666 #
667 # Two stages, because the Dart does not exist until ClojureDart writes
668 # it. `preBuild` runs the compiler over `flutter/src` and `common/`
669 # with `--offline`, out of the caches `cljd-deps` fetched; everything
670 # after that is an ordinary Flutter application as far as nixpkgs is
671 # concerned.
672 #
673 # The caches are copied in rather than used where they lie. Maven,
674 # tools.gitlibs and pub all expect to be able to write to their own
675 # cache — a lock file, a resolved marker — and the store is read-only,
676 # so pointing them at $out of a fixed-output derivation fails in three
677 # different ways at three different depths.
678 #
679 # `src` is the whole tree and not `flutter/`: `flutter/deps.edn` puts
680 # `../common` on the classpath, which is the entire point of that
681 # directory, and a source root of `flutter/` would leave the screens
682 # outside it.
683 flutter-desktop-unwrapped = pkgs.flutter.buildFlutterApplication rec {
684 pname = "frq-flutter";
685 version = "0.1.0";
686
687 src = lib.cleanSourceWith {
688 src = ./.;
The window opens, and a devShell that remembers what it built c20643a nandi 5d ago689 # Two trees, and only two: `flutter/` is the app and `common/`
690 # is the screens its deps.edn puts on the classpath. The root is
691 # still the source root because of that `../common`, but letting
692 # the *whole* root in means every file in the repo is an input —
693 # so editing flake.nix, or CLAUDE.md, or the jolt half in `src/`,
694 # invalidated the entire Dart compile and paid ten minutes for a
695 # change the Flutter build cannot even see.
696 #
697 # Matched on the path relative to the root rather than on
698 # basename: `src` as a basename would also exclude `flutter/src`
699 # and `common/src`, which is everything that matters.
700 filter =
701 let root = toString ./.; in
702 path: type:
703 let
704 rel = lib.removePrefix (root + "/") (toString path);
705 inTree = d: rel == d || lib.hasPrefix (d + "/") rel;
706 in
707 (inTree "flutter" || inTree "common")
708 # Build trees and caches, which are large, machine-specific
709 # and would make every one of them a new store path.
710 && !(builtins.elem (baseNameOf path) [
711 "build" ".home" ".clojuredart" ".cpcache" "cljd-out"
712 ".dart_tool" "result" ".git" ".jolt" "buck-out"
713 ]);
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago714 };
715 sourceRoot = "source/flutter";
716
717 # Read at eval time, so the lock in git is the lock that is built.
718 autoPubspecLock = ./flutter/pubspec.lock;
719
720 # git, because tools.deps resolves the ClojureDart dependency through
721 # tools.gitlibs even when every byte of it is already on disk — see
722 # the _repos note in cljd-deps.
723 nativeBuildInputs = [ pkgs.clojure pkgs.jdk17 pkgs.git ];
724
725 preBuild = ''
726 export PUB_CACHE="$NIX_BUILD_TOP/pub-cache"
727 export GITLIBS="$NIX_BUILD_TOP/gitlibs"
728 cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/pub-cache "$PUB_CACHE"
729 cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/gitlibs "$GITLIBS"
730 cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/m2 "$NIX_BUILD_TOP/m2"
731 mkdir -p .clojuredart
732 cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/clojuredart/cache .clojuredart/cache
733 chmod -R u+w "$PUB_CACHE" "$GITLIBS" "$NIX_BUILD_TOP/m2" .clojuredart
734
735 # Resolve the analyzer project here rather than in cljd-deps,
736 # which was not allowed to name the store. Offline, out of the
737 # cache that derivation did fetch. ClojureDart only reaches for
738 # the network when `bin/analyzer.dart` is missing, and it is not.
739 for helper in .clojuredart/cache/*/cljd_helper; do
740 ( cd "$helper" && flutter pub get --offline )
741 done
742
743 # --offline is what keeps `pub get` out of a sandbox that has no
744 # network; the analyzer project it would otherwise resolve is
745 # already in .clojuredart, put there by cljd-deps.
746 clojure -Sdeps "{:mvn/local-repo \"$NIX_BUILD_TOP/m2\"}" \
747 -M:cljd compile --offline
748 '';
749
750 meta = {
751 description = "frq's screens on Flutter's Linux target (no GL launcher)";
752 mainProgram = "frq";
753 platforms = systems;
754 };
755 };
756
757 # The same shape as `frq` above: a launcher, and a package that is a
758 # symlink to it. The reason is the same one `frqScript` gives — on
759 # NixOS the store's Mesa is the system's and the window opens, and
760 # anywhere else the real driver is the host's, so the process is
761 # handed to nixGL. Without it the store build dies on a distrobox
762 # Arch with "No provider of eglGetPlatformDisplayEXT found", which is
763 # that failure wearing an EGL hat.
764 #
765 # A wrapper *around* the built application rather than a `postFixup`
766 # inside it, because buildFlutterApplication's own dartFixupHook runs
767 # after postFixup and rewrites `bin/frq` — so anything done to that
768 # path from inside is undone on the way out.
769 flutter-desktop =
770 let
771 unwrapped =
772 self.packages.${pkgs.stdenv.hostPlatform.system}.flutter-desktop-unwrapped;
The window opens, and a devShell that remembers what it built c20643a nandi 5d ago773
774 # buildFlutterApplication's own wrapper appends a bare `/lib` to
775 # LD_LIBRARY_PATH — the host's, not the bundle's. Off NixOS that
776 # is a foreign library directory in front of nothing, and the app
777 # dies in the loader before main: first
778 #
779 # /lib/libc.so.6: undefined symbol: __pointer_chk_guard
780 #
781 # and, once the store's glibc is put ahead of it,
782 #
783 # libc.so.6: version `GLIBC_2.43' not found
784 # (required by /lib/libglib-2.0.so.0)
785 #
786 # which is the same bug wearing the other hat: the host's glib
787 # against the store's glibc. Ordering cannot fix a mixture, so
788 # the entry goes rather than moves. The wrapper is generated, so
789 # this edits a copy and asserts the edit landed — a silent miss
790 # here is a runtime failure on someone else's machine.
791 fixed = pkgs.runCommand "frq-flutter-wrapper" { } ''
792 mkdir -p "$out/bin"
793 sed "s|'/lib'||g" ${unwrapped}/bin/frq > "$out/bin/frq"
794 chmod +x "$out/bin/frq"
795 if grep -q "'/lib'" "$out/bin/frq"; then
796 echo "the /lib entry outlived the edit; look at the wrapper" >&2
797 exit 1
798 fi
799 '';
800
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago801 script = pkgs.writeShellScript "frq" ''
802 runner=""
803 [ -e /run/current-system ] || runner="${nixGLFor pkgs}/bin/nixGLIntel"
The window opens, and a devShell that remembers what it built c20643a nandi 5d ago804 exec ''${runner} ${fixed}/bin/frq "$@"
Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 7d ago805 '';
806 in
807 pkgs.runCommand "frq-flutter-0.1.0"
808 {
809 meta = {
810 description = "frq's screens on Flutter's Linux target";
811 mainProgram = "frq";
812 platforms = systems;
813 };
814 }
815 ''
816 mkdir -p "$out/bin"
817 ln -s ${script} "$out/bin/frq"
818 '';
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago819 });
820
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago821 # Where `just cosmic run` runs, and — because entering it realises what it
Run this tree on a native half the builders made a32699e nandi 17d ago822 # names — what builds the half of frq that is not this working tree.
823 #
824 # The two halves, and the split is the whole point of the shell. The frq
825 # source is the files on disk, uncommitted edits and all. Everything
826 # under it — jolt, glimmer, glimmer-vidya, both native objects — is the
827 # flake's, at the revs flake.lock names, so a run says what it ran
828 # against and both halves of glimmer-vidya move together. That is the
829 # drift the `jolt-native` input's comment is about, and a pin frq can
830 # answer for is worth more here than the convenience of a checkout.
831 #
Take the native objects from their own flake rather than rebuilding them 0ee8bba nandi 13d ago832 # `native` is jolt-native's own flake output. It was a buck2 graph when
833 # this comment was first written and a cargo build restated here when it
834 # was second: buck2 fetches its rustc, zig and every third-party crate as
835 # it goes and writes buck-out into the tree it builds, so a sandbox with
836 # no network and a read-only store was the one place it could not run.
837 # Upstream builds with nix now, so the thing its CI runs and the thing
838 # this shell hands a builder are the same derivation.
Run this tree on a native half the builders made a32699e nandi 17d ago839 #
840 # Nothing here says "nixbuild", though: it is a plain derivation, and
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago841 # where it gets built is the machine's business. The `cosmic` recipe asks for
Run this tree on a native half the builders made a32699e nandi 17d ago842 # the shell with --max-jobs 0, which is what sends it to the `builders`
843 # entry rather than compiling egui on a laptop.
844 devShells = forEachSystem (pkgs:
845 let
846 inherit (pkgs) lib;
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago847 inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) jolt native nativeAll;
Run this tree on a native half the builders made a32699e nandi 17d ago848 in
849 {
850 default = pkgs.mkShellNoCC {
851 name = "frq";
852
853 # jolt, because the runtime frq is run by should be the flake's
854 # too. nixGL for the same reason the launcher reaches for it — see
Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago855 # frqScript. just so the recipe runner comes from here too rather
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago856 # than the host — `nix develop` and then `just cosmic run` is the
857 # whole of what a machine with nix needs.
Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago858 packages = [ jolt pkgs.just (nixGLFor pkgs) ];
859
860 # Read by the recipes rather than baked into a wrapper: the frq
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago861 # source `just cosmic run` runs is the working tree, so the
862 # launcher has to live in that tree and the shell has to hand it its answers.
Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago863 # Naming these is also what makes the shell build them.
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago864 JOLT_NATIVE_LIB = "${nativeAll}/lib";
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago865 # Spelled out rather than shared with the packages block, which
866 # is a different `let`. See `alsaPluginDir` there for why.
867 ALSA_PLUGIN_DIR = "${pkgs.pipewire}/lib/alsa-lib";
Run this tree on a native half the builders made a32699e nandi 17d ago868 GLIMMER_SRC = glimmer;
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago869 GLIMMER_COSMIC_SRC = "${jolt-native}/glimmer-backends/glimmer-cosmic";
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago870 GLIMMER_TUI_SRC = "${jolt-native}/glimmer-backends/glimmer-tui";
Run this tree on a native half the builders made a32699e nandi 17d ago871 FRQ_LIB_PATH = lib.makeLibraryPath (runtimeLibsFor pkgs);
872 NIXGL = "${nixGLFor pkgs}/bin/nixGLIntel";
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago873
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago874 # A checkout of jolt-native in place of the pin, when one is NAMED.
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago875 #
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago876 # The pin is a rev on a server, so the loop for a change to a
877 # backend would be commit, push, re-pin, re-lock — four steps and
878 # an upload for a line of Rust. Pointing FRQ_JOLT_NATIVE at a
879 # working copy makes the loop `cargo build` and `just tui`.
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago880 #
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago881 # This used to find that copy by itself — ../jolt-native beside the
882 # checkout — and that is gone, because a found tree is the wrong
883 # default twice over. It fired on a sibling nobody had asked about,
884 # so a shell could be running something other than the pin on the
885 # strength of a directory existing. And what it prepended was a raw
886 # `cargo build` artifact: no store RUNPATH, so its libc.so.6
887 # resolves to the host's, and the Nix-glibc jolt that dlopens it
888 # gets a second libc and fails. jolt reports that as "required
889 # native library ... not found", which names neither libc nor the
890 # tree the object came from. An override worth having is one you
891 # can see in the command you typed.
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago892 #
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago893 # So: named or nothing. A named tree is taken even unbuilt (with a
894 # word about what to run) — naming it is asking for it — and it has
895 # to carry the RUNPATH the store copy does, which in practice means
896 # building it under Nix.
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago897 #
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago898 # Only the sources and the objects a checkout actually builds move.
899 # Everything else on the library path — libopus, libmoq_ffi, the
900 # ALSA plugins — stays the pin's, since a checkout has no build of
901 # those to offer.
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago902 shellHook = ''
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago903 if [ -n "''${FRQ_JOLT_NATIVE:-}" ]; then
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago904 if [ -d "$FRQ_JOLT_NATIVE/crates/jolt-tui" ]; then
905 FRQ_JOLT_NATIVE="$(cd "$FRQ_JOLT_NATIVE" && pwd)"
906 export FRQ_JOLT_NATIVE
907 export GLIMMER_TUI_SRC="$FRQ_JOLT_NATIVE/glimmer-backends/glimmer-tui"
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago908 export GLIMMER_COSMIC_SRC="$FRQ_JOLT_NATIVE/glimmer-backends/glimmer-cosmic"
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago909 # First, so a cargo build wins over the pin's copy of the
910 # same object. The rest of the pin's lib directory is still
911 # behind it.
912 export JOLT_NATIVE_LIB="$FRQ_JOLT_NATIVE/target/release:$JOLT_NATIVE_LIB"
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago913 echo "frq: jolt-native from $FRQ_JOLT_NATIVE, not the pin (unset FRQ_JOLT_NATIVE for the pin)" >&2
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago914 if [ ! -e "$FRQ_JOLT_NATIVE/target/release/libjolttui.so" ]; then
915 echo "frq: no libjolttui.so there yet cargo build --release --features terminal -p jolt-tui" >&2
916 fi
Take the jolt-native you name, not the one next door c6ce105 nandi 6d ago917 else
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago918 echo "frq: FRQ_JOLT_NATIVE=$FRQ_JOLT_NATIVE is not a jolt-native checkout; using the pin" >&2
919 fi
920 fi
921 '';
Run this tree on a native half the builders made a32699e nandi 17d ago922 };
Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago923
924 # The APK toolchain, which the default shell deliberately does not
925 # carry: Flutter brings its own Dart, Gradle and a JDK's worth of
926 # closure, and a desktop build has no use for any of it.
927 #
928 # `just apk` used to name these as `nix shell nixpkgs#clojure
929 # nixpkgs#jdk17 nixpkgs#flutter`, which is the flake registry's
930 # nixpkgs and not this flake's — so the Flutter under the APK
931 # floated while everything else was locked. Same three packages,
932 # from flake.lock now.
933 #
934 # JDK 17 and not newer on purpose: the Flutter template's Gradle
935 # plugin pins a Gradle that rejects a JDK it was released before,
936 # and the failure reads as an unsupported class file version rather
937 # than as a version mismatch.
938 flutter = pkgs.mkShellNoCC {
939 name = "frq-flutter";
Seed the ClojureDart caches out of the flake f2e12af nandi 7d ago940
941 # git, because tools.deps resolves the ClojureDart dependency
942 # through tools.gitlibs even when every byte of it is already in
943 # the seeded cache — the same reason flutter-desktop-unwrapped
944 # names it. The host's git has always been there to answer; naming
945 # it means the shell does not depend on that.
946 packages = [ pkgs.clojure pkgs.jdk17 pkgs.flutter pkgs.just pkgs.git ];
Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago947
948 # Where the recipe copies from. Naming it here is also what makes
949 # entering the shell build it, so the first `just apk` does not
950 # stop for a few hundred megabytes of SDK with nothing said about
951 # why.
952 FRQ_ANDROID_SDK =
953 "${androidSdkFor pkgs.stdenv.hostPlatform.system}/libexec/android-sdk";
Seed the ClojureDart caches out of the flake f2e12af nandi 7d ago954
955 # The Maven, gitlibs and pub caches the ClojureDart compile would
956 # otherwise fetch, plus the analyzer project it writes under
957 # .clojuredart. Here for both of FRQ_ANDROID_SDK's reasons: it is
958 # where the recipe copies from, and naming it is what makes
959 # entering the shell build it.
960 #
961 # The compile still runs online. These are a warm start and not a
962 # pin — `--offline` would be, and would turn adding a line to
963 # flutter/deps.edn into a re-hash of cljd-deps before anything
964 # compiled again. The sandbox build takes that trade because it
965 # has no network; the loop someone edits in should not.
966 FRQ_CLJD_DEPS = "${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}";
Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago967 };
Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago968
969 # The other desktop GUI. Same ClojureDart half as the APK — one
970 # `clojure -M:cljd compile`, one flutter/src — over Flutter's Linux
971 # target instead of its Android one, so `flutter/linux/` is the
972 # runner and CMake and Ninja are the build rather than Gradle.
973 #
974 # A separate shell from `flutter` rather than one that carries both,
975 # because the halves are disjoint: this wants GTK and a C++ toolchain
976 # and no JDK, and the APK wants a JDK and an SDK and no GTK. Sharing
977 # them would mean every desktop build paying for a few hundred
978 # megabytes of Android SDK it never opens, which is the same argument
979 # that keeps Flutter out of the default shell.
980 #
981 # mkShell and not mkShellNoCC, unlike every other shell here: this is
982 # the one that actually compiles C++. stdenv brings the compiler, and
983 # gtk3 in buildInputs is what puts its .pc file where the runner's
984 # `pkg_check_modules(GTK gtk+-3.0)` can find it.
985 flutter-desktop = pkgs.mkShell {
986 name = "frq-flutter-desktop";
987
988 # clojure and flutter are the APK shell's, and deliberately the
989 # same two: the Dart that runs here is generated by the same
990 # compiler from the same source, and a second Flutter version
991 # under it would be a second set of engine artifacts and a second
992 # answer to "does the phone build match the desktop one".
993 nativeBuildInputs = [
994 pkgs.clojure
995 pkgs.flutter
996 pkgs.just
997 pkgs.cmake
998 pkgs.ninja
999 pkgs.pkg-config
1000 ];
1001
1002 # gtk3 is the runner's own dependency; the rest are url_launcher's
1003 # Linux implementation, which is a GTK plugin compiled into the
1004 # bundle. path_provider needs nothing here — its Linux half is
1005 # pure Dart over the XDG directories.
1006 buildInputs = [ pkgs.gtk3 pkgs.glib ];
1007
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago1008 # Same reason `just cosmic run` reaches for it: Flutter paints
1009 # through GL, and off NixOS the driver that can do that is the
1010 # host's, not the store's. The recipe reads this exactly as
1011 # `cosmic` does.
Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago1012 NIXGL = "${nixGLFor pkgs}/bin/nixGLIntel";
1013
Seed the ClojureDart caches out of the flake f2e12af nandi 7d ago1014 # The `flutter` shell's, deliberately the same one and for the
1015 # same reason clojure and flutter are: the ClojureDart half of
1016 # both builds is one compile over one deps.edn, so a second set of
1017 # caches would be a second answer to what it resolved against.
1018 # This recipe reads the variable directly — it is inside this
1019 # shell before it does any of the work — where `just apk` reaches
1020 # for the flake output itself.
1021 FRQ_CLJD_DEPS = "${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}";
1022
Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago1023 # The recipe's re-entry test, the way JOLT_NATIVE_LIB is the
1024 # default shell's. Nothing else sets it, so `just flutter-desktop`
1025 # outside the shell re-enters and lands back on the same recipe —
1026 # no flag to forget, and no second code path for someone who runs
1027 # `nix develop .#flutter-desktop --command just flutter-desktop`
1028 # by hand.
1029 FRQ_FLUTTER_DESKTOP = "1";
1030 };
A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago1031
1032 # The third frontend, and the first that is not a window: the same
Three tarballs where a devShell was 5ce66d5 nandi 23h ago1033 # No `flutter-web` shell here any more. The web target was the one
1034 # that needed nothing of the host -- no JDK and no Android SDK as
1035 # the APK wants, no GTK and no C++ and no nixGL as the desktop one
1036 # does -- and a devShell whose only job is to hand over a Dart and
1037 # a JVM is a devShell that a pinned tarball can replace. It did:
1038 # `tools/toolchain.sh` fetches Flutter, a JDK and the Clojure CLI by
1039 # sha256, `tools/build-web.sh` builds out of them, and
1040 # `.modal/flutter-web/` runs that same script on a plain Debian
1041 # image with no store to populate.
A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago1042 #
Three tarballs where a devShell was 5ce66d5 nandi 23h ago1043 # The other two shells stay. What they supply is a host toolchain,
1044 # which is exactly what nix is better at than a tarball.
Run this tree on a native half the builders made a32699e nandi 17d ago1045 });
1046
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago1047 apps = forEachSystem (pkgs: {
1048 default = {
1049 type = "app";
1050 program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.frq}/bin/frq";
1051 };
Paint the same screens into a terminal ab83b42 nandi 17d ago1052 tui = {
1053 type = "app";
1054 program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.tui}/bin/frq-tui";
1055 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago1056 });
1057 };
1058}