nandi/jolt-nativepublic Fork 0
6f016e7c203def2c5a9370f62742125788f0c57d
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 · 308 lines · 13.0 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.
Stop handing buildDepsOnly a src it will not read 16408ca nandi 13d ago140 # `src` comes out: with a dummySrc given, crane ignores it and says so
141 # in an eval warning on every build that touches this flake.
142 cargoArtifacts = craneLib.buildDepsOnly (removeAttrs commonArgs [ "src" ] // {
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago143 inherit dummySrc;
144 pname = "jolt-native-deps";
145 version = "0.1.0";
146 });
147
148 # cargo does not install a cdylib, so crane's default install phase —
149 # `cargo install`, which only knows about binaries — has nothing to do.
150 # Take the objects out of the target directory instead.
Run the formatter over the tree 3e8c6f0 nandi 13d ago151 # `dir` is named separately because a crate's directory and its cargo
152 # package name are not the same thing here: vidya-ffi lives in
153 # crates/jolt-vidya. Nothing is silenced — a header that stops being
154 # there should fail the build rather than ship an object with no ABI
155 # beside it.
156 soPackage = { pname, package, dir, soname }:
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago157 craneLib.buildPackage (commonArgs // {
158 inherit pname cargoArtifacts;
159 version = "0.1.0";
160 cargoExtraArgs = "--locked -p ${package}";
161 doCheck = false;
162 installPhaseCommand = ''
163 mkdir -p $out/lib $out/include
164 cp target/release/${soname} $out/lib/
Run the formatter over the tree 3e8c6f0 nandi 13d ago165 cp -r crates/${dir}/include/. $out/include/
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago166 '';
167 });
168
169 libvidya = soPackage {
170 pname = "libvidya";
171 package = "vidya-ffi";
Run the formatter over the tree 3e8c6f0 nandi 13d ago172 dir = "jolt-vidya";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago173 soname = "libvidya.so";
174 };
175 libjolttui = soPackage {
176 pname = "libjolttui";
177 package = "jolt-tui";
Run the formatter over the tree 3e8c6f0 nandi 13d ago178 dir = "jolt-tui";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago179 soname = "libjolttui.so";
180 };
181 libjoltmoq = soPackage {
182 pname = "libjoltmoq";
183 package = "jolt-moq";
Run the formatter over the tree 3e8c6f0 nandi 13d ago184 dir = "jolt-moq";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago185 soname = "libjoltmoq.so";
186 };
187
188 # The phone. Same sources, same Cargo.lock, only the target
189 # configuration moves — which is what //:libs-android and its pair of
190 # configured_alias targets used to say. Build scripts and proc macros
191 # still compile for the host; cargo arranges that on its own.
192 androidApi = 28;
193 androidTarget = "aarch64-linux-android";
194
195 androidComposition = pkgs.androidenv.composeAndroidPackages {
196 includeNDK = true;
197 ndkVersions = [ "27.2.12479018" ];
198 platformVersions = [ "${toString androidApi}" ];
199 abiVersions = [ "arm64-v8a" ];
200 };
201 ndkRoot = "${androidComposition.androidsdk}/libexec/android-sdk/ndk-bundle";
202 ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
203
204 # The NDK's clang is the linker rustc runs and the compiler cc-rs runs.
205 # scripts/android-cc existed only because -Clinker takes a plain string
206 # and so needed something with a stable name; a nix store path is one.
207 androidCC = "${ndkBin}/clang";
208
209 androidEnv = {
210 CARGO_BUILD_TARGET = androidTarget;
211 "CC_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = androidCC;
212 "CXX_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/clang++";
213 "AR_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/llvm-ar";
214 CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = androidCC;
215 # cc-rs and clang both need the API level; rustc's target triple does
216 # not carry one, so it is passed as a flag on every C compile and on
217 # the link.
218 "CFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
219 "--target=${androidTarget}${toString androidApi}";
220 "CXXFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
221 "--target=${androidTarget}${toString androidApi}";
222 CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS =
223 "-Clink-arg=--target=${androidTarget}${toString androidApi}";
224 ANDROID_NDK_HOME = ndkRoot;
225 ANDROID_NDK_ROOT = ndkRoot;
226 };
227
228 # None of the desktop libraries cross: the camera is Camera2 over JNI
229 # rather than V4L2, and the audio is cpal's AAudio host rather than
230 # PipeWire or ALSA. The bindgen hook still comes along for aws-lc-sys.
231 androidArgs = {
232 inherit src;
233 strictDeps = true;
234 nativeBuildInputs = nativeBuildInputs ++ [ androidComposition.androidsdk ];
235 buildInputs = [ ];
236 doCheck = false;
237 cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq";
238 } // androidEnv;
239
Run the formatter over the tree 3e8c6f0 nandi 13d ago240 # Same stubbed-source caveat as the host deps above: the vendored cpal
241 # has to survive into the dummy tree, or moq-media builds against an
242 # empty crate here too.
Stop handing buildDepsOnly a src it will not read 16408ca nandi 13d ago243 androidArtifacts = craneLib.buildDepsOnly (removeAttrs androidArgs [ "src" ] // {
Run the formatter over the tree 3e8c6f0 nandi 13d ago244 inherit dummySrc;
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago245 pname = "jolt-native-android-deps";
246 version = "0.1.0";
247 });
248
249 libsAndroid = craneLib.buildPackage (androidArgs // {
250 pname = "jolt-native-android";
251 version = "0.1.0";
252 cargoArtifacts = androidArtifacts;
253 installPhaseCommand = ''
254 mkdir -p $out/lib/arm64-v8a
255 cp target/${androidTarget}/release/libvidya.so $out/lib/arm64-v8a/
256 cp target/${androidTarget}/release/libjoltmoq.so $out/lib/arm64-v8a/
257 '';
258 });
259
260 # The directory a consumer points LD_LIBRARY_PATH at — the same shape
261 # `just build` leaves in target/release, and the same one //:libs used
262 # to stage into build/lib.
263 libs = pkgs.symlinkJoin {
264 name = "jolt-native-libs";
265 paths = [ libvidya libjolttui libjoltmoq ];
266 };
267 in
268 {
269 packages = {
270 default = libs;
271 android = libsAndroid;
272 inherit libs libvidya libjolttui libjoltmoq;
273 };
274
275 devShells.default = pkgs.mkShell ({
276 packages = [ rustToolchain pkgs.just pkgs.sccache pkgs.cargo-nextest ]
277 ++ nativeBuildInputs
278 ++ desktopBuildInputs;
279
280 # egui opens libGL and the Wayland/X11 client libraries with dlopen,
281 # so they have to be findable at run time and not only at link time.
282 LD_LIBRARY_PATH = lib.makeLibraryPath desktopBuildInputs;
283 } // v4l2Env);
284
285 checks = {
286 inherit libvidya libjolttui libjoltmoq;
287
288 clippy = craneLib.cargoClippy (commonArgs // {
289 inherit cargoArtifacts;
290 pname = "jolt-native-clippy";
291 cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
292 });
293
294 fmt = craneLib.cargoFmt {
295 inherit src;
296 pname = "jolt-native-fmt";
297 };
298
299 test = craneLib.cargoTest (commonArgs // {
300 inherit cargoArtifacts;
301 pname = "jolt-native-test";
302 cargoTestExtraArgs = "--workspace";
303 });
304 };
305
306 formatter = pkgs.nixpkgs-fmt;
307 });
308}