| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 1 | # 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 |
| Move iroh-live forward and let the vendored cpal go 789bb13 nandi 11d ago | 70 | || lib.hasInfix "/assets/" rel; |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 71 | }; |
| 72 | |
| 73 | # Built by a build script, linked into the objects, or opened by them at |
| 74 | # run time. Split out because the Android graph wants almost none of it. |
| 75 | desktopBuildInputs = with pkgs; [ |
| 76 | alsa-lib # cpal's ALSA host |
| 77 | pipewire # cpal's `pipewire` feature, via libspa-sys |
| 78 | libGL # glow/glutin |
| 79 | libxkbcommon # winit |
| 80 | wayland |
| 81 | libx11 |
| 82 | libxcursor |
| 83 | libxi |
| 84 | libxrandr |
| 85 | ]; |
| 86 | |
| 87 | nativeBuildInputs = with pkgs; [ |
| 88 | pkg-config |
| 89 | # aws-lc-sys (under rustls and moq-native) configures with cmake and |
| 90 | # generates its assembly with perl and go. |
| 91 | cmake |
| 92 | ninja |
| 93 | perl |
| 94 | go |
| 95 | # Sets LIBCLANG_PATH and the clang resource-dir include for every |
| 96 | # bindgen build script in the graph — v4l2r, libspa-sys, aws-lc-sys. |
| 97 | rustPlatform.bindgenHook |
| 98 | ]; |
| 99 | |
| 100 | # v4l2r's build script wants linux/videodev2.h. On a machine with kernel |
| 101 | # headers installed that is /usr/include; on one without it is nowhere, |
| 102 | # which is the whole reason the justfile used to reach into zig's |
| 103 | # bundled copies. nixpkgs has them as a package. |
| 104 | v4l2Env = { |
| 105 | V4L2R_VIDEODEV2_H_PATH = "${pkgs.linuxHeaders}/include"; |
| 106 | BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.linuxHeaders}/include"; |
| 107 | }; |
| 108 | |
| 109 | commonArgs = { |
| 110 | inherit src; |
| 111 | strictDeps = true; |
| 112 | inherit nativeBuildInputs; |
| 113 | buildInputs = desktopBuildInputs; |
| 114 | } // v4l2Env; |
| 115 | |
| 116 | # Every external crate, compiled once and shared by the three objects, |
| 117 | # the clippy run and the test run. |
| Move iroh-live forward and let the vendored cpal go 789bb13 nandi 11d ago | 118 | cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 119 | pname = "jolt-native-deps"; |
| 120 | version = "0.1.0"; |
| 121 | }); |
| 122 | |
| 123 | # cargo does not install a cdylib, so crane's default install phase — |
| 124 | # `cargo install`, which only knows about binaries — has nothing to do. |
| 125 | # Take the objects out of the target directory instead. |
| Run the formatter over the tree 3e8c6f0 nandi 13d ago | 126 | # `dir` is named separately because a crate's directory and its cargo |
| 127 | # package name are not the same thing here: vidya-ffi lives in |
| 128 | # crates/jolt-vidya. Nothing is silenced — a header that stops being |
| 129 | # there should fail the build rather than ship an object with no ABI |
| 130 | # beside it. |
| 131 | soPackage = { pname, package, dir, soname }: |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 132 | craneLib.buildPackage (commonArgs // { |
| 133 | inherit pname cargoArtifacts; |
| 134 | version = "0.1.0"; |
| 135 | cargoExtraArgs = "--locked -p ${package}"; |
| 136 | doCheck = false; |
| 137 | installPhaseCommand = '' |
| 138 | mkdir -p $out/lib $out/include |
| 139 | cp target/release/${soname} $out/lib/ |
| Run the formatter over the tree 3e8c6f0 nandi 13d ago | 140 | cp -r crates/${dir}/include/. $out/include/ |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 141 | ''; |
| 142 | }); |
| 143 | |
| 144 | libvidya = soPackage { |
| 145 | pname = "libvidya"; |
| 146 | package = "vidya-ffi"; |
| Run the formatter over the tree 3e8c6f0 nandi 13d ago | 147 | dir = "jolt-vidya"; |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 148 | soname = "libvidya.so"; |
| 149 | }; |
| 150 | libjolttui = soPackage { |
| 151 | pname = "libjolttui"; |
| 152 | package = "jolt-tui"; |
| Run the formatter over the tree 3e8c6f0 nandi 13d ago | 153 | dir = "jolt-tui"; |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 154 | soname = "libjolttui.so"; |
| 155 | }; |
| 156 | libjoltmoq = soPackage { |
| 157 | pname = "libjoltmoq"; |
| 158 | package = "jolt-moq"; |
| Run the formatter over the tree 3e8c6f0 nandi 13d ago | 159 | dir = "jolt-moq"; |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 160 | soname = "libjoltmoq.so"; |
| 161 | }; |
| 162 | |
| 163 | # The phone. Same sources, same Cargo.lock, only the target |
| 164 | # configuration moves — which is what //:libs-android and its pair of |
| 165 | # configured_alias targets used to say. Build scripts and proc macros |
| 166 | # still compile for the host; cargo arranges that on its own. |
| 167 | androidApi = 28; |
| 168 | androidTarget = "aarch64-linux-android"; |
| 169 | |
| 170 | androidComposition = pkgs.androidenv.composeAndroidPackages { |
| 171 | includeNDK = true; |
| 172 | ndkVersions = [ "27.2.12479018" ]; |
| 173 | platformVersions = [ "${toString androidApi}" ]; |
| 174 | abiVersions = [ "arm64-v8a" ]; |
| 175 | }; |
| 176 | ndkRoot = "${androidComposition.androidsdk}/libexec/android-sdk/ndk-bundle"; |
| 177 | ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin"; |
| 178 | |
| 179 | # The NDK's clang is the linker rustc runs and the compiler cc-rs runs. |
| 180 | # scripts/android-cc existed only because -Clinker takes a plain string |
| 181 | # and so needed something with a stable name; a nix store path is one. |
| 182 | androidCC = "${ndkBin}/clang"; |
| 183 | |
| 184 | androidEnv = { |
| 185 | CARGO_BUILD_TARGET = androidTarget; |
| 186 | "CC_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = androidCC; |
| 187 | "CXX_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/clang++"; |
| 188 | "AR_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = "${ndkBin}/llvm-ar"; |
| 189 | CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = androidCC; |
| 190 | # cc-rs and clang both need the API level; rustc's target triple does |
| 191 | # not carry one, so it is passed as a flag on every C compile and on |
| 192 | # the link. |
| 193 | "CFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = |
| 194 | "--target=${androidTarget}${toString androidApi}"; |
| 195 | "CXXFLAGS_${builtins.replaceStrings ["-"] ["_"] androidTarget}" = |
| 196 | "--target=${androidTarget}${toString androidApi}"; |
| 197 | CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS = |
| 198 | "-Clink-arg=--target=${androidTarget}${toString androidApi}"; |
| 199 | ANDROID_NDK_HOME = ndkRoot; |
| 200 | ANDROID_NDK_ROOT = ndkRoot; |
| 201 | }; |
| 202 | |
| 203 | # None of the desktop libraries cross: the camera is Camera2 over JNI |
| 204 | # rather than V4L2, and the audio is cpal's AAudio host rather than |
| 205 | # PipeWire or ALSA. The bindgen hook still comes along for aws-lc-sys. |
| 206 | androidArgs = { |
| 207 | inherit src; |
| 208 | strictDeps = true; |
| 209 | nativeBuildInputs = nativeBuildInputs ++ [ androidComposition.androidsdk ]; |
| 210 | buildInputs = [ ]; |
| 211 | doCheck = false; |
| 212 | cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq"; |
| 213 | } // androidEnv; |
| 214 | |
| Move iroh-live forward and let the vendored cpal go 789bb13 nandi 11d ago | 215 | androidArtifacts = craneLib.buildDepsOnly (androidArgs // { |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 216 | pname = "jolt-native-android-deps"; |
| 217 | version = "0.1.0"; |
| 218 | }); |
| 219 | |
| 220 | libsAndroid = craneLib.buildPackage (androidArgs // { |
| 221 | pname = "jolt-native-android"; |
| 222 | version = "0.1.0"; |
| 223 | cargoArtifacts = androidArtifacts; |
| 224 | installPhaseCommand = '' |
| 225 | mkdir -p $out/lib/arm64-v8a |
| 226 | cp target/${androidTarget}/release/libvidya.so $out/lib/arm64-v8a/ |
| 227 | cp target/${androidTarget}/release/libjoltmoq.so $out/lib/arm64-v8a/ |
| 228 | ''; |
| 229 | }); |
| 230 | |
| 231 | # The directory a consumer points LD_LIBRARY_PATH at — the same shape |
| 232 | # `just build` leaves in target/release, and the same one //:libs used |
| 233 | # to stage into build/lib. |
| 234 | libs = pkgs.symlinkJoin { |
| 235 | name = "jolt-native-libs"; |
| 236 | paths = [ libvidya libjolttui libjoltmoq ]; |
| 237 | }; |
| 238 | in |
| 239 | { |
| 240 | packages = { |
| 241 | default = libs; |
| 242 | android = libsAndroid; |
| 243 | inherit libs libvidya libjolttui libjoltmoq; |
| 244 | }; |
| 245 | |
| Run the glimmer-gfx counter from the repo root 5416ab2 nandi 11d ago | 246 | # Just the X11 client library on LD_LIBRARY_PATH, for `jolt gfx-demo`. |
| 247 | # The default shell would do too, but it evaluates the rust toolchain |
| 248 | # and the crane graph to hand a Clojure program one dlopen target. |
| 249 | devShells.gfx = pkgs.mkShell { |
| 250 | LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libx11 ]; |
| 251 | }; |
| 252 | |
| Pin the toolchain with a flake, and let buck2 and DotSlash go 5ae197f nandi 13d ago | 253 | devShells.default = pkgs.mkShell ({ |
| 254 | packages = [ rustToolchain pkgs.just pkgs.sccache pkgs.cargo-nextest ] |
| 255 | ++ nativeBuildInputs |
| 256 | ++ desktopBuildInputs; |
| 257 | |
| 258 | # egui opens libGL and the Wayland/X11 client libraries with dlopen, |
| 259 | # so they have to be findable at run time and not only at link time. |
| 260 | LD_LIBRARY_PATH = lib.makeLibraryPath desktopBuildInputs; |
| 261 | } // v4l2Env); |
| 262 | |
| 263 | checks = { |
| 264 | inherit libvidya libjolttui libjoltmoq; |
| 265 | |
| 266 | clippy = craneLib.cargoClippy (commonArgs // { |
| 267 | inherit cargoArtifacts; |
| 268 | pname = "jolt-native-clippy"; |
| 269 | cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings"; |
| 270 | }); |
| 271 | |
| 272 | fmt = craneLib.cargoFmt { |
| 273 | inherit src; |
| 274 | pname = "jolt-native-fmt"; |
| 275 | }; |
| 276 | |
| 277 | test = craneLib.cargoTest (commonArgs // { |
| 278 | inherit cargoArtifacts; |
| 279 | pname = "jolt-native-test"; |
| 280 | cargoTestExtraArgs = "--workspace"; |
| 281 | }); |
| 282 | }; |
| 283 | |
| 284 | formatter = pkgs.nixpkgs-fmt; |
| 285 | }); |
| 286 | } |