nix-playgraound/notes/004-packaging.md

91 lines
4.0 KiB
Markdown

# 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/`:
1. Nix evaluates `flake.nix` and produces a `.drv` file (the build recipe).
2. Nix realises the `.drv`: it runs the install phase inside a sandbox with no network and no access to files outside the declared inputs.
3. The output lands in `/nix/store/<hash>-greet-0.1.0/`.
4. A `./result` symlink 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:
1. `unpackPhase`: extracts `src` (tarball, git checkout, or copied path).
2. `patchPhase`: applies patches from the `patches` list.
3. `configurePhase`: runs `./configure` or cmake or similar.
4. `buildPhase`: runs `make` or equivalent.
5. `installPhase`: copies outputs into `$out`.
6. `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 `/tmp` for 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
```bash
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.buildRustPackage` for Rust (Cargo).
- `buildGoModule` for Go.
- `python3Packages.buildPythonPackage` for Python.
- `mkDerivation` directly 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.