- Declarative
- Reproducible
- Functional
- Pure
- Lazy
Tell the computer what to do,
users.spacekookie.homeDir = "/home";not how to do it
# useradd spacekookie -d /homeThis system works in layers!
- Your configuration sets a value (
users.spacekookie.homeDir = "/home";) usersmodule provides options, and evaluates settings- Impure layer: (e.g.) run
useradd ..., conditionally based on whether that user already exists in passwd file, etc
- Nix configuration changes are idempotent
users.spacekookie = { createHome = true; homeDir = "/home"; extraGroups = [ "wheel" "dialout" ]; };
- The same build inputs yield the same build outputs
- Build inputs are hashed, and can be re-used if already present
- If build inputs may change, build sare fixed by their output hash (more on that later)
build-01-set = [ 9gkyl3knyalavd5v77rb0ciwry1r4v77-foo
gm1vihrf3d8hks2fgjfgfyn5wm2rs49a-bar ]
build-02-set = [ 9gkyl3knyalavd5v77rb0ciwry1r4v77-foo
psfi2l3kqpsp2zv66ngnaqhxnbzx1dn7-bar ]
Because the hash of the foo input is the same, it can be re-used
from the store. The bar package was updated, and thus needs to
be rebuilt!
- Usually located at
/nix/store - Special permission setup
- Owned by
root:nixbld - Write permission on
/nix/storefor owner and group - No write permissions for actual outputs (e.g.
/nix/store/<hash>-foo/) - Additional
tbit on/nix/store, which means that removing a file requires write-permission on the file, not just the containing directory.
- Owned by
$ /n/s/zzg015adjliwmdm4jfkbhnkpw6dmq1ym-urxvt-autocomplete-all-the-things-1.6.0> tree -p
.
└── [dr-xr-xr-x] lib
└── [dr-xr-xr-x] urxvt
└── [dr-xr-xr-x] perl
└── [-r-xr-xr-x] autocomplete-ALL-the-things
3 directories, 1 fileThe result of all this?
A nixbld user can create a new directory and store build outputs,
but never remove or change them again! Build artifacts are
read-only!
- Functions have no side-effects
- Inputs map directly to outputs
let
makeYouUnderstand = rickAstley {
giveYouUp = "never"; letYouDown = "never";
runAround = "never"; desertYou = "never";
makeyouCry = "never"; sayGoodbye = "never";
tellALie = "never"; hurtYou = "never";
};
in
{ }(no it doesn’t mean “it works”)
- Functions are first-class types in the Nix language
- Functions can be…
- passed as parameters
- returned as results
Syntax is Haskell inspired:
let cube = x: y: z: x * y * z;
in
(cube 2 4 8)Expressions are only evaluated when they are needed for the result of an operation.
{
never = abort "<oh-no.jpg>";
use = "I'm a String";
}.useAs in: why go through all this trouble
- Dependency closures solve a lot of problems
- Closures only work in a pure environment
- Pure environments demand certain design principles (functional, lazy, reproducible)
We’ll get into the specifics but there are two scenarios that closures can cover.
Two rules if you will…
| I’m building some software, and I don’t know the exact hash of what I will be producing! |
- You can do arbitrary computation
- Produce arbitrary outputs
- The build can not access the network!
| I’m fetching a source archive. I know exactly what I will produce, so give me network access! |
- Only run once: will not be built again after the source is fetched
- Arbitrary computation is still possible, but bulider can not produce arbitrary outputs
- The exact hash of this build must be known ahead of time