nandi/jolt-nativepublic Fork 0
fccae8816c94635db5af22e96b848d1042ddcb63
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 · 311 lines · 13.1 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 12d 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
135 # Every external crate, compiled once and shared by the three objects,
136 # the clippy run and the test run.
Move iroh-live forward and let the vendored cpal go 789bb13 nandi 12d ago137 cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago138 pname = "jolt-native-deps";
139 version = "0.1.0";
140 });
141
142 # cargo does not install a cdylib, so crane's default install phase —
143 # `cargo install`, which only knows about binaries — has nothing to do.
144 # Take the objects out of the target directory instead.
Run the formatter over the tree 3e8c6f0 nandi 13d ago145 # `dir` is named separately because a crate's directory and its cargo
146 # package name are not the same thing here: vidya-ffi lives in
147 # crates/jolt-vidya. Nothing is silenced — a header that stops being
148 # there should fail the build rather than ship an object with no ABI
149 # beside it.
150 soPackage = { pname, package, dir, soname }:
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago151 craneLib.buildPackage (commonArgs // {
152 inherit pname cargoArtifacts;
153 version = "0.1.0";
154 cargoExtraArgs = "--locked -p ${package}";
155 doCheck = false;
156 installPhaseCommand = ''
157 mkdir -p $out/lib $out/include
158 cp target/release/${soname} $out/lib/
Run the formatter over the tree 3e8c6f0 nandi 13d ago159 cp -r crates/${dir}/include/. $out/include/
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago160 '';
161 });
162
163 libvidya = soPackage {
164 pname = "libvidya";
165 package = "vidya-ffi";
Run the formatter over the tree 3e8c6f0 nandi 13d ago166 dir = "jolt-vidya";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago167 soname = "libvidya.so";
168 };
169 libjolttui = soPackage {
170 pname = "libjolttui";
171 package = "jolt-tui";
Run the formatter over the tree 3e8c6f0 nandi 13d ago172 dir = "jolt-tui";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago173 soname = "libjolttui.so";
174 };
175 libjoltmoq = soPackage {
176 pname = "libjoltmoq";
177 package = "jolt-moq";
Run the formatter over the tree 3e8c6f0 nandi 13d ago178 dir = "jolt-moq";
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago179 soname = "libjoltmoq.so";
180 };
181
182 # The phone. Same sources, same Cargo.lock, only the target
183 # configuration moves — which is what //:libs-android and its pair of
184 # configured_alias targets used to say. Build scripts and proc macros
185 # still compile for the host; cargo arranges that on its own.
186 androidApi = 28;
187 androidTarget = "aarch64-linux-android";
188
189 androidComposition = pkgs.androidenv.composeAndroidPackages {
190 includeNDK = true;
191 ndkVersions = [ "27.2.12479018" ];
192 platformVersions = [ "${toString androidApi}" ];
193 abiVersions = [ "arm64-v8a" ];
194 };
195 ndkRoot = "${androidComposition.androidsdk}/libexec/android-sdk/ndk-bundle";
196 ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
197
198 # The NDK's clang is the linker rustc runs and the compiler cc-rs runs.
199 # scripts/android-cc existed only because -Clinker takes a plain string
200 # and so needed something with a stable name; a nix store path is one.
201 androidCC = "${ndkBin}/clang";
202
203 androidEnv = {
204 CARGO_BUILD_TARGET = androidTarget;
205 "CC_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = androidCC;
206 "CXX_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/clang++";
207 "AR_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/llvm-ar";
208 CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = androidCC;
209 # cc-rs and clang both need the API level; rustc's target triple does
210 # not carry one, so it is passed as a flag on every C compile and on
211 # the link.
212 "CFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
213 "--target=${androidTarget}${toString androidApi}";
214 "CXXFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" =
215 "--target=${androidTarget}${toString androidApi}";
216 CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS =
217 "-Clink-arg=--target=${androidTarget}${toString androidApi}";
218 ANDROID_NDK_HOME = ndkRoot;
219 ANDROID_NDK_ROOT = ndkRoot;
220 };
221
222 # None of the desktop libraries cross: the camera is Camera2 over JNI
223 # rather than V4L2, and the audio is cpal's AAudio host rather than
224 # PipeWire or ALSA. The bindgen hook still comes along for aws-lc-sys.
225 androidArgs = {
226 inherit src;
227 strictDeps = true;
228 nativeBuildInputs = nativeBuildInputs ++ [ androidComposition.androidsdk ];
229 buildInputs = [ ];
230 doCheck = false;
231 cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq";
232 } // androidEnv;
233
Move iroh-live forward and let the vendored cpal go 789bb13 nandi 12d ago234 androidArtifacts = craneLib.buildDepsOnly (androidArgs // {
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago235 pname = "jolt-native-android-deps";
236 version = "0.1.0";
237 });
238
239 libsAndroid = craneLib.buildPackage (androidArgs // {
240 pname = "jolt-native-android";
241 version = "0.1.0";
242 cargoArtifacts = androidArtifacts;
243 installPhaseCommand = ''
244 mkdir -p $out/lib/arm64-v8a
245 cp target/${androidTarget}/release/libvidya.so $out/lib/arm64-v8a/
246 cp target/${androidTarget}/release/libjoltmoq.so $out/lib/arm64-v8a/
Publish the shared objects instead of dropping them b6001e6 nandi 11d ago247 # Both objects NEEDED it aws-lc-sys and libspa-sys pull in the
248 # C++ runtime and the NDK ships it as a shared library that
249 # nothing else provides on the device. It has to ride along in the
250 # same directory the APK packages, or dlopen fails at run time.
251 cp ${ndkBin}/../sysroot/usr/lib/${androidTarget}/libc++_shared.so \
252 $out/lib/arm64-v8a/
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago253 '';
254 });
255
256 # The directory a consumer points LD_LIBRARY_PATH at — the same shape
257 # `just build` leaves in target/release, and the same one //:libs used
258 # to stage into build/lib.
259 libs = pkgs.symlinkJoin {
260 name = "jolt-native-libs";
261 paths = [ libvidya libjolttui libjoltmoq ];
262 };
263 in
264 {
265 packages = {
266 default = libs;
267 android = libsAndroid;
268 inherit libs libvidya libjolttui libjoltmoq;
269 };
270
Run the glimmer-gfx counter from the repo root 5416ab2 nandi 11d ago271 # Just the X11 client library on LD_LIBRARY_PATH, for `jolt gfx-demo`.
272 # The default shell would do too, but it evaluates the rust toolchain
273 # and the crane graph to hand a Clojure program one dlopen target.
274 devShells.gfx = pkgs.mkShell {
275 LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libx11 ];
276 };
277
Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago278 devShells.default = pkgs.mkShell ({
279 packages = [ rustToolchain pkgs.just pkgs.sccache pkgs.cargo-nextest ]
280 ++ nativeBuildInputs
281 ++ desktopBuildInputs;
282
283 # egui opens libGL and the Wayland/X11 client libraries with dlopen,
284 # so they have to be findable at run time and not only at link time.
285 LD_LIBRARY_PATH = lib.makeLibraryPath desktopBuildInputs;
286 } // v4l2Env);
287
288 checks = {
289 inherit libvidya libjolttui libjoltmoq;
290
291 clippy = craneLib.cargoClippy (commonArgs // {
292 inherit cargoArtifacts;
293 pname = "jolt-native-clippy";
294 cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
295 });
296
297 fmt = craneLib.cargoFmt {
298 inherit src;
299 pname = "jolt-native-fmt";
300 };
301
302 test = craneLib.cargoTest (commonArgs // {
303 inherit cargoArtifacts;
304 pname = "jolt-native-test";
305 cargoTestExtraArgs = "--workspace";
306 });
307 };
308
309 formatter = pkgs.nixpkgs-fmt;
310 });
311}