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 |
dart pub global activate melos
melos bootstrapThe 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-webThe 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 doctorThe 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 pinA 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.shPackage-specific docs live in packages/nix/README.md.
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/SHA256SUMSnix_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.
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-existingapps/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.incMakefile.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.
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.incThen run:
make nix-bootstrap
make nix-setup-linux
make nix-shell-linux
make nix-build-linuxnix_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.shPinning happens at four layers:
nix/flake.lockfor Nix system dependencies.flutter_version.envfor the Flutter SDK version and archive checksums.android_sdk_version.envfor Android cmdline-tools, platform, build-tools, NDK, and CMake.pubspec.lockfor 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".
- Install Nix with flakes enabled:
experimental-features = nix-command flakes - macOS and iOS builds still need Xcode. The darwin shells respect an exported
DEVELOPER_DIRand otherwise use/Applications/Xcode.app;doctorchecks the same one. setup androidandbuild androiduse the Nixandroidshell for JDK 17 on both macOS and Linux. Only the barescripts/fetch-android-sdk.shandmake setup-androidneed a JDK onPATH(Apple's/usr/bin/javastub doesn't count).- Linux desktop builds get GTK, clang, cmake, and ninja from the
linuxshell innix/flake.nix. - On Windows,
nix_dartruns Nix in WSL2. Keep the checkout on the Linux filesystem;/mnt/cworks but builds about 3x slower. Worktrees made by Windows git needgit worktree repair --relative-paths(git 2.48+) before Nix in WSL can read them. Piping a script intonix_dart shellskips the dev shell environment; usebuild.commandornix developdirectly.
- Nix packages:
make pinornix_dart pin - Flutter SDK: edit
flutter_version.envornix.yaml, then re-run setup - Android SDK versions: edit
android_sdk_version.envornix.yaml, then re-run setup - Dart packages:
flutter pub upgradeordart pub upgrade
MIT. See LICENSE.