nandi/frqpublic Fork 0
9dd480bc7a38e31d11a362f3f962a08b73983027
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 · 670 lines · 33.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 #
31 # The fork rather than jolt-lang/jolt: it is what jolt-android-src already
32 # pins for the boot image, and a desktop runtime built from a different
33 # tree than the APK's is the same drift the jolt-native comment warns
34 # about. Unpinned here — the desktop follows the fork's main, while the
35 # APK stays on the rev below.
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago36 jolt-src = {
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago37 url = "git+https://gitlab.com/nandithebull/jolt?submodules=1";
38 inputs.nixpkgs.follows = "nixpkgs";
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago39 };
40
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago41 # The source half of jolt-native: the Jolt code under glimmer-backends/ that
42 # binds the native objects, and the flake that builds the desktop ones. The
43 # Android objects no longer come from here — jolt-native-android below
44 # fetches those prebuilt — so this input is what `just run` builds against
45 # and what an APK's Clojure side is read from, both at this rev.
Take the jolt-native ahead of the release, since a run can build it 36f6c77 nandi 17d ago46 #
Pin jolt-native at the merge rather than at the branch 3d7c428 nandi 9d ago47 # It carries jvui and glimmer-jvui — the toolkit the window is painted
48 # with now — and, from no-moq-deps, the dependency split and the
49 # JOLT_WITHOUT_MOQ guard on the Android glue. Both were on the
50 # `jvui-for-frq` branch while they were being written and are merged into
51 # main now, which is why this names a rev on main again.
Stop building the media plane frq no longer loads 231b9d3 nandi 9d ago52 #
Pin jolt-native at the merge rather than at the branch 3d7c428 nandi 9d ago53 # 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 ago54 # unpinned `main` is a build whose native half is free to sit at a
55 # different commit from the tree that talks to it. It did, and what the
56 # drift cost was silence: the Jolt half sent a reaction pill's hover card
57 # 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 ago58 # It also carries the terminal backend — crates/jolt-tui, the same tree ABI
59 # over a grid of cells, and jolt/glimmer-tui beside glimmer-vidya. That was
60 # a second input at a second rev while it lived on a branch, which is the
61 # drift this comment warns about wearing a different hat: one input, and
62 # 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 ago63 jolt-native = {
Take the jolt-native that jumps to a message and walks only what shows bf7ef3e nandi 8d ago64 url = "git+https://gitlab.com/nandithebull/jolt-native?rev=b48e404b7a731fdd2d303624a0681b7cfcbb93fa";
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago65 inputs.nixpkgs.follows = "nixpkgs";
Paint the same screens into a terminal ab83b42 nandi 17d ago66 };
67
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago68 # The Android objects, prebuilt by jolt-native's CI rather than compiled
69 # here: an APK needs libvidya and libjoltmoq for arm64, and building them
70 # locally means an NDK, a Rust cross toolchain and the whole crane graph
71 # for two files that upstream already built and published.
72 #
73 # "latest" is the version its CI overwrites on every default-branch build,
74 # so this input finds a new one on `nix flake update` -- but flake.lock
75 # still records exactly which bytes an APK was built from, which is the
76 # pin that matters. `just bump` decides when to move; this only decides
77 # where to look. The archive is rooted at include/ and lib/arm64-v8a/, so
78 # nothing here has to unpack it.
79 jolt-native-android = {
80 url = "https://gitlab.com/api/v4/projects/nandithebull%2Fjolt-native/packages/generic/jolt-native/latest/android-arm64-v8a.tar.gz";
81 flake = false;
82 };
83
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago84 # Chez itself, because the APK needs a cross target nixpkgs does not
85 # build: frq's Scheme is compiled to an arm64 boot image, and that wants
86 # Chez's own `tarm64le` workarea — boot files, xpatch and libkernel.a.
87 # The version is the one the hand-built tree under ~/.cache used, and the
88 # submodules are not optional (zuo builds it, lz4 and zlib link into it).
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago89 # The same fork jolt-src takes, built here rather than fetched as a
90 # release binary: upstream reads the socket address out of `struct
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago91 # addrinfo` at glibc's offset, which on Bionic is `ai_canonname`, so an APK
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago92 # built with upstream cannot open a TLS connection at all. Pinned to a rev
93 # where jolt-src is not: the APK is a release artefact, so its runtime
94 # moves when `just bump` says so rather than when the fork does.
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago95 jolt-android-src = {
96 url = "git+https://gitlab.com/nandithebull/jolt?rev=2b80d68d1f7a31ba92b208b3957e5fb555617ada&submodules=1";
Take jolt and jolt-native as flakes, off my own fork aaa560d nandi 13d ago97 inputs.nixpkgs.follows = "nixpkgs";
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago98 };
99
100 chez-src = {
101 url = "git+https://github.com/cisco/ChezScheme?ref=refs/tags/v10.4.1&submodules=1";
102 flake = false;
103 };
104
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago105 # The sha deps.edn pins, on the fork with the reconciler fixes.
106 glimmer = {
107 url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";
108 flake = false;
109 };
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago110
111 # Only ever used off NixOS, to put the host GL driver on the loader path.
112 nixgl = {
113 url = "github:nix-community/nixGL";
114 inputs.nixpkgs.follows = "nixpkgs";
115 };
Bundle the closure into one file the AppImage runtime mounts f82b93f nandi 18d ago116
117 # Wraps a closure into a single self-extracting file. Only the `appimage`
118 # output evaluates it.
119 nix-appimage = {
120 url = "github:ralismark/nix-appimage";
121 inputs.nixpkgs.follows = "nixpkgs";
122 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago123 };
124
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago125 outputs = { self, nixpkgs, jolt-src, jolt-native, jolt-native-android, glimmer, chez-src, jolt-android-src, nixgl, nix-appimage }:
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago126 let
127 systems = [ "x86_64-linux" "aarch64-linux" ];
128 forEachSystem = f:
129 nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
Run this tree on a native half the builders made a32699e nandi 17d ago130
131 # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA
132 # wrappers are the ones that need --impure (they read the host kernel
133 # 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 ago134 #
135 # Built from nixGL's default.nix rather than taken from its flake
136 # outputs, for the one argument the flake hardcodes on: `enable32bits`,
137 # which on x86_64 puts a second, i686 copy of mesa, its LLVM, and
138 # intel-media-driver into the wrapper. frq is 64-bit on both halves —
139 # the Rust cdylibs and the Chez runtime — so nothing here ever opens the
140 # 32-bit driver, and carrying it is most of the dev shell's closure.
141 nixGLFor = pkgs: (import nixgl {
142 inherit pkgs;
143 enable32bits = false;
144 }).nixGLIntel;
Run this tree on a native half the builders made a32699e nandi 17d ago145
146 # egui reaches for these with dlopen rather than linking them, so being
147 # 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 ago148 # 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 ago149 # "X11 unavailable", falls back to Wayland, and winit refuses to build a
150 # second event loop after the failed first one.
151 #
152 # Out here rather than beside the package that first needed them: the
153 # dev shell starts frq too, on this tree's source rather than the store's
154 # copy of it, and a second copy of this list is a second chance for the
155 # two ways of running to disagree about what the window needs.
156 runtimeLibsFor = pkgs: with pkgs; [
157 libGL
158 libxkbcommon
159 wayland
Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 16d ago160 libx11
161 libxcursor
162 libxi
163 libxrandr
Run this tree on a native half the builders made a32699e nandi 17d ago164 vulkan-loader
165 ];
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago166 in
167 {
168 packages = forEachSystem (pkgs:
169 let
170 inherit (pkgs) lib;
171
Run this tree on a native half the builders made a32699e nandi 17d ago172 nixGL = nixGLFor pkgs;
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago173
Take the native objects from their own flake rather than rebuilding them 0ee8bba nandi 13d ago174 # libvidya (the retained-tree ABI glimmer-vidya binds, on egui),
175 # libjolttui (the same tree over a grid of cells) and libjoltmoq (the
176 # AV media plane) — one workspace, three cdylibs, taken from
177 # jolt-native's own flake rather than rebuilt here.
178 #
179 # This used to be a rustPlatform.buildRustPackage over the same
180 # source, which meant restating upstream's build: the seven git deps
181 # hashed by hand in `cargoLock.outputHashes` and re-hashed whenever
182 # its Cargo.lock moved, the linuxHeaders path v4l2r's bindgen wants,
183 # and a postPatch dropping the .cargo/config.toml that pointed the
184 # build at DotSlash. Upstream's flake says all of that itself now,
185 # and says it once. It also builds cpal with the `pipewire` feature,
186 # which the restatement did not — so device names in a call are
187 # PipeWire's rather than raw ALSA PCMs.
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago188 # libjolttui only. Not libjoltmoq, whose job `frq.av.plane` does
189 # now, and no longer libvidya either: the window is jvui on SDL,
190 # so the only object left out of that Cargo workspace is the
191 # terminal backend, and only `just tui` loads it.
Switch frq.av onto the jolt media plane, and stop shipping libjoltmoq b33f288 nandi 9d ago192 #
193 # This makes the closure smaller and the APK smaller. It does NOT
194 # make the build shorter, and it is worth being exact about why:
195 # jolt-native compiles its external crates ONCE, in a
196 # `buildDepsOnly` derivation shared by all three objects, so
197 # asking for two of them still builds every dependency the third
198 # has — the 440 crates that are jolt-moq's alone. Getting those
199 # out of the build is a change in jolt-native, not here: either
200 # jolt-moq leaves that workspace, or its deps artifact stops
201 # being workspace-wide.
202 native =
203 let np = jolt-native.packages.${pkgs.stdenv.hostPlatform.system};
204 in pkgs.symlinkJoin {
205 name = "jolt-native-ui";
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago206 paths = [ np.libjolttui ];
Switch frq.av onto the jolt media plane, and stop shipping libjoltmoq b33f288 nandi 9d ago207 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago208
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago209 # libmoq_ffi — MoQ over QUIC behind UniFFI's C ABI, FETCHED rather
210 # 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 ago211 #
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago212 # Fetched because building it is the thing this whole exercise is
213 # about: moq-ffi pulls moq-native, iroh, quinn, rustls and aws-lc-sys
214 # behind it, which is 440 crates that nothing else in this tree
215 # needs. Upstream already publishes the object for both Linux
216 # architectures, so we take those bytes.
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago217 #
Fetch libmoq_ffi again, and keep the codecs out of Rust f518938 nandi 9d ago218 # Pinned to a release and to a hash, and the hashes below are
219 # upstream's own published .sha256 files rather than ones observed
220 # here — a `nix-prefetch` of whatever the URL serves today would
221 # record that it downloaded something, not that it downloaded the
222 # right thing.
223 #
224 # WHAT THIS BUILD IS NOT: moq-ffi's `audio` and `video` features are
225 # on by default upstream and are OFF in these artifacts, so there is
226 # no publish_audio/publish_video and no moqaudio*/moqvideo* here —
227 # 206 functions where the Apple artifact has 230. That is why the
228 # bindings are generated from the object (`just gen-moq`) and not
229 # from the C header the release ships, which describes the Apple one.
230 moqFfi =
231 let
232 version = "0.3.17";
233 target = {
234 "x86_64-linux" = "x86_64-unknown-linux-gnu";
235 "aarch64-linux" = "aarch64-unknown-linux-gnu";
236 }.${pkgs.stdenv.hostPlatform.system};
237 hash = {
238 "x86_64-linux" = "sha256-dzQXpV4JgdtD+g33WX51FFAQdfCUXkNsx1xPbobPfUI=";
239 "aarch64-linux" = "sha256-PdzRwbJFqOZWRgI0HHX2XUH+Ljh4V3jvQ9asfvCuIPA=";
240 }.${pkgs.stdenv.hostPlatform.system};
241 in
242 pkgs.stdenv.mkDerivation {
243 pname = "libmoq-ffi";
244 inherit version;
245 src = pkgs.fetchurl {
246 url = "https://github.com/kixelated/moq/releases/download/moq-ffi-v${version}/moq-ffi-${version}-${target}-libmoq_ffi.so";
247 inherit hash;
248 };
249 dontUnpack = true;
250 # It carries no RUNPATH and needs libgcc_s, libm and libc — the
251 # host's on an ordinary distro, and nothing at all on NixOS
252 # unless they are bound here.
253 nativeBuildInputs = [ pkgs.autoPatchelfHook ];
254 buildInputs = [ pkgs.stdenv.cc.cc.lib ];
255 installPhase = ''
256 mkdir -p $out/lib
257 cp $src $out/lib/libmoq_ffi.so
258 chmod +w $out/lib/libmoq_ffi.so
259 '';
260 };
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago261
262 # One directory for the loader to look in. jolt resolves every
263 # :jolt/native name against JOLT_NATIVE_LIB, and the objects now come
264 # from two places — jolt-native's flake, and the moq-ffi release — so
265 # they are joined rather than the path being made a list, which the
266 # loader does not take.
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago267 # The C codecs, from nixpkgs. libmoq_ffi carries the transport and
268 # nothing else — moq-ffi's `audio` and `video` features would have
269 # brought Opus and H.264 with them, at the price of compiling a
270 # 1062-crate workspace — so the codecs are linked here instead,
271 # where they have always lived.
272 #
273 # Named in :jolt/native, so the loader resolves them the same way it
274 # resolves libvidya: by name, out of one directory.
275 # A flat C face for openh264, because openh264 has none. Its
276 # `ISVCEncoder` is `const ISVCEncoderVtbl*` — every method is a
277 # function pointer in a vtable — and jolt.ffi cannot call one: Chez
278 # fixes a foreign procedure's types when it compiles it, and the
279 # target must be a literal C symbol name. So the vtable is walked in
280 # c/frq_h264.c and jolt binds the five plain symbols it exports.
281 #
282 # One translation unit against a library nixpkgs already has. It is
283 # a calling convention adapter, not a second media plane, and the
284 # distinction from the moq-ffi build it replaces is the whole point:
285 # this compiles one .c file, not a 1062-crate workspace.
286 frqH264 = pkgs.stdenv.mkDerivation {
287 pname = "frq-h264";
288 version = "0.1";
289 src = ./c;
290 nativeBuildInputs = [ pkgs.pkg-config ];
291 buildInputs = [ pkgs.openh264 ];
292 buildPhase = ''
293 $CC -O2 -fPIC -shared frq_h264.c -o libfrqh264.so \
294 $(pkg-config --cflags --libs openh264)
295 '';
296 installPhase = ''
297 mkdir -p $out/lib && cp libfrqh264.so $out/lib/
298 '';
299 };
300
301 # openh264 is here for frqH264's DT_NEEDED; alsa-lib for capture
302 # and playback. V4L2 needs nothing: it is ioctls against libc and
303 # the kernel, so there is no library to name.
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago304 # SDL is what the UI is now: jvui declares SDL3, SDL3_ttf and
305 # SDL3_image in its own :jolt/native and dlopens them by soname,
306 # so they have to be somewhere the loader looks. sdl3-image keeps
307 # its library in a separate `lib` output — the default one holds
308 # only share/, which is an afternoon nobody needs to repeat.
309 sdl = [ pkgs.sdl3 pkgs.sdl3-ttf (pkgs.sdl3-image.lib or pkgs.sdl3-image) ];
310
311 codecs = [ pkgs.libopus pkgs.openh264 frqH264 pkgs.alsa-lib ] ++ sdl;
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago312
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago313 # ALSA's PipeWire plugin, which is how `default` resolves to
314 # anything on a machine running PipeWire — and every machine frq
315 # targets does. Without it alsa-lib fails to dlopen
316 # libasound_module_pcm_pipewire.so and the only devices that open
317 # are raw hardware ones, which PipeWire is already holding.
318 #
319 # An environment variable rather than a library in the join:
320 # alsa-lib looks plugins up by directory, not by soname.
321 alsaPluginDir = "${pkgs.pipewire}/lib/alsa-lib";
322
Ship the faces jvui draws with 91302db nandi 9d ago323 # The faces jvui draws with, from here rather than from whatever
324 # the host happens to have installed. jvui hunts a list of the
325 # usual system paths and, failing that, draws the missing-glyph
326 # box — which is what "the glyphs are broke" has been every time
327 # it has come up. An Arch container has NotoColorEmoji and no
328 # monochrome Noto Emoji, and NotoColorEmoji is a bitmap face with
329 # one 128-pixel strike that jvui rejects on purpose, so the chips
330 # in a message row had nothing left to be drawn from.
331 #
332 # NotoEmoji is the outline companion to NotoColorEmoji: scalable,
333 # monochrome, and full coverage of the emoji the chrome uses.
334 # Symbols2 behind it for the arrows and technical marks that are
335 # not emoji at all.
336 uiFont = "${pkgs.noto-fonts}/share/fonts/noto/NotoSans.ttf";
337 fallbackFonts = lib.concatStringsSep ":" [
338 "${pkgs.noto-fonts-monochrome-emoji}/share/fonts/noto/NotoEmoji.ttf"
339 "${pkgs.noto-fonts}/share/fonts/noto/NotoSansSymbols2-Regular.otf"
340 "${pkgs.noto-fonts}/share/fonts/noto/NotoSansSymbols.ttf"
341 ];
342
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago343 nativeAll = pkgs.symlinkJoin {
344 name = "frq-native";
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago345 paths = [ native moqFfi ] ++ codecs;
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago346 };
347
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago348 # Jolt itself: Clojure on Chez, built the way its own flake builds it.
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago349 # A function, because there are two of them — upstream for the
350 # desktop, and the Bionic-addrinfo fork for the boot image the APK
351 # carries. Nothing else about the build differs.
352 joltFrom = src: pkgs.stdenv.mkDerivation {
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago353 pname = "jolt";
354 version = "dev";
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago355 inherit src;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago356
357 strictDeps = true;
358 nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];
359 buildInputs = with pkgs; [ lz4 zlib ncurses openssl libuuid ];
360
361 JOLT_VERSION = "dev";
362 dontConfigure = true;
363
364 buildPhase = ''
365 runHook preBuild
366 scheme --script host/chez/build-jolt.ss release target/release/jolt
367 runHook postBuild
368 '';
369
370 installPhase = ''
371 runHook preInstall
372 mkdir -p "$out/bin"
373 install -m755 target/release/jolt "$out/bin/jolt"
374 runHook postInstall
375 '';
376
377 # 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 ago378 # OpenSSL through the JOLT_OPENSSL_LIBDIR seam. gitMinimal rather
379 # than git: all jolt.deps asks for is clone/fetch/rev-parse, and
380 # the full package carries Perl and Python for the subcommands
381 # 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 ago382 #
383 # TZDIR so a zone *name* resolves wherever this runs: frq.clock
384 # hands one to tzset, and glibc then looks for the tzfile under
385 # /usr/share/zoneinfo unless told otherwise — which a NixOS host
386 # does not have. The store's own tzdata is there on both kinds of
387 # 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 ago388 postFixup = ''
389 wrapProgram "$out/bin/jolt" \
Stop carrying the 32-bit GL stack and half of git 040f2c1 nandi 16d ago390 --prefix PATH : "${pkgs.lib.makeBinPath [ pkgs.gitMinimal pkgs.unzip ]}" \
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago391 --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 ago392 --set-default TZDIR "${pkgs.tzdata}/share/zoneinfo" \
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago393 --set-default SSL_CERT_FILE "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
394 '';
395 };
396
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago397 joltRuntime = joltFrom jolt-src;
398 joltAndroid = joltFrom jolt-android-src;
399
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago400 # glimmer-vidya lives inside the jolt-native checkout, and its own
401 # deps.edn asks for glimmer by git — the top-level override below
402 # answers for both.
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago403 # glimmer-jvui and the toolkit it is a backend for. TWO paths and
404 # not one: glimmer-jvui's own deps.edn names jvui by :local/root,
405 # a relative path that means nothing once nix has copied the
406 # subtree, so the -Sdeps below has to name both.
407 glimmerJvui = "${jolt-native}/glimmer-backends/glimmer-jvui";
408 jvui = "${jolt-native}/jvui";
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago409 glimmerTui = "${jolt-native}/glimmer-backends/glimmer-tui";
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago410
Run this tree on a native half the builders made a32699e nandi 17d ago411 runtimeLibs = runtimeLibsFor pkgs;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago412
413 # The project as jolt sees it: source, deps.edn, nothing else.
414 frqSource = pkgs.runCommand "frq-source" { } ''
415 mkdir -p "$out"
416 cp -r ${self}/src ${self}/deps.edn "$out/"
417 '';
418
419 # Jolt resolves deps.edn from the working directory, so the launcher
420 # runs from the store copy. Its .jolt/cpcache write lands on a
421 # read-only directory and jolt treats that as a quiet cache miss, so
422 # the only cost is re-resolving the (already local) graph per start.
423 frqScript = pkgs.writeShellScript "frq" ''
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago424 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 ago425 export ALSA_PLUGIN_DIR="${alsaPluginDir}"
Ship the faces jvui draws with 91302db nandi 9d ago426 export JVUI_FONT="${uiFont}"
427 export JVUI_FALLBACK_FONTS="${fallbackFonts}"
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago428 cd ${frqSource}
Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago429
430 # On NixOS the store's Mesa is the system's and the window opens.
431 # Anywhere else the real driver is the host's, so defer to nixGL
432 # it prepends the host driver, which has to win over ours.
433 runner=""
434 [ -e /run/current-system ] || runner="${nixGL}/bin/nixGLIntel"
435
436 exec ''${runner} ${joltRuntime}/bin/jolt \
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago437 -Sdeps '{:deps {jolt-lang/glimmer {:local/root "${glimmer}"} nandi/glimmer-jvui {:local/root "${glimmerJvui}"} jvui/jvui {:local/root "${jvui}"}}}' \
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago438 -M:frq "$@"
439 '';
440
Paint the same screens into a terminal ab83b42 nandi 17d ago441 # The same source, the other backend. No GL, no nixGL and no X11 —
442 # a terminal is the one surface that needs nothing from the host but
443 # a terminal, which is the reason this output exists.
444 tuiScript = pkgs.writeShellScript "frq-tui" ''
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago445 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 ago446 export ALSA_PLUGIN_DIR="${alsaPluginDir}"
Paint the same screens into a terminal ab83b42 nandi 17d ago447 cd ${frqSource}
448
449 exec ${joltRuntime}/bin/jolt \
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago450 -Sdeps '{:deps {jolt-lang/glimmer {:local/root "${glimmer}"} nandi/glimmer-jvui {:local/root "${glimmerJvui}"} jvui/jvui {:local/root "${jvui}"} nandi/glimmer-tui {:local/root "${glimmerTui}"}}}' \
Paint the same screens into a terminal ab83b42 nandi 17d ago451 -m frq.tui "$@"
452 '';
453
454 tui = pkgs.runCommand "frq-tui-0.1.0"
455 {
456 meta = {
457 description = "frq's screens in a terminal";
458 mainProgram = "frq-tui";
459 platforms = systems;
460 };
461 }
462 ''
463 mkdir -p "$out/bin"
464 ln -s ${tuiScript} "$out/bin/frq-tui"
465 '';
466
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago467 frq = pkgs.runCommand "frq-0.1.0"
468 {
469 meta = {
470 description = "A freeq client in jolt";
471 mainProgram = "frq";
472 platforms = systems;
473 };
474 }
475 ''
476 mkdir -p "$out/bin"
477 ln -s ${frqScript} "$out/bin/frq"
478 '';
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago479 # --- Android ------------------------------------------------------
480 # The SDK and the NDK are Google's, which means unfree and a licence
481 # to accept — so this is its own import of nixpkgs rather than the
482 # `legacyPackages` everything above uses. Confined to the Android
483 # outputs: `nix build` of frq itself never evaluates it.
484 #
Build the APK as derivations only, and retire the buck2 graph 3bc3aaa nandi 16d ago485 # The NDK here is r29, which is the one the pinned libvidya was
486 # built with.
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago487 androidPkgs = import nixpkgs {
488 inherit (pkgs.stdenv.hostPlatform) system;
489 config = {
490 allowUnfree = true;
491 android_sdk.accept_license = true;
492 };
493 };
494
495 androidComposition = androidPkgs.androidenv.composeAndroidPackages {
496 buildToolsVersions = [ "36.0.0" ];
497 platformVersions = [ "36" ];
498 includeNDK = true;
499 };
500
501 android = import ./nix/android.nix {
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago502 inherit pkgs self chez-src jolt-native jolt-native-android glimmer joltAndroid;
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago503 inherit (pkgs) lib;
504 androidSdk = androidComposition.androidsdk;
505 ndk = androidComposition.ndk-bundle;
506 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago507 in
508 {
Bind the C libraries a call actually needs 87e5be9 nandi 9d ago509 inherit native moqFfi frqH264 nativeAll frq;
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago510 inherit (pkgs) pipewire;
Merge origin/main, and let one jolt-native answer for both backends 1e8b590 nandi 16d ago511 inherit tui;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago512 jolt = joltRuntime;
513 default = frq;
Bundle the closure into one file the AppImage runtime mounts f82b93f nandi 18d ago514
515 # frq and everything it loads, squashed into one runnable file for
516 # hosts without Nix. The whole closure rides along — Mesa included,
517 # which is not waste: off NixOS the launcher goes through nixGL, and
518 # nixGL needs a store Mesa to put the host's driver in front of.
519 appimage =
520 nix-appimage.bundlers.${pkgs.stdenv.hostPlatform.system}.default frq;
Build the APK from the flake, and from nothing 6aa2a6b nandi 18d ago521 }
522 # An APK is built by a linux-x86_64 NDK and a linux-x86_64 jolt, and
523 # Google ships no other; on aarch64 the Android outputs are simply
524 # absent rather than present and broken.
525 // lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
526 inherit (android) apk chezAndroid joltBoot libjoltapp;
527 apk-unsigned = android.apk-unsigned;
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago528 });
529
Run this tree on a native half the builders made a32699e nandi 17d ago530 # Where `just run` runs, and — because entering it realises what it
531 # names — what builds the half of frq that is not this working tree.
532 #
533 # The two halves, and the split is the whole point of the shell. The frq
534 # source is the files on disk, uncommitted edits and all. Everything
535 # under it — jolt, glimmer, glimmer-vidya, both native objects — is the
536 # flake's, at the revs flake.lock names, so a run says what it ran
537 # against and both halves of glimmer-vidya move together. That is the
538 # drift the `jolt-native` input's comment is about, and a pin frq can
539 # answer for is worth more here than the convenience of a checkout.
540 #
Take the native objects from their own flake rather than rebuilding them 0ee8bba nandi 13d ago541 # `native` is jolt-native's own flake output. It was a buck2 graph when
542 # this comment was first written and a cargo build restated here when it
543 # was second: buck2 fetches its rustc, zig and every third-party crate as
544 # it goes and writes buck-out into the tree it builds, so a sandbox with
545 # no network and a read-only store was the one place it could not run.
546 # Upstream builds with nix now, so the thing its CI runs and the thing
547 # this shell hands a builder are the same derivation.
Run this tree on a native half the builders made a32699e nandi 17d ago548 #
549 # Nothing here says "nixbuild", though: it is a plain derivation, and
Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago550 # where it gets built is the machine's business. The `run` recipe asks for
Run this tree on a native half the builders made a32699e nandi 17d ago551 # the shell with --max-jobs 0, which is what sends it to the `builders`
552 # entry rather than compiling egui on a laptop.
553 devShells = forEachSystem (pkgs:
554 let
555 inherit (pkgs) lib;
Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago556 inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) jolt native nativeAll;
Run this tree on a native half the builders made a32699e nandi 17d ago557 in
558 {
559 default = pkgs.mkShellNoCC {
560 name = "frq";
561
562 # jolt, because the runtime frq is run by should be the flake's
563 # too. nixGL for the same reason the launcher reaches for it — see
Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago564 # frqScript. just so the recipe runner comes from here too rather
565 # than the host — `nix develop` and then `just run` is the whole of
566 # what a machine with nix needs.
567 packages = [ jolt pkgs.just (nixGLFor pkgs) ];
568
569 # Read by the recipes rather than baked into a wrapper: the frq
Run this tree on a native half the builders made a32699e nandi 17d ago570 # source `just run` runs is the working tree, so the launcher has
Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago571 # to live in that tree and the shell has to hand it its answers.
572 # 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 ago573 JOLT_NATIVE_LIB = "${nativeAll}/lib";
Let the plane take real devices, not only test thunks 596dc29 nandi 9d ago574 # Spelled out rather than shared with the packages block, which
575 # is a different `let`. See `alsaPluginDir` there for why.
576 ALSA_PLUGIN_DIR = "${pkgs.pipewire}/lib/alsa-lib";
Run this tree on a native half the builders made a32699e nandi 17d ago577 GLIMMER_SRC = glimmer;
Paint frq with jvui, and take the last Rust out of the window 77963dc nandi 9d ago578 GLIMMER_JVUI_SRC = "${jolt-native}/glimmer-backends/glimmer-jvui";
579 JVUI_SRC = "${jolt-native}/jvui";
Take the Android objects from jolt-native's CI, always the latest 472e179 nandi 11d ago580 GLIMMER_TUI_SRC = "${jolt-native}/glimmer-backends/glimmer-tui";
Ship the faces jvui draws with 91302db nandi 9d ago581 # See `uiFont` in the packages block for why these are named
582 # here rather than left to whatever the host has installed.
583 # Spelled out again for the same reason ALSA_PLUGIN_DIR is: a
584 # different `let`.
585 JVUI_FONT = "${pkgs.noto-fonts}/share/fonts/noto/NotoSans.ttf";
586 JVUI_FALLBACK_FONTS = lib.concatStringsSep ":" [
587 "${pkgs.noto-fonts-monochrome-emoji}/share/fonts/noto/NotoEmoji.ttf"
588 "${pkgs.noto-fonts}/share/fonts/noto/NotoSansSymbols2-Regular.otf"
589 "${pkgs.noto-fonts}/share/fonts/noto/NotoSansSymbols.ttf"
590 ];
Run this tree on a native half the builders made a32699e nandi 17d ago591 FRQ_LIB_PATH = lib.makeLibraryPath (runtimeLibsFor pkgs);
592 NIXGL = "${nixGLFor pkgs}/bin/nixGLIntel";
Take the jolt-native beside this one when there is one e10e1c1 nandi 9d ago593
594 # A checkout of jolt-native beside this one, in place of the pin.
595 #
596 # The pin is a rev on a server, so the loop for a change to the
597 # terminal backend was commit, push, re-pin, re-lock — four steps
598 # and an upload for a line of Rust. With a working copy beside this
599 # one the loop is `cargo build` and `just tui`, and the shell finds
600 # that copy itself: ../jolt-native from the checkout this was run
601 # in, which is where it is on the machines this is developed on.
602 # A worktree under .claude/worktrees counts as the same checkout —
603 # the sibling is the main one's, not the worktree's.
604 #
605 # Found rather than named, but not silently: it says which tree it
606 # took on the way in, because `just tui` running something other
607 # than the pin is the sort of thing you have to be able to see.
608 #
609 # It has to be a built one. A checkout with no target/release/
610 # libjolttui.so in it would mean the Jolt half of the backend from
611 # the working copy and the shared object from the pin — two halves
612 # of two different libraries, which fail in ways that look like
613 # neither. So an unbuilt sibling is left alone and the pin stands.
614 #
615 # FRQ_JOLT_NATIVE overrides the search, and is taken even unbuilt
616 # (with a word about what to run): naming a tree is asking for it.
617 # Empty is how you say the pin, on a machine that has a sibling and
618 # wants what everyone else is running.
619 #
620 # Only the sources and libjolttui move either way. Everything else
621 # on the library path — libopus, libmoq_ffi, the ALSA plugins —
622 # stays the pin's, since a checkout has no build of those to offer.
623 shellHook = ''
624 frq_named=1
625 if [ -z "''${FRQ_JOLT_NATIVE+named}" ]; then
626 frq_named=
627 frq_git="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)"
628 frq_near="''${frq_git:+$(dirname "$(dirname "$frq_git")")/jolt-native}"
629 if [ -n "$frq_near" ] && [ -e "$frq_near/target/release/libjolttui.so" ]; then
630 FRQ_JOLT_NATIVE="$frq_near"
631 else
632 FRQ_JOLT_NATIVE=""
633 fi
634 fi
635 if [ -n "$FRQ_JOLT_NATIVE" ]; then
636 if [ -d "$FRQ_JOLT_NATIVE/crates/jolt-tui" ]; then
637 FRQ_JOLT_NATIVE="$(cd "$FRQ_JOLT_NATIVE" && pwd)"
638 export FRQ_JOLT_NATIVE
639 export GLIMMER_TUI_SRC="$FRQ_JOLT_NATIVE/glimmer-backends/glimmer-tui"
640 export GLIMMER_JVUI_SRC="$FRQ_JOLT_NATIVE/glimmer-backends/glimmer-jvui"
641 export JVUI_SRC="$FRQ_JOLT_NATIVE/jvui"
642 # First, so a cargo build wins over the pin's copy of the
643 # same object. The rest of the pin's lib directory is still
644 # behind it.
645 export JOLT_NATIVE_LIB="$FRQ_JOLT_NATIVE/target/release:$JOLT_NATIVE_LIB"
646 echo "frq: jolt-native from $FRQ_JOLT_NATIVE, not the pin (FRQ_JOLT_NATIVE= for the pin)" >&2
647 if [ ! -e "$FRQ_JOLT_NATIVE/target/release/libjolttui.so" ]; then
648 echo "frq: no libjolttui.so there yet cargo build --release --features terminal -p jolt-tui" >&2
649 fi
650 elif [ -n "$frq_named" ]; then
651 echo "frq: FRQ_JOLT_NATIVE=$FRQ_JOLT_NATIVE is not a jolt-native checkout; using the pin" >&2
652 fi
653 fi
654 unset frq_named frq_git frq_near
655 '';
Run this tree on a native half the builders made a32699e nandi 17d ago656 };
657 });
658
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago659 apps = forEachSystem (pkgs: {
660 default = {
661 type = "app";
662 program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.frq}/bin/frq";
663 };
Paint the same screens into a terminal ab83b42 nandi 17d ago664 tui = {
665 type = "app";
666 program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.tui}/bin/frq-tui";
667 };
Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago668 });
669 };
670}