Posted on

Nix Build System

How Nix compiles everything in Wawona — from native C libraries to Rust backends to final app bundles for macOS, iOS, and Android.


Why Nix?

Nix provides hermetic, reproducible builds with a single dependency. No vendored source code, no global system pollution. Every artifact is content-addressed and cached independently.


Three-Layer Architecture

┌──────────────────────────────────────────────────┐
│  Layer 3: App Packaging                          │
│  .app bundles, Xcode projects, Gradle projects   │
├──────────────────────────────────────────────────┤
│  Layer 2: Rust Backend (crate2nix)               │
│  Per-crate Nix derivations for incremental       │
│  builds — only changed crates rebuild            │
├──────────────────────────────────────────────────┤
│  Layer 1: Native C/C++ Libraries                 │
│  libwayland, xkbcommon, ffmpeg, zstd, lz4,       │
│  openssl, libssh2, mbedtls, etc.                 │
└──────────────────────────────────────────────────┘

Layer 1: Native Libraries

Each library has per-platform .nix files in dependencies/libs/:

dependencies/libs/
├── ffmpeg/        # android.nix, ios.nix, macos.nix
├── libssh2/       # android.nix, ios.nix
├── libwayland/    # android.nix, ios.nix, macos.nix
├── lz4/           # android.nix, ios.nix, macos.nix
├── openssl/       # android.nix, ios.nix
├── waypipe/       # ios.nix, macos.nix, android.nix
├── xkbcommon/     # android.nix, ios.nix, macos.nix
├── zstd/          # android.nix, ios.nix, macos.nix
└── ...

The dependencies/toolchains/default.nix dispatcher exports:

  • buildForIOS name entry — dispatches to libs/<name>/ios.nix
  • buildForMacOS name entry — dispatches to libs/<name>/macos.nix
  • buildForAndroid name entry — dispatches to libs/<name>/android.nix

Each library is a standalone Nix derivation. Changing zstd does not rebuild openssl.


Layer 2: Rust Backend (crate2nix)

Why crate2nix?

The previous buildRustPackage approach treated the entire workspace as a single derivation — any Rust change forced a full rebuild.

crate2nix generates a separate Nix derivation for every crate in Cargo.lock. Nix caches each independently. Changing one crate only rebuilds that crate and its reverse dependencies.

The Pipeline

Cargo.toml + Cargo.lock
        │
        ▼
crate2nix generates Cargo.nix
        │
        ▼
Per-crate Nix derivations (~120 crates)
        │
        ▼
Final libwawona.a / libwawona.so

Cross-Compilation

PlatformCargo TargetStrategy
macOSNative (no --target)Direct build
iOS deviceaarch64-apple-iosOverride stdenv.hostPlatform
iOS simulatoraarch64-apple-ios-simOverride stdenv.hostPlatform
Androidaarch64-linux-androidNDK toolchain via override

For iOS/Android, Nix overrides stdenv.hostPlatform so that buildRustCrate correctly sets TARGET, CARGO_CFG_TARGET_OS, and the --target flag.

Features by Platform

PlatformEnabled FeaturesWhy
macOS(none)No waypipe in macOS backend
iOSwaypipe-sshIn-process waypipe with static libssh2
AndroidwaypipeIn-process waypipe

Layer 3: App Packaging

macOS

Standard mkDerivation that compiles Obj-C sources and links against the Rust backend. Produces a .app bundle.

iOS

Two stages:

  1. Nix build: Compiles Obj-C, links libwawona.a + native C libs into .app
  2. Simulator automation: Generates Xcode project via xcodegen.nix, builds with xcodebuild, installs, launches, attaches LLDB

Android

Compiles JNI C code, links native libraries, bundles SSH binaries, and uses Gradle for final APK assembly.

Project Generators

GeneratorNix ModuleOutput
XcodeGendependencies/generators/xcodegen.nixWawona.xcodeproj
GradleGendependencies/generators/gradlegen.nixGradle project files

Caching Behavior

ChangeRebuild Scope
Rust source in src/Only wawona crate + final assembly
Waypipe sourcewaypipe crate + wawona crate
Native C library (e.g. zstd)That library + crates linking it
flake.nix or rust-backend-c2n.nixMay invalidate crate2nix generation (full rebuild)
Switch platform (e.g. iOS sim → device)Full rebuild (target triple change)

Build Commands

CommandDescription
nix runmacOS app (build + launch)
nix run .#wawona-iosiOS Simulator (xcodegen + build + run)
nix run .#wawona-androidAndroid app
nix build .#wawona-macos-backendmacOS Rust static library
nix build .#wawona-ios-backendiOS device Rust static library
nix build .#wawona-ios-sim-backendiOS sim Rust static library
nix build .#wawona-android-backendAndroid Rust shared library
nix run .#xcodegenGenerate Xcode project (iOS + macOS)
nix run .#xcodegen-iosGenerate Xcode project (iOS only)
nix run .#gradlegenGenerate Gradle project

Flake Inputs

InputPurpose
nixpkgsBase package set (unstable)
rust-overlayRust toolchain with iOS/Android targets
crate2nixPer-crate Nix derivation generator

Key Files

FileRole
flake.nixTop-level: inputs, overlays, all packages
dependencies/wawona/rust-backend-c2n.nixcrate2nix Rust backend
dependencies/wawona/workspace-src.nixCargo workspace assembly
dependencies/wawona/ios.nixiOS app bundle + simulator automation
dependencies/wawona/macos.nixmacOS app bundle
dependencies/wawona/android.nixAndroid project
dependencies/toolchains/default.nixPlatform dispatcher
dependencies/generators/xcodegen.nixXcode project generator
dependencies/generators/gradlegen.nixGradle project generator