nandi/jolt-nativepublic Fork 0
228672deff39ee418d1b734ed0920ce16985e8e1
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

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

flake.nix · 370 lines · 15.7 KBNix Blame HistoryRaw
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago1# The whole toolchain, as one closure.
2#
3# This replaces buck2 and DotSlash together, and for the same reason each of
4# them was here: a checkout should build on a machine with nothing installed.
5# DotSlash pinned five tools by digest and fetched them on first use; buck2 knew
6# how to run them. Nix pins the compiler, the C toolchain, the system libraries
7# *and* the crate graph in one lockfile, so the pinning and the running stop
8# being two problems.
9#
10# nix develop # the edit loop: cargo, just, everything below
11# nix build # all three desktop objects into result/lib
12# nix build .#android # the two device objects, cross-compiled
13# nix flake check # fmt, clippy, tests
14#
Publish the shared objects instead of dropping them b6001e6 nandi 11d ago15# Every push to main publishes the same two outputs to the GitLab package
16# registry as tarballs rooted at lib/ and include/, so another flake can link
17# against them without building this one:
18#
19# inputs.jolt-native-libs = {
20# url = "https://gitlab.example/api/v4/projects/<id>/packages/generic/jolt-native/<sha>/android-arm64-v8a.tar.gz";
21# flake = false; # a plain tarball, not a flake — nix unpacks it as-is
22# };
23# # then: ${jolt-native-libs}/lib/arm64-v8a/libjoltmoq.so
24#
25# The URL carries a commit sha and flake.lock pins the unpacked tree's narHash,
26# so the input is immutable from both ends; moving it is an edit plus a lock
27# update. There is deliberately no `latest` URL — a moving target under a
28# pinned hash is a lockfile that lies. x86_64-linux.tar.gz is published the
29# same way, but those objects carry a RUNPATH into the *builder's* /nix/store
30# and only resolve on a machine that holds those paths; desktop consumers
31# should take this repo as a flake input and build .#libs instead. The tarball
32# is for the Android side, which links nothing but the NDK sysroot.
33#
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago34# What went away with buck2: third-party/rust's reindeer-generated BUCK graph
35# (Cargo.lock is the one dependency graph now), the RBE container and its GitLab
36# job, and scripts/ entire — zcc, zxx, zig-include, the .dotslash manifests and
37# the generator that restated them for buck. The zig sysroot in particular was
38# only ever standing in for a system C toolchain; nix supplies a real one, which
39# is also why cpal's `pipewire` feature no longer has to be off.
40{
41 description = "jolt's native backends glimmer/egui, a terminal painter, and a MoQ media plane, each behind a C ABI";
42
43 inputs = {
44 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
45 rust-overlay = {
46 url = "github:oxalica/rust-overlay";
47 inputs.nixpkgs.follows = "nixpkgs";
48 };
49 crane.url = "github:ipetkov/crane";
50 flake-utils.url = "github:numtide/flake-utils";
51 };
52
53 outputs = { self, nixpkgs, rust-overlay, crane, flake-utils }:
54 flake-utils.lib.eachDefaultSystem (system:
55 let
56 pkgs = import nixpkgs {
57 inherit system;
58 overlays = [ (import rust-overlay) ];
59 # The NDK is unfree, and only the android outputs pull it in.
60 config.allowUnfree = true;
61 config.android_sdk.accept_license = true;
62 };
63
64 inherit (pkgs) lib;
65
66 # One toolchain for both directions. The Android std comes from the same
67 # rustc rather than a second pinned tarball, which is what
68 # scripts/rust-std-android.dotslash and the sysroot symlink farm in
69 # scripts/rustc existed to stitch together by hand.
70 rustToolchain = pkgs.rust-bin.stable.latest.default.override {
71 extensions = [ "rust-src" "clippy" "rustfmt" ];
72 targets = [ "aarch64-linux-android" ];
73 };
74
75 craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
76
77 # crane's own cleanCargoSource keeps .rs and the manifests and drops
78 # everything else — which here would drop the C headers each crate
79 # publishes and vidya-core's font and emoji assets, all of which are
80 # `include_bytes!`d or shipped beside the object. Keep them.
81 src = lib.cleanSourceWith {
82 src = ./.;
83 name = "jolt-native-source";
84 filter = path: type:
85 let rel = lib.removePrefix (toString ./. + "/") (toString path);
86 in
87 (craneLib.filterCargoSources path type)
88 || lib.hasInfix "/include/" rel
Move iroh-live forward and let the vendored cpal go 789bb13 nandi 11d ago89 || lib.hasInfix "/assets/" rel;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago90 };
91
92 # Built by a build script, linked into the objects, or opened by them at
93 # run time. Split out because the Android graph wants almost none of it.
94 desktopBuildInputs = with pkgs; [
95 alsa-lib # cpal's ALSA host
96 pipewire # cpal's `pipewire` feature, via libspa-sys
97 libGL # glow/glutin
98 libxkbcommon # winit
99 wayland
100 libx11
101 libxcursor
102 libxi
103 libxrandr
104 ];
105
106 nativeBuildInputs = with pkgs; [
107 pkg-config
108 # aws-lc-sys (under rustls and moq-native) configures with cmake and
109 # generates its assembly with perl and go.
110 cmake
111 ninja
112 perl
113 go
114 # Sets LIBCLANG_PATH and the clang resource-dir include for every
115 # bindgen build script in the graph — v4l2r, libspa-sys, aws-lc-sys.
116 rustPlatform.bindgenHook
117 ];
118
119 # v4l2r's build script wants linux/videodev2.h. On a machine with kernel
120 # headers installed that is /usr/include; on one without it is nowhere,
121 # which is the whole reason the justfile used to reach into zig's
122 # bundled copies. nixpkgs has them as a package.
123 v4l2Env = {
124 V4L2R_VIDEODEV2_H_PATH = "${pkgs.linuxHeaders}/include";
125 BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.linuxHeaders}/include";
126 };
127
128 commonArgs = {
129 inherit src;
130 strictDeps = true;
131 inherit nativeBuildInputs;
132 buildInputs = desktopBuildInputs;
133 } // v4l2Env;
134
Build each object's dependencies, not every object's 2975a43 nandi 8d ago135 # Every external crate, compiled once and shared by the objects that
136 # actually want it.
137 #
138 # This used to be ONE derivation for the whole workspace, and the
139 # sharing was the point: three objects, one dependency build. What it
140 # cost was invisible until a consumer wanted only some of them.
141 # jolt-moq brings moq-net, iroh, quinn, rustls and aws-lc-sys behind
142 # it -- 440 crates that nothing else here touches -- so a build of
143 # libvidya alone still paid for the media plane. A client that has
144 # stopped loading libjoltmoq paid for it too, which is the case that
145 # made this worth splitting.
146 #
147 # Split by CONSUMER rather than per package: vidya and tui share
148 # nearly everything, and giving them an artifact each would trade one
149 # kind of waste for another.
150 depsFor = { pname, packages }:
151 craneLib.buildDepsOnly (commonArgs // {
152 inherit pname;
153 version = "0.1.0";
154 cargoExtraArgs =
155 "--locked " + lib.concatMapStringsSep " " (p: "-p " + p) packages;
156 });
157
158 uiArtifacts = depsFor {
159 pname = "jolt-native-ui-deps";
160 packages = [ "vidya-ffi" "jolt-tui" ];
161 };
162 moqArtifacts = depsFor {
163 pname = "jolt-native-moq-deps";
164 packages = [ "jolt-moq" ];
165 };
Build libjoltcosmic out of the flake, so frq can link it 4706c92 nandi 7d ago166 # Its own artifact rather than a share of the UI one, for the reason
167 # crates/jolt-cosmic/Cargo.toml gives: libcosmic drags iced, wgpu,
168 # cosmic-text and zbus in behind it, and none of that belongs in the
169 # dependency build a terminal backend waits on.
170 cosmicArtifacts = depsFor {
171 pname = "jolt-native-cosmic-deps";
172 packages = [ "jolt-cosmic" ];
173 };
Build each object's dependencies, not every object's 2975a43 nandi 8d ago174
175 # The whole workspace, for the clippy and test runs — those do build
176 # everything, and want the sharing this split gives up.
Move iroh-live forward and let the vendored cpal go 789bb13 nandi 11d ago177 cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago178 pname = "jolt-native-deps";
179 version = "0.1.0";
180 });
181
182 # cargo does not install a cdylib, so crane's default install phase —
183 # `cargo install`, which only knows about binaries — has nothing to do.
184 # Take the objects out of the target directory instead.
Run the formatter over the tree 3e8c6f0 nandi 13d ago185 # `dir` is named separately because a crate's directory and its cargo
186 # package name are not the same thing here: vidya-ffi lives in
187 # crates/jolt-vidya. Nothing is silenced — a header that stops being
188 # there should fail the build rather than ship an object with no ABI
189 # beside it.
Build libjoltcosmic out of the flake, so frq can link it 4706c92 nandi 7d ago190 # `headers` is false for a backend that exports no C API of its own.
191 # jolt-cosmic is one: the jolt side reaches it through jolt.ffi by
192 # symbol name, so there is no header for an embedder to include and no
193 # crates/jolt-cosmic/include to copy. The others keep the check that
194 # a vanished header fails the build.
195 soPackage = { pname, package, dir, soname, artifacts, headers ? true }:
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago196 craneLib.buildPackage (commonArgs // {
Build each object's dependencies, not every object's 2975a43 nandi 8d ago197 inherit pname;
198 cargoArtifacts = artifacts;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago199 version = "0.1.0";
200 cargoExtraArgs = "--locked -p ${package}";
201 doCheck = false;
202 installPhaseCommand = ''
Build libjoltcosmic out of the flake, so frq can link it 4706c92 nandi 7d ago203 mkdir -p $out/lib
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago204 cp target/release/${soname} $out/lib/
Build libjoltcosmic out of the flake, so frq can link it 4706c92 nandi 7d ago205 '' + lib.optionalString headers ''
206 mkdir -p $out/include
Run the formatter over the tree 3e8c6f0 nandi 13d ago207 cp -r crates/${dir}/include/. $out/include/
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago208 '';
209 });
210
211 libvidya = soPackage {
Build each object's dependencies, not every object's 2975a43 nandi 8d ago212 artifacts = uiArtifacts;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago213 pname = "libvidya";
214 package = "vidya-ffi";
Run the formatter over the tree 3e8c6f0 nandi 13d ago215 dir = "jolt-vidya";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago216 soname = "libvidya.so";
217 };
218 libjolttui = soPackage {
Build each object's dependencies, not every object's 2975a43 nandi 8d ago219 artifacts = uiArtifacts;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago220 pname = "libjolttui";
221 package = "jolt-tui";
Run the formatter over the tree 3e8c6f0 nandi 13d ago222 dir = "jolt-tui";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago223 soname = "libjolttui.so";
224 };
225 libjoltmoq = soPackage {
Build each object's dependencies, not every object's 2975a43 nandi 8d ago226 artifacts = moqArtifacts;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago227 pname = "libjoltmoq";
228 package = "jolt-moq";
Run the formatter over the tree 3e8c6f0 nandi 13d ago229 dir = "jolt-moq";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago230 soname = "libjoltmoq.so";
231 };
Build libjoltcosmic out of the flake, so frq can link it 4706c92 nandi 7d ago232 libjoltcosmic = soPackage {
233 artifacts = cosmicArtifacts;
234 pname = "libjoltcosmic";
235 package = "jolt-cosmic";
236 dir = "jolt-cosmic";
237 soname = "libjoltcosmic.so";
238 headers = false;
239 };
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago240
241 # The phone. Same sources, same Cargo.lock, only the target
242 # configuration moves — which is what //:libs-android and its pair of
243 # configured_alias targets used to say. Build scripts and proc macros
244 # still compile for the host; cargo arranges that on its own.
245 androidApi = 28;
246 androidTarget = "aarch64-linux-android";
247
248 androidComposition = pkgs.androidenv.composeAndroidPackages {
249 includeNDK = true;
250 ndkVersions = [ "27.2.12479018" ];
251 platformVersions = [ "${toString androidApi}" ];
252 abiVersions = [ "arm64-v8a" ];
253 };
254 ndkRoot = "${androidComposition.androidsdk}/libexec/android-sdk/ndk-bundle";
255 ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
256
257 # The NDK's clang is the linker rustc runs and the compiler cc-rs runs.
258 # scripts/android-cc existed only because -Clinker takes a plain string
259 # and so needed something with a stable name; a nix store path is one.
260 androidCC = "${ndkBin}/clang";
261
262 androidEnv = {
263 CARGO_BUILD_TARGET = androidTarget;
264 "CC_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = androidCC;
265 "CXX_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/clang++";
266 "AR_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/llvm-ar";
267 CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = androidCC;
268 # cc-rs and clang both need the API level; rustc's target triple does
269 # not carry one, so it is passed as a flag on every C compile and on
270 # the link.
271 "CFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
272 "--target=${androidTarget}${toString androidApi}";
273 "CXXFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
274 "--target=${androidTarget}${toString androidApi}";
275 CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS =
276 "-Clink-arg=--target=${androidTarget}${toString androidApi}";
277 ANDROID_NDK_HOME = ndkRoot;
278 ANDROID_NDK_ROOT = ndkRoot;
279 };
280
281 # None of the desktop libraries cross: the camera is Camera2 over JNI
282 # rather than V4L2, and the audio is cpal's AAudio host rather than
283 # PipeWire or ALSA. The bindgen hook still comes along for aws-lc-sys.
284 androidArgs = {
285 inherit src;
286 strictDeps = true;
287 nativeBuildInputs = nativeBuildInputs ++ [ androidComposition.androidsdk ];
288 buildInputs = [ ];
289 doCheck = false;
290 cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq";
291 } // androidEnv;
292
Move iroh-live forward and let the vendored cpal go 789bb13 nandi 11d ago293 androidArtifacts = craneLib.buildDepsOnly (androidArgs // {
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago294 pname = "jolt-native-android-deps";
295 version = "0.1.0";
296 });
297
298 libsAndroid = craneLib.buildPackage (androidArgs // {
299 pname = "jolt-native-android";
300 version = "0.1.0";
301 cargoArtifacts = androidArtifacts;
302 installPhaseCommand = ''
303 mkdir -p $out/lib/arm64-v8a
304 cp target/${androidTarget}/release/libvidya.so $out/lib/arm64-v8a/
305 cp target/${androidTarget}/release/libjoltmoq.so $out/lib/arm64-v8a/
Publish the shared objects instead of dropping them b6001e6 nandi 11d ago306 # Both objects NEEDED it aws-lc-sys and libspa-sys pull in the
307 # C++ runtime and the NDK ships it as a shared library that
308 # nothing else provides on the device. It has to ride along in the
309 # same directory the APK packages, or dlopen fails at run time.
310 cp ${ndkBin}/../sysroot/usr/lib/${androidTarget}/libc++_shared.so \
311 $out/lib/arm64-v8a/
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago312 '';
313 });
314
315 # The directory a consumer points LD_LIBRARY_PATH at — the same shape
316 # `just build` leaves in target/release, and the same one //:libs used
317 # to stage into build/lib.
318 libs = pkgs.symlinkJoin {
319 name = "jolt-native-libs";
320 paths = [ libvidya libjolttui libjoltmoq ];
321 };
322 in
323 {
324 packages = {
325 default = libs;
326 android = libsAndroid;
Build libjoltcosmic out of the flake, so frq can link it 4706c92 nandi 7d ago327 inherit libs libvidya libjolttui libjoltmoq libjoltcosmic;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago328 };
329
Run the glimmer-gfx counter from the repo root 5416ab2 nandi 11d ago330 # Just the X11 client library on LD_LIBRARY_PATH, for `jolt gfx-demo`.
331 # The default shell would do too, but it evaluates the rust toolchain
332 # and the crane graph to hand a Clojure program one dlopen target.
333 devShells.gfx = pkgs.mkShell {
334 LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libx11 ];
335 };
336
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago337 devShells.default = pkgs.mkShell ({
338 packages = [ rustToolchain pkgs.just pkgs.sccache pkgs.cargo-nextest ]
339 ++ nativeBuildInputs
340 ++ desktopBuildInputs;
341
342 # egui opens libGL and the Wayland/X11 client libraries with dlopen,
343 # so they have to be findable at run time and not only at link time.
344 LD_LIBRARY_PATH = lib.makeLibraryPath desktopBuildInputs;
345 } // v4l2Env);
346
347 checks = {
348 inherit libvidya libjolttui libjoltmoq;
349
350 clippy = craneLib.cargoClippy (commonArgs // {
351 inherit cargoArtifacts;
352 pname = "jolt-native-clippy";
353 cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
354 });
355
356 fmt = craneLib.cargoFmt {
357 inherit src;
358 pname = "jolt-native-fmt";
359 };
360
361 test = craneLib.cargoTest (commonArgs // {
362 inherit cargoArtifacts;
363 pname = "jolt-native-test";
364 cargoTestExtraArgs = "--workspace";
365 });
366 };
367
368 formatter = pkgs.nixpkgs-fmt;
369 });
370}