nandi/jolt-nativepublic Fork 0
4956d1e2f73eae7b1f8ef16fab875c56c69dea84
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 · 306 lines · 12.8 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#
15# What went away with buck2: third-party/rust's reindeer-generated BUCK graph
16# (Cargo.lock is the one dependency graph now), the RBE container and its GitLab
17# job, and scripts/ entire — zcc, zxx, zig-include, the .dotslash manifests and
18# the generator that restated them for buck. The zig sysroot in particular was
19# only ever standing in for a system C toolchain; nix supplies a real one, which
20# is also why cpal's `pipewire` feature no longer has to be off.
21{
22 description = "jolt's native backends glimmer/egui, a terminal painter, and a MoQ media plane, each behind a C ABI";
23
24 inputs = {
25 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
26 rust-overlay = {
27 url = "github:oxalica/rust-overlay";
28 inputs.nixpkgs.follows = "nixpkgs";
29 };
30 crane.url = "github:ipetkov/crane";
31 flake-utils.url = "github:numtide/flake-utils";
32 };
33
34 outputs = { self, nixpkgs, rust-overlay, crane, flake-utils }:
35 flake-utils.lib.eachDefaultSystem (system:
36 let
37 pkgs = import nixpkgs {
38 inherit system;
39 overlays = [ (import rust-overlay) ];
40 # The NDK is unfree, and only the android outputs pull it in.
41 config.allowUnfree = true;
42 config.android_sdk.accept_license = true;
43 };
44
45 inherit (pkgs) lib;
46
47 # One toolchain for both directions. The Android std comes from the same
48 # rustc rather than a second pinned tarball, which is what
49 # scripts/rust-std-android.dotslash and the sysroot symlink farm in
50 # scripts/rustc existed to stitch together by hand.
51 rustToolchain = pkgs.rust-bin.stable.latest.default.override {
52 extensions = [ "rust-src" "clippy" "rustfmt" ];
53 targets = [ "aarch64-linux-android" ];
54 };
55
56 craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
57
58 # crane's own cleanCargoSource keeps .rs and the manifests and drops
59 # everything else — which here would drop the C headers each crate
60 # publishes and vidya-core's font and emoji assets, all of which are
61 # `include_bytes!`d or shipped beside the object. Keep them.
62 src = lib.cleanSourceWith {
63 src = ./.;
64 name = "jolt-native-source";
65 filter = path: type:
66 let rel = lib.removePrefix (toString ./. + "/") (toString path);
67 in
68 (craneLib.filterCargoSources path type)
69 || lib.hasInfix "/include/" rel
70 || lib.hasInfix "/assets/" rel
71 # The vendored cpal is a path dependency of jolt-moq and a
72 # [patch] target for the whole graph; it is source, not a fixture.
73 || lib.hasPrefix "third-party/rust/cpal" rel;
74 };
75
76 # Built by a build script, linked into the objects, or opened by them at
77 # run time. Split out because the Android graph wants almost none of it.
78 desktopBuildInputs = with pkgs; [
79 alsa-lib # cpal's ALSA host
80 pipewire # cpal's `pipewire` feature, via libspa-sys
81 libGL # glow/glutin
82 libxkbcommon # winit
83 wayland
84 libx11
85 libxcursor
86 libxi
87 libxrandr
88 ];
89
90 nativeBuildInputs = with pkgs; [
91 pkg-config
92 # aws-lc-sys (under rustls and moq-native) configures with cmake and
93 # generates its assembly with perl and go.
94 cmake
95 ninja
96 perl
97 go
98 # Sets LIBCLANG_PATH and the clang resource-dir include for every
99 # bindgen build script in the graph — v4l2r, libspa-sys, aws-lc-sys.
100 rustPlatform.bindgenHook
101 ];
102
103 # v4l2r's build script wants linux/videodev2.h. On a machine with kernel
104 # headers installed that is /usr/include; on one without it is nowhere,
105 # which is the whole reason the justfile used to reach into zig's
106 # bundled copies. nixpkgs has them as a package.
107 v4l2Env = {
108 V4L2R_VIDEODEV2_H_PATH = "${pkgs.linuxHeaders}/include";
109 BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.linuxHeaders}/include";
110 };
111
112 commonArgs = {
113 inherit src;
114 strictDeps = true;
115 inherit nativeBuildInputs;
116 buildInputs = desktopBuildInputs;
117 } // v4l2Env;
118
119 # crane builds the dependency graph against a stubbed-out copy of the
120 # workspace, which is what makes that step cacheable. The stub has to
121 # keep the vendored cpal intact, though: it is not a workspace member
122 # but a [patch] target, and moq-media compiles *against* it. Stub it and
123 # moq-media is built against an empty crate — 27 errors about
124 # `cpal::traits`, `StreamError` and friends that look like the version
125 # skew the patch exists to prevent, rather than the empty file it is.
126 dummySrc = craneLib.mkDummySrc {
127 inherit src;
Run the formatter over the tree 3e8c6f0 nandi 13d ago128 # The stub tree already has a third-party/ of its own, so this has to
129 # replace that directory rather than copy into it — `cp -r a b` where
130 # b exists means b/a, and the real cpal lands at third-party/third-party
131 # while the path cargo reads keeps the stub.
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago132 extraDummyScript = ''
Run the formatter over the tree 3e8c6f0 nandi 13d ago133 rm -rf $out/third-party
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago134 cp -r --no-preserve=mode,ownership ${src}/third-party $out/third-party
135 '';
136 };
137
138 # Every external crate, compiled once and shared by the three objects,
139 # the clippy run and the test run.
140 cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
141 inherit dummySrc;
142 pname = "jolt-native-deps";
143 version = "0.1.0";
144 });
145
146 # cargo does not install a cdylib, so crane's default install phase —
147 # `cargo install`, which only knows about binaries — has nothing to do.
148 # Take the objects out of the target directory instead.
Run the formatter over the tree 3e8c6f0 nandi 13d ago149 # `dir` is named separately because a crate's directory and its cargo
150 # package name are not the same thing here: vidya-ffi lives in
151 # crates/jolt-vidya. Nothing is silenced — a header that stops being
152 # there should fail the build rather than ship an object with no ABI
153 # beside it.
154 soPackage = { pname, package, dir, soname }:
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago155 craneLib.buildPackage (commonArgs // {
156 inherit pname cargoArtifacts;
157 version = "0.1.0";
158 cargoExtraArgs = "--locked -p ${package}";
159 doCheck = false;
160 installPhaseCommand = ''
161 mkdir -p $out/lib $out/include
162 cp target/release/${soname} $out/lib/
Run the formatter over the tree 3e8c6f0 nandi 13d ago163 cp -r crates/${dir}/include/. $out/include/
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago164 '';
165 });
166
167 libvidya = soPackage {
168 pname = "libvidya";
169 package = "vidya-ffi";
Run the formatter over the tree 3e8c6f0 nandi 13d ago170 dir = "jolt-vidya";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago171 soname = "libvidya.so";
172 };
173 libjolttui = soPackage {
174 pname = "libjolttui";
175 package = "jolt-tui";
Run the formatter over the tree 3e8c6f0 nandi 13d ago176 dir = "jolt-tui";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago177 soname = "libjolttui.so";
178 };
179 libjoltmoq = soPackage {
180 pname = "libjoltmoq";
181 package = "jolt-moq";
Run the formatter over the tree 3e8c6f0 nandi 13d ago182 dir = "jolt-moq";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago183 soname = "libjoltmoq.so";
184 };
185
186 # The phone. Same sources, same Cargo.lock, only the target
187 # configuration moves — which is what //:libs-android and its pair of
188 # configured_alias targets used to say. Build scripts and proc macros
189 # still compile for the host; cargo arranges that on its own.
190 androidApi = 28;
191 androidTarget = "aarch64-linux-android";
192
193 androidComposition = pkgs.androidenv.composeAndroidPackages {
194 includeNDK = true;
195 ndkVersions = [ "27.2.12479018" ];
196 platformVersions = [ "${toString androidApi}" ];
197 abiVersions = [ "arm64-v8a" ];
198 };
199 ndkRoot = "${androidComposition.androidsdk}/libexec/android-sdk/ndk-bundle";
200 ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
201
202 # The NDK's clang is the linker rustc runs and the compiler cc-rs runs.
203 # scripts/android-cc existed only because -Clinker takes a plain string
204 # and so needed something with a stable name; a nix store path is one.
205 androidCC = "${ndkBin}/clang";
206
207 androidEnv = {
208 CARGO_BUILD_TARGET = androidTarget;
209 "CC_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = androidCC;
210 "CXX_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/clang++";
211 "AR_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/llvm-ar";
212 CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = androidCC;
213 # cc-rs and clang both need the API level; rustc's target triple does
214 # not carry one, so it is passed as a flag on every C compile and on
215 # the link.
216 "CFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
217 "--target=${androidTarget}${toString androidApi}";
218 "CXXFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
219 "--target=${androidTarget}${toString androidApi}";
220 CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS =
221 "-Clink-arg=--target=${androidTarget}${toString androidApi}";
222 ANDROID_NDK_HOME = ndkRoot;
223 ANDROID_NDK_ROOT = ndkRoot;
224 };
225
226 # None of the desktop libraries cross: the camera is Camera2 over JNI
227 # rather than V4L2, and the audio is cpal's AAudio host rather than
228 # PipeWire or ALSA. The bindgen hook still comes along for aws-lc-sys.
229 androidArgs = {
230 inherit src;
231 strictDeps = true;
232 nativeBuildInputs = nativeBuildInputs ++ [ androidComposition.androidsdk ];
233 buildInputs = [ ];
234 doCheck = false;
235 cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq";
236 } // androidEnv;
237
Run the formatter over the tree 3e8c6f0 nandi 13d ago238 # Same stubbed-source caveat as the host deps above: the vendored cpal
239 # has to survive into the dummy tree, or moq-media builds against an
240 # empty crate here too.
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago241 androidArtifacts = craneLib.buildDepsOnly (androidArgs // {
Run the formatter over the tree 3e8c6f0 nandi 13d ago242 inherit dummySrc;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago243 pname = "jolt-native-android-deps";
244 version = "0.1.0";
245 });
246
247 libsAndroid = craneLib.buildPackage (androidArgs // {
248 pname = "jolt-native-android";
249 version = "0.1.0";
250 cargoArtifacts = androidArtifacts;
251 installPhaseCommand = ''
252 mkdir -p $out/lib/arm64-v8a
253 cp target/${androidTarget}/release/libvidya.so $out/lib/arm64-v8a/
254 cp target/${androidTarget}/release/libjoltmoq.so $out/lib/arm64-v8a/
255 '';
256 });
257
258 # The directory a consumer points LD_LIBRARY_PATH at — the same shape
259 # `just build` leaves in target/release, and the same one //:libs used
260 # to stage into build/lib.
261 libs = pkgs.symlinkJoin {
262 name = "jolt-native-libs";
263 paths = [ libvidya libjolttui libjoltmoq ];
264 };
265 in
266 {
267 packages = {
268 default = libs;
269 android = libsAndroid;
270 inherit libs libvidya libjolttui libjoltmoq;
271 };
272
273 devShells.default = pkgs.mkShell ({
274 packages = [ rustToolchain pkgs.just pkgs.sccache pkgs.cargo-nextest ]
275 ++ nativeBuildInputs
276 ++ desktopBuildInputs;
277
278 # egui opens libGL and the Wayland/X11 client libraries with dlopen,
279 # so they have to be findable at run time and not only at link time.
280 LD_LIBRARY_PATH = lib.makeLibraryPath desktopBuildInputs;
281 } // v4l2Env);
282
283 checks = {
284 inherit libvidya libjolttui libjoltmoq;
285
286 clippy = craneLib.cargoClippy (commonArgs // {
287 inherit cargoArtifacts;
288 pname = "jolt-native-clippy";
289 cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
290 });
291
292 fmt = craneLib.cargoFmt {
293 inherit src;
294 pname = "jolt-native-fmt";
295 };
296
297 test = craneLib.cargoTest (commonArgs // {
298 inherit cargoArtifacts;
299 pname = "jolt-native-test";
300 cargoTestExtraArgs = "--workspace";
301 });
302 };
303
304 formatter = pkgs.nixpkgs-fmt;
305 });
306}