4.0 KiB
Packaging
This note covers 02-package/, which builds a shell script into a Nix store path using stdenv.mkDerivation.
1. What nix build Does
When you run nix build inside 02-package/:
- Nix evaluates
flake.nixand produces a.drvfile (the build recipe). - Nix realises the
.drv: it runs the install phase inside a sandbox with no network and no access to files outside the declared inputs. - The output lands in
/nix/store/<hash>-greet-0.1.0/. - A
./resultsymlink is created pointing to that store path.
You can then run ./result/bin/greet directly, or use nix run which does step 1 through 3 and then executes the apps.<system>.default.program path.
2. stdenv.mkDerivation in Detail
stdenv.mkDerivation is the main builder in nixpkgs. It wraps the low-level derivation builtin with a phased build system. The default phases, in order:
unpackPhase: extractssrc(tarball, git checkout, or copied path).patchPhase: applies patches from thepatcheslist.configurePhase: runs./configureor cmake or similar.buildPhase: runsmakeor equivalent.installPhase: copies outputs into$out.fixupPhase: strips binaries, patches RPATHs, shrink-wraps references.
For a simple script, most of these are irrelevant. The 02-package example disables unpack and build with dontUnpack = true and dontBuild = true, then writes a custom installPhase.
3. Key Concepts in the Example
$out: every derivation has at least one output. $out is the default output path. The build must place files under $out or the derivation produces nothing.
substituteInPlace: a nixpkgs helper that does in-place string replacement. The example uses it to rewrite the shebang from #!/usr/bin/env bash to a store-path bash. This matters because /usr/bin/env may not exist on a pure NixOS system, and even where it does, it would pick up whichever bash happens to be on $PATH at runtime rather than the pinned one.
src = ./.: copies the entire flake directory into the store before the build starts. Changes to any file in the directory change the hash, which triggers a rebuild. For real projects you'd use lib.fileset or lib.cleanSource to exclude irrelevant files (editor backups, .git/, etc.).
apps.<system>.default: the nix run command looks for this attribute. Its program field must be an absolute store path to an executable.
4. The Build Sandbox
By default, Nix builds run inside a sandbox:
- No network access (except fixed-output derivations).
- No access to the host filesystem beyond explicitly declared inputs.
- No access to environment variables from the host.
- A restricted
/tmpfor scratch space.
This is what guarantees reproducibility: the build can only use what it declared.
If a build needs to download something (a source tarball, a Go module cache), it must be a fixed-output derivation. You provide the expected hash, and Nix verifies the output matches. See builtins.fetchurl, pkgs.fetchFromGitHub, and similar.
5. Commands to Try
cd 02-package
nix build # build and create ./result symlink
./result/bin/greet # run directly
./result/bin/greet "nix learner" # pass an argument
nix run # build + run via apps.default
nix run . -- "nix learner" # pass arguments after --
nix path-info ./result # show the store path
nix path-info -rS ./result # show closure size (all transitive deps)
nix derivation show ./result # inspect the .drv (inputs, env, builder)
6. Beyond Shell Scripts
The same stdenv.mkDerivation pattern scales to compiled languages. nixpkgs provides wrappers:
rustPlatform.buildRustPackagefor Rust (Cargo).buildGoModulefor Go.python3Packages.buildPythonPackagefor Python.mkDerivationdirectly for C/C++ (autotools, cmake, meson).
Each wrapper pre-configures the phases for its ecosystem. You supply src, a lock file hash, and metadata; the wrapper handles configure/build/install.