nandi/jolt-nativepublic Fork 0
d682bfa94c04bddb877d4b5e21ef938b826cc9be
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 · 289 lines · 11.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;
128 extraDummyScript = ''
129 cp -r --no-preserve=mode,ownership ${src}/third-party $out/third-party
130 '';
131 };
132
133 # Every external crate, compiled once and shared by the three objects,
134 # the clippy run and the test run.
135 cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
136 inherit dummySrc;
137 pname = "jolt-native-deps";
138 version = "0.1.0";
139 });
140
141 # cargo does not install a cdylib, so crane's default install phase —
142 # `cargo install`, which only knows about binaries — has nothing to do.
143 # Take the objects out of the target directory instead.
144 soPackage = { pname, package, soname }:
145 craneLib.buildPackage (commonArgs // {
146 inherit pname cargoArtifacts;
147 version = "0.1.0";
148 cargoExtraArgs = "--locked -p ${package}";
149 doCheck = false;
150 installPhaseCommand = ''
151 mkdir -p $out/lib $out/include
152 cp target/release/${soname} $out/lib/
153 cp -r crates/${package}/include/. $out/include/ 2>/dev/null || true
154 '';
155 });
156
157 libvidya = soPackage {
158 pname = "libvidya";
159 package = "vidya-ffi";
160 soname = "libvidya.so";
161 };
162 libjolttui = soPackage {
163 pname = "libjolttui";
164 package = "jolt-tui";
165 soname = "libjolttui.so";
166 };
167 libjoltmoq = soPackage {
168 pname = "libjoltmoq";
169 package = "jolt-moq";
170 soname = "libjoltmoq.so";
171 };
172
173 # The phone. Same sources, same Cargo.lock, only the target
174 # configuration moves — which is what //:libs-android and its pair of
175 # configured_alias targets used to say. Build scripts and proc macros
176 # still compile for the host; cargo arranges that on its own.
177 androidApi = 28;
178 androidTarget = "aarch64-linux-android";
179
180 androidComposition = pkgs.androidenv.composeAndroidPackages {
181 includeNDK = true;
182 ndkVersions = [ "27.2.12479018" ];
183 platformVersions = [ "${toString androidApi}" ];
184 abiVersions = [ "arm64-v8a" ];
185 };
186 ndkRoot = "${androidComposition.androidsdk}/libexec/android-sdk/ndk-bundle";
187 ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
188
189 # The NDK's clang is the linker rustc runs and the compiler cc-rs runs.
190 # scripts/android-cc existed only because -Clinker takes a plain string
191 # and so needed something with a stable name; a nix store path is one.
192 androidCC = "${ndkBin}/clang";
193
194 androidEnv = {
195 CARGO_BUILD_TARGET = androidTarget;
196 "CC_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = androidCC;
197 "CXX_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/clang++";
198 "AR_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/llvm-ar";
199 CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = androidCC;
200 # cc-rs and clang both need the API level; rustc's target triple does
201 # not carry one, so it is passed as a flag on every C compile and on
202 # the link.
203 "CFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
204 "--target=${androidTarget}${toString androidApi}";
205 "CXXFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
206 "--target=${androidTarget}${toString androidApi}";
207 CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS =
208 "-Clink-arg=--target=${androidTarget}${toString androidApi}";
209 ANDROID_NDK_HOME = ndkRoot;
210 ANDROID_NDK_ROOT = ndkRoot;
211 };
212
213 # None of the desktop libraries cross: the camera is Camera2 over JNI
214 # rather than V4L2, and the audio is cpal's AAudio host rather than
215 # PipeWire or ALSA. The bindgen hook still comes along for aws-lc-sys.
216 androidArgs = {
217 inherit src;
218 strictDeps = true;
219 nativeBuildInputs = nativeBuildInputs ++ [ androidComposition.androidsdk ];
220 buildInputs = [ ];
221 doCheck = false;
222 cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq";
223 } // androidEnv;
224
225 androidArtifacts = craneLib.buildDepsOnly (androidArgs // {
226 pname = "jolt-native-android-deps";
227 version = "0.1.0";
228 });
229
230 libsAndroid = craneLib.buildPackage (androidArgs // {
231 pname = "jolt-native-android";
232 version = "0.1.0";
233 cargoArtifacts = androidArtifacts;
234 installPhaseCommand = ''
235 mkdir -p $out/lib/arm64-v8a
236 cp target/${androidTarget}/release/libvidya.so $out/lib/arm64-v8a/
237 cp target/${androidTarget}/release/libjoltmoq.so $out/lib/arm64-v8a/
238 '';
239 });
240
241 # The directory a consumer points LD_LIBRARY_PATH at — the same shape
242 # `just build` leaves in target/release, and the same one //:libs used
243 # to stage into build/lib.
244 libs = pkgs.symlinkJoin {
245 name = "jolt-native-libs";
246 paths = [ libvidya libjolttui libjoltmoq ];
247 };
248 in
249 {
250 packages = {
251 default = libs;
252 android = libsAndroid;
253 inherit libs libvidya libjolttui libjoltmoq;
254 };
255
256 devShells.default = pkgs.mkShell ({
257 packages = [ rustToolchain pkgs.just pkgs.sccache pkgs.cargo-nextest ]
258 ++ nativeBuildInputs
259 ++ desktopBuildInputs;
260
261 # egui opens libGL and the Wayland/X11 client libraries with dlopen,
262 # so they have to be findable at run time and not only at link time.
263 LD_LIBRARY_PATH = lib.makeLibraryPath desktopBuildInputs;
264 } // v4l2Env);
265
266 checks = {
267 inherit libvidya libjolttui libjoltmoq;
268
269 clippy = craneLib.cargoClippy (commonArgs // {
270 inherit cargoArtifacts;
271 pname = "jolt-native-clippy";
272 cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
273 });
274
275 fmt = craneLib.cargoFmt {
276 inherit src;
277 pname = "jolt-native-fmt";
278 };
279
280 test = craneLib.cargoTest (commonArgs // {
281 inherit cargoArtifacts;
282 pname = "jolt-native-test";
283 cargoTestExtraArgs = "--workspace";
284 });
285 };
286
287 formatter = pkgs.nixpkgs-fmt;
288 });
289}