Derivations¶
A derivation is Nix's unit of build. It describes how to produce one or more store paths from inputs, a builder, and environment variables. Derivations are stored in the Nix store as .drv files in ATerm format.
ATerm format¶
A .drv file is a single ATerm expression:
Derive(
[("out","/nix/store/...-hello","",""), ...], # outputs
[("/nix/store/...-dep.drv",["out"]), ...], # input derivations
["/nix/store/...-source", ...], # input sources
"x86_64-linux", # platform
"/nix/store/...-bash/bin/bash", # builder
["--", "-e", "..."], # arguments
[("key","value"), ...] # environment
)
Fields¶
| # | Field | Type | Description |
|---|---|---|---|
| 1 | outputs | [(name, path, hashAlgo, hash)] |
Output name-to-path mapping |
| 2 | inputDrvs | [(drvPath, [outputNames])] |
Derivation dependencies |
| 3 | inputSrcs | [path] |
Source file dependencies |
| 4 | platform | string |
Build platform (e.g. x86_64-linux) |
| 5 | builder | string |
Path to the builder executable |
| 6 | args | [string] |
Builder command-line arguments |
| 7 | env | [(key, value)] |
Environment variables for the builder |
Outputs¶
Each output is a 4-tuple:
| Field | Normal output | Fixed-output |
|---|---|---|
name |
"out", "lib", "dev", etc. |
"out" |
path |
/nix/store/...-name |
/nix/store/...-name |
hashAlgo |
"" |
"sha256", "r:sha256", etc. |
hash |
"" |
hex hash of expected content |
The r: prefix in hashAlgo means recursive (NAR hash). Without it, the hash is of the flat file content.
String escaping¶
Strings in ATerm use these escape sequences:
| Escape | Character |
|---|---|
\\ |
Backslash |
\" |
Double quote |
\n |
Newline |
\r |
Carriage return |
\t |
Tab |
Ordering¶
In the canonical serialization:
- Outputs are sorted by output name
- Input derivations are sorted by
.drvpath - Input sources are sorted
- Environment variables are sorted by key
- Arguments preserve their original order
hashDerivationModulo¶
This is the algorithm Nix uses to compute the hash that determines output paths. It exists to break circular dependencies — an output path depends on the derivation hash, but the derivation contains its own output paths.
Fixed-output derivations¶
For derivations with a single output "out" that has a hashAlgo set:
This means fixed-output derivations have stable hashes that don't change when their build dependencies change — only the expected output hash matters.
Regular derivations¶
For all other derivations:
- Optionally blank output paths: Replace output paths with
""in both.outputsand.env(see two modes below) - Replace input drv paths: For each input derivation, replace the
.drvpath with the hex-encodedhashDerivationModuloof that input - Serialize: Convert the masked derivation back to ATerm
- Hash:
sha256(serialized_masked_drv)
┌─────────────────────────────────────────┐
│ Original .drv │
│ │
│ outputs: ("out", "/nix/store/abc-x") │
│ inputDrvs: ("/nix/store/def.drv",["out"])│
│ ... │
└──────────────────┬──────────────────────┘
│
mask & replace
│
v
┌─────────────────────────────────────────┐
│ Masked .drv │
│ │
│ outputs: ("out", "") ← blanked │
│ inputDrvs: ("a1b2c3...",["out"]) ← hash │
│ ... │
└──────────────────┬──────────────────────┘
│
serialize to ATerm string
│
v
sha256(aterm_string)
│
v
derivation hash (32 bytes)
Two modes: maskOutputs¶
hashDerivationModulo takes a maskOutputs parameter that controls whether output paths are blanked. Nix uses two different call sites:
| Call site | maskOutputs |
Output paths | Used for |
|---|---|---|---|
staticOutputHashes |
true |
blanked ("") |
Computing a derivation's own output paths |
pathDerivationModulo |
false |
kept | Computing the hash of an input derivation |
This distinction matters: when derivation A depends on derivation B, Nix computes B's hash with maskOutputs=false — B's filled output paths are part of the hash used as A's inputDrvs key. Only A's own outputs are blanked (to break A's circularity).
Computing output path for consumer.drv:
dep.drv (input) consumer.drv (self)
───────────── ───────────────────
maskOutputs = false maskOutputs = true
output paths KEPT output paths BLANKED
│ │
v v
dep_hash (includes consumer_hash
dep's output path) │
│ v
└──── used as ──────> inputDrvs key
in consumer's masked ATerm
Common pitfall
Using maskOutputs=true for input derivations produces the wrong hash — the input's output path is lost, changing the consumer's inputDrvs key and ultimately its output path. This is the difference between staticOutputHashes (for self) and pathDerivationModulo (for inputs).
Computing output paths¶
Once you have the derivation hash from hashDerivationModulo:
from pix.store_path import make_output_path
output_path = make_output_path(drv_hash, "out", "hello-2.12.2")
# /nix/store/<hash>-hello-2.12.2
The type prefix is output:<output-name>, so the fingerprint becomes:
Real-world example¶
A minimal derivation (builtins.derivation { name = "hello"; builder = "/bin/sh"; args = ["-c" "echo hello > $out"]; system = "x86_64-linux"; }):
Derive(
[("out","/nix/store/...-hello","","")],
[],
[],
"x86_64-linux",
"/bin/sh",
["-c","echo hello > $out"],
[("builder","/bin/sh"),
("name","hello"),
("out","/nix/store/...-hello"),
("system","x86_64-linux")]
)
Note that the environment contains both explicit env vars and automatic ones (builder, out, system, name).