# Nix Flakes - Learnings and Gotchas **Purpose:** Document Nix flake patterns, gotchas, and best practices learned during ngit-grasp development **Last Updated:** November 4, 2025 --- ## Critical Gotchas ### Always Use `nix develop`, Not `nix-shell` **Problem:** We use `flake.nix`, not `shell.nix`. Using `nix-shell` will fail or use the wrong environment. ```bash # ✅ Correct - for flake.nix cd grasp-audit nix develop nix develop -c cargo build # ❌ Wrong - for shell.nix (we don't use this) nix-shell nix-shell --run "cargo build" ``` **Why:** - `nix-shell` looks for `shell.nix` or `default.nix` - `nix develop` looks for `flake.nix` - We migrated from `shell.nix` to `flake.nix` on November 4, 2025 **Related:** See `docs/archive/2025-11-04-flake-migration.md` --- ## Flake Structure ### Our Standard Flake Pattern ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; rust-overlay.url = "github:oxalica/rust-overlay"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { nixpkgs, rust-overlay, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: let overlays = [ (import rust-overlay) ]; pkgs = import nixpkgs { inherit system overlays; }; manifest = pkgs.lib.importTOML ./Cargo.toml; in with pkgs; { # Development shell devShells.default = mkShell { nativeBuildInputs = [ rust-bin.stable.latest.default pkg-config gitlint ]; buildInputs = [ openssl ]; shellHook = '' echo "🦀 Development environment loaded" export RUST_SRC_PATH=${pkgs.rustPlatform.rustLibSrc} ''; }; # Package output packages.default = pkgs.rustPlatform.buildRustPackage { pname = manifest.package.name; version = manifest.package.version; src = ./.; cargoLock = { lockFile = ./Cargo.lock; }; buildInputs = [ openssl ]; nativeBuildInputs = [ pkg-config ]; doCheck = false; # Run tests separately }; }); } ``` ### Key Components 1. **rust-overlay**: Provides latest stable Rust toolchain 2. **flake-utils**: Cross-platform support helper 3. **manifest**: Auto-read version from Cargo.toml 4. **devShells.default**: Development environment 5. **packages.default**: Buildable package --- ## Common Flake Commands ### Essential Commands ```bash # Enter development shell nix develop # Run command in dev shell (one-off) nix develop -c cargo build # Show flake outputs nix flake show # Check flake validity nix flake check # Update flake inputs (like updating dependencies) nix flake update # Build the package directly nix build # Run without installing nix run # Show flake metadata nix flake metadata ``` ### Debugging Commands ```bash # Show detailed evaluation trace nix develop --show-trace # Print flake evaluation nix eval .#devShells.x86_64-linux.default # Check what's in the store nix path-info .#packages.x86_64-linux.default ``` --- ## Subproject Flakes ### grasp-audit Has Its Own Flake **Important:** `grasp-audit/` is a subproject with its own `flake.nix` and `Cargo.toml`. ```bash # ✅ Correct - enter grasp-audit environment cd grasp-audit nix develop cargo build # ❌ Wrong - can't build from root cd ngit-grasp cargo build # This won't find grasp-audit dependencies ``` **Why:** - Each Rust workspace needs its own Nix environment - Dependencies are project-specific - Flake inputs are locked per-project --- ## Migration from shell.nix to flake.nix ### What Changed **Before (shell.nix):** ```nix { pkgs ? import {} }: pkgs.mkShell { buildInputs = with pkgs; [ rustc cargo openssl pkg-config ]; } ``` **After (flake.nix):** - Locked inputs (reproducible) - Multi-output (dev shell + package) - Cross-platform by default - Better tooling integration ### Migration Steps 1. Create `flake.nix` with standard structure 2. Run `nix flake check` to validate 3. Update all documentation: `nix-shell` → `nix develop` 4. Test that build works: `nix develop -c cargo build` 5. Remove `shell.nix` 6. Commit changes **Reference:** See `docs/archive/2025-11-04-flake-migration.md` --- ## Benefits of Flakes ### Reproducibility **Locked inputs** ensure everyone gets the same environment: ```bash # flake.lock contains exact commits $ cat flake.lock { "nodes": { "nixpkgs": { "locked": { "lastModified": 1698611440, "narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", "rev": "23e89e0c8c5e2d9cf5b5e7c3e8e8e8e8e8e8e8e8" } } } } ``` Everyone running `nix develop` gets **exactly** this version of nixpkgs. ### Multi-Output Single flake provides: - **devShells.default**: Development environment - **packages.default**: Buildable package - **apps.default**: Runnable application (optional) ### Composability Flakes can use other flakes as inputs: ```nix { inputs = { grasp-audit.url = "path:./grasp-audit"; }; } ``` --- ## Common Issues ### Issue: "error: getting status of '/nix/store/...': No such file or directory" **Cause:** Flake inputs need to be updated or fetched **Solution:** ```bash nix flake update nix develop ``` ### Issue: "error: experimental feature 'nix-command' is not enabled" **Cause:** Nix flakes are experimental and need to be enabled **Solution:** Add to `~/.config/nix/nix.conf`: ``` experimental-features = nix-command flakes ``` ### Issue: Changes to flake.nix not taking effect **Cause:** Flake evaluation is cached **Solution:** ```bash # Clear evaluation cache nix flake update # Or force re-evaluation nix develop --refresh ``` ### Issue: "error: cannot find flake 'flake:self' in the flake registries" **Cause:** Not in a git repository or flake.nix not committed **Solution:** ```bash git add flake.nix flake.lock git commit -m "Add flake" ``` **Note:** Flakes require git. Uncommitted files are ignored by default. --- ## Best Practices ### 1. Always Commit flake.lock ```bash git add flake.lock git commit -m "Update flake inputs" ``` **Why:** Ensures reproducibility across machines and CI/CD ### 2. Use Specific Rust Versions When Needed ```nix # Latest stable (default) rust-bin.stable.latest.default # Specific version rust-bin.stable."1.75.0".default # Nightly rust-bin.nightly."2024-01-01".default ``` ### 3. Include Helpful Shell Hooks ```nix shellHook = '' echo "🦀 GRASP Audit development environment" echo "" echo "Common commands:" echo " cargo build - Build project" echo " cargo test - Run tests" echo " cargo run - Run binary" echo "" export RUST_SRC_PATH=${pkgs.rustPlatform.rustLibSrc} ''; ``` ### 4. Separate Build and Runtime Dependencies ```nix # Build-time only nativeBuildInputs = [ pkg-config rustc cargo ]; # Runtime needed buildInputs = [ openssl ]; ``` ### 5. Disable Tests in Package Build ```nix packages.default = pkgs.rustPlatform.buildRustPackage { # ... doCheck = false; # Run tests separately with cargo test }; ``` **Why:** Faster builds, tests run via `cargo test` in dev shell --- ## Workflow Examples ### Daily Development ```bash # Start work cd grasp-audit nix develop # Inside nix shell cargo build cargo test cargo run -- --help # Exit shell exit ``` ### CI/CD ```bash # One-off commands (no interactive shell) nix develop -c cargo build nix develop -c cargo test --lib nix develop -c cargo test -- --ignored ``` ### Building Release ```bash # Build package directly nix build # Result is in ./result/bin/ ./result/bin/grasp-audit --version ``` --- ## References - **Nix Flakes Manual**: https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake.html - **rust-overlay**: https://github.com/oxalica/rust-overlay - **flake-utils**: https://github.com/numtide/flake-utils - **Our Migration**: `docs/archive/2025-11-04-flake-migration.md` --- ## Quick Reference | Task | Command | |------|---------| | Enter dev shell | `nix develop` | | Run one command | `nix develop -c ` | | Show outputs | `nix flake show` | | Validate flake | `nix flake check` | | Update inputs | `nix flake update` | | Build package | `nix build` | | Run package | `nix run` | --- *Last updated: November 4, 2025* *Status: Living document - update as we learn more*