Skip to content
ManyMathPublic
forked from sneurlax/nix
 
 

Repository files navigation

nix

Reproducible Flutter builds on macOS, iOS, Android, Linux, and Web with Nix.

This is a melos monorepo containing:

Path Description
packages/nix Dart CLI package exposing the nix_dart executable
apps/example Reference Flutter app demonstrating the toolkit

Quick Start

dart pub global activate melos
melos bootstrap

Reference App

The example app in apps/example/ is the smallest working example. It wraps the same Makefile.inc that subtree users include in their host app.

cd apps/example
make setup-web
make build-web
make verify-web

The example app also ships a nix.yaml, so nix_dart works there too. Run it from the app directory:

cd apps/example
dart run ../../packages/nix/bin/nix_dart.dart doctor

Dart CLI

The publishable package lives in packages/nix and exposes nix_dart.

dart pub global activate nix

# In a Flutter project:
nix_dart init macos ios linux android web   # any subset
nix_dart setup
nix_dart shell linux
nix_dart build web
nix_dart pin

A fresh nix.yaml pins no checksums, so the first setup downloads the SDK, prints its SHA-256, and stops. Put that hash under flutter.checksums and run setup again, or pass --allow-unverified once.

nix_dart init --from-existing is the migration path for projects that already have flutter_version.env, android_sdk_version.env, or a vendored copy of this repo. After editing nix.yaml, run nix_dart sync to refresh the .env files that the shell scripts consume.

If you want to start CLI-first and later hand a self-contained toolkit to CI or to another repo, nix_dart eject materializes bootstrap.sh, Makefile.inc, nix/, scripts/, and the .env files into the output directory:

nix_dart eject --output-dir tooling/nix
bash tooling/nix/bootstrap.sh

Package-specific docs live in packages/nix/README.md.

Projects without Flutter

nix_dart also works for Dart packages and native libraries that have their own flake. Set flutter: false in nix.yaml (or run nix_dart init --no-flutter <platform...> next to the flake.nix): setup fetches nothing, shell and build use the flake's dev shell as-is, and each platform's build command is whatever the flake needs, usually nix build .#<package>.

flutter: false

nix:
  flake: .

platforms:
  native:
    shell: default
    build:
      command: nix build .#all --out-link result
      output: result/
      hashes: expected-hashes/SHA256SUMS

nix_dart verify native hashes everything under build.output and compares it with build.hashes (sha256sum format, paths relative to the output). Any difference, missing file, or unlisted file fails; --write records the hashes after a known-good build.

Embedded Toolkit

Subtree + CLI

For new projects, prefer nix_dart init (see Dart CLI above) or nix_dart eject (see Standalone Script Toolkit below). The subtree approach is still supported for projects that want a vendored copy of the example toolkit checked into their repo.

git subtree add --prefix=nix \
  --squash https://github.com/ManyMath/nix.git main
PROJECT_ROOT="$PWD" bash nix/apps/example/bootstrap.sh

dart pub global activate nix
nix_dart init --from-existing

apps/example has its own pubspec.yaml, so without PROJECT_ROOT, bootstrap.sh treats it as the project and writes the .env files there. make nix-bootstrap sets it for you.

The host project's Makefile should point NIX_FLUTTER_DIR at the subtree's example toolkit:

NIX_FLUTTER_DIR ?= nix/apps/example
include $(NIX_FLUTTER_DIR)/Makefile.inc

Makefile.inc auto-delegates to nix_dart only when both nix_dart and nix.yaml are present in the host project. Until then it falls back to the vendored shell scripts.

Subtree Only

If you do not want a Dart CLI dependency in the host project, include Makefile.inc and use the vendored scripts directly through make.

NIX_FLUTTER_DIR ?= nix/apps/example
include $(NIX_FLUTTER_DIR)/Makefile.inc

Then run:

make nix-bootstrap
make nix-setup-linux
make nix-shell-linux
make nix-build-linux

Standalone Script Toolkit

nix_dart eject is the bridge between the CLI workflow and the vendored script workflow. It produces the same files that a subtree checkout would contain, but without requiring git subtree history in the host project.

nix_dart init web
nix_dart setup web
nix_dart eject --output-dir .

./scripts/fetch-flutter-web.sh
PROJECT_ROOT="$PWD" ./scripts/build-web.sh

Reproducibility Inputs

Pinning happens at four layers:

  • nix/flake.lock for Nix system dependencies.
  • flutter_version.env for the Flutter SDK version and archive checksums.
  • android_sdk_version.env for Android cmdline-tools, platform, build-tools, NDK, and CMake.
  • pubspec.lock for Dart/Flutter packages.

make pin or nix_dart pin advances the checked-in flake.lock.

flake.lock pins the toolchain, not the output. flutter build runs on the host outside the Nix sandbox, so bit-identical output is up to the SDK. Record hashes in expected-hashes/ and check them with nix_dart verify. A native dependency that needs more can build as a nix build derivation in its own flake and pin those hashes.

Web builds pass --pwa-strategy=none: Flutter's default offline-first strategy stamps a random token into flutter_bootstrap.js and flutter_service_worker.js on every build. The build also deletes build/web/.last_build_id, which hashes the output's absolute path.

Android: setup android installs the pinned platform, build-tools, NDK, and CMake through the pinned cmdline-tools. sdkmanager checks each package against Google's manifest over TLS; the toolkit doesn't pin those hashes itself. The example sets android.builder.sdkDownload=false so a drifted pin fails the build instead of being downloaded silently. Pin the platform Flutter compiles against (android-36 for Flutter 3.38.2) and cmake_version: "3.22.1".

Setup Notes

  1. Install Nix with flakes enabled:
    experimental-features = nix-command flakes
    
  2. macOS and iOS builds still need Xcode. The darwin shells respect an exported DEVELOPER_DIR and otherwise use /Applications/Xcode.app; doctor checks the same one.
  3. setup android and build android use the Nix android shell for JDK 17 on both macOS and Linux. Only the bare scripts/fetch-android-sdk.sh and make setup-android need a JDK on PATH (Apple's /usr/bin/java stub doesn't count).
  4. Linux desktop builds get GTK, clang, cmake, and ninja from the linux shell in nix/flake.nix.
  5. On Windows, nix_dart runs Nix in WSL2. Keep the checkout on the Linux filesystem; /mnt/c works but builds about 3x slower. Worktrees made by Windows git need git worktree repair --relative-paths (git 2.48+) before Nix in WSL can read them. Piping a script into nix_dart shell skips the dev shell environment; use build.command or nix develop directly.

Updating

  • Nix packages: make pin or nix_dart pin
  • Flutter SDK: edit flutter_version.env or nix.yaml, then re-run setup
  • Android SDK versions: edit android_sdk_version.env or nix.yaml, then re-run setup
  • Dart packages: flutter pub upgrade or dart pub upgrade

License

MIT. See LICENSE.