WIP
This commit is contained in:
parent
d2814acdc9
commit
66d368555d
@ -4,47 +4,47 @@ Core vocabulary you'll hit in the first hour of Nix. Skim, don't memorize; come
|
||||
|
||||
## The Store and Builds
|
||||
|
||||
- **Nix store**: `/nix/store`. Immutable, content-addressed directory where *everything* Nix builds lives. Each entry is prefixed by a hash of its inputs.
|
||||
- **Store path**: a single entry under `/nix/store`, e.g. `/nix/store/abc123…-hello-2.12.1`. The hash makes it unique per set of build inputs.
|
||||
- **Derivation (`.drv`)**: a *recipe* for a build, covering inputs, builder script, env vars, and outputs. Produced by evaluating Nix code. Not the built artifact itself.
|
||||
- **Realisation / "realising a derivation"**: actually executing the `.drv` to produce the output store path(s). "Build" colloquially means this.
|
||||
- **Output path**: the store path a derivation produces. A derivation can have multiple outputs (e.g. `out`, `dev`, `man`, `lib`).
|
||||
- **Closure**: a store path plus all its runtime dependencies, transitively. What you need to copy to another machine to actually use something.
|
||||
- **Hash**: content/input hash that names store paths. Changing any input changes the hash, which changes the path; this is how Nix guarantees no cache collisions.
|
||||
- **Fixed-output derivation (FOD)**: a derivation whose output hash you declare up front (e.g. source tarballs, `fetchurl`). Allowed network access during build; everything else is sandboxed offline.
|
||||
- Nix store: `/nix/store`. Immutable, content-addressed directory where *everything* Nix builds lives. Each entry is prefixed by a hash of its inputs.
|
||||
- Store path: a single entry under `/nix/store`, e.g. `/nix/store/abc123…-hello-2.12.1`. The hash makes it unique per set of build inputs.
|
||||
- Derivation (`.drv`): a *recipe* for a build, covering inputs, builder script, env vars, and outputs. Produced by evaluating Nix code. Not the built artifact itself.
|
||||
- Realisation, or "realising a derivation": actually executing the `.drv` to produce the output store path(s). "Build" colloquially means this.
|
||||
- Output path: the store path a derivation produces. A derivation can have multiple outputs (e.g. `out`, `dev`, `man`, `lib`).
|
||||
- Closure: a store path plus all its runtime dependencies, transitively. What you need to copy to another machine to actually use something.
|
||||
- Hash: content/input hash that names store paths. Changing any input changes the hash, which changes the path; this is how Nix guarantees no cache collisions.
|
||||
- Fixed-output derivation (FOD): a derivation whose output hash you declare up front (e.g. source tarballs, `fetchurl`). Allowed network access during build; everything else is sandboxed offline.
|
||||
|
||||
## Language
|
||||
|
||||
- **Nix (the language)**: lazy, purely functional, dynamically typed. Every `.nix` file is one expression.
|
||||
- **Attrset**: `{ a = 1; b = "x"; }`. The main data structure.
|
||||
- **Lambda**: `x: x + 1` or `{ a, b }: a + b`. Functions take one argument; multi-arg functions are attrset-destructured.
|
||||
- **`let ... in`**: local bindings, for example `let x = 1; in x + 1`.
|
||||
- **`with expr; body`**: brings `expr`'s attrs into scope for `body`. Common in `with pkgs; [ hello jq ]`. Useful but can shadow bindings, so use sparingly.
|
||||
- **`inherit`**: shorthand to pull names from outer scope into an attrset: `{ inherit pkgs system; }` ≡ `{ pkgs = pkgs; system = system; }`.
|
||||
- **`import`**: evaluate another `.nix` file. `import ./foo.nix { }` imports and calls it.
|
||||
- **`callPackage`**: a nixpkgs convention that auto-supplies function arguments from a package set. You'll see it everywhere in nixpkgs.
|
||||
- Nix (the language): lazy, purely functional, dynamically typed. Every `.nix` file is one expression.
|
||||
- Attrset: `{ a = 1; b = "x"; }`. The main data structure.
|
||||
- Lambda: `x: x + 1` or `{ a, b }: a + b`. Functions take one argument; multi-arg functions are attrset-destructured.
|
||||
- `let ... in`: local bindings, for example `let x = 1; in x + 1`.
|
||||
- `with expr; body`: brings `expr`'s attrs into scope for `body`. Common in `with pkgs; [ hello jq ]`. Useful but can shadow bindings, so use sparingly.
|
||||
- `inherit`: shorthand to pull names from outer scope into an attrset: `{ inherit pkgs system; }` ≡ `{ pkgs = pkgs; system = system; }`.
|
||||
- `import`: evaluate another `.nix` file. `import ./foo.nix { }` imports and calls it.
|
||||
- `callPackage`: a nixpkgs convention that auto-supplies function arguments from a package set. You'll see it everywhere in nixpkgs.
|
||||
|
||||
## Package Management
|
||||
|
||||
- **nixpkgs**: the community-maintained collection of Nix expressions for ~100k packages. Lives at `github:NixOS/nixpkgs`.
|
||||
- **Channel**: a named, periodically-updated snapshot of nixpkgs (e.g. `nixos-unstable`, `nixos-25.11`). Pre-flakes way to pin. Flakes mostly replace this with lockfiles.
|
||||
- **Overlay**: a function that extends or overrides a package set. Lets you patch, pin, or add packages without forking nixpkgs.
|
||||
- **Profile**: a symlink tree representing "what's installed" for a user or system. `nix-env`, `nix profile`, NixOS, and home-manager all manage profiles.
|
||||
- **Generation**: a versioned snapshot of a profile. Every change creates a new generation; old ones stay until garbage-collected. This is how rollback works.
|
||||
- **Garbage collection (`nix-collect-garbage`)**: deletes store paths not reachable from any "GC root" (profiles, running processes, and `result` symlinks).
|
||||
- nixpkgs: the community-maintained collection of Nix expressions for ~100k packages. Lives at `github:NixOS/nixpkgs`.
|
||||
- Channel: a named, periodically-updated snapshot of nixpkgs (e.g. `nixos-unstable`, `nixos-25.11`). Pre-flakes way to pin. Flakes mostly replace this with lockfiles.
|
||||
- Overlay: a function that extends or overrides a package set. Lets you patch, pin, or add packages without forking nixpkgs.
|
||||
- Profile: a symlink tree representing "what's installed" for a user or system. `nix-env`, `nix profile`, NixOS, and home-manager all manage profiles.
|
||||
- Generation: a versioned snapshot of a profile. Every change creates a new generation; old ones stay until garbage-collected. This is how rollback works.
|
||||
- Garbage collection (`nix-collect-garbage`): deletes store paths not reachable from any "GC root" (profiles, running processes, and `result` symlinks).
|
||||
|
||||
## Flakes
|
||||
|
||||
- **Flake**: a directory with a `flake.nix`. Has pinned `inputs` and structured `outputs`. Reproducible, composable, and schema-driven.
|
||||
- **`flake.lock`**: JSON file pinning the exact revision (and hash) of each input. Commit it.
|
||||
- **Flake reference**: URL-like string identifying a flake: `github:NixOS/nixpkgs`, `path:./foo`, `git+https://…`, or `nixpkgs` (registry alias).
|
||||
- **Registry**: short-name to flake-ref mapping. `nixpkgs` resolves via the global registry by default.
|
||||
- **Pure evaluation**: flakes evaluate in a sandbox with no env vars, no arbitrary filesystem reads, and only declared inputs. This is what makes them reproducible.
|
||||
- Flake: a directory with a `flake.nix`. Has pinned `inputs` and structured `outputs`. Reproducible, composable, and schema-driven.
|
||||
- `flake.lock`: JSON file pinning the exact revision (and hash) of each input. Commit it.
|
||||
- Flake reference: URL-like string identifying a flake: `github:NixOS/nixpkgs`, `path:./foo`, `git+https://…`, or `nixpkgs` (registry alias).
|
||||
- Registry: short-name to flake-ref mapping. `nixpkgs` resolves via the global registry by default.
|
||||
- Pure evaluation: flakes evaluate in a sandbox with no env vars, no arbitrary filesystem reads, and only declared inputs. This is what makes them reproducible.
|
||||
|
||||
## NixOS and home-manager
|
||||
|
||||
- **NixOS module**: a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration.
|
||||
- **home-manager**: user-level declarative config for dotfiles, per-user packages, and services. Works standalone or as a NixOS module.
|
||||
- NixOS module: a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration.
|
||||
- home-manager: user-level declarative config for dotfiles, per-user packages, and services. Works standalone or as a NixOS module.
|
||||
|
||||
## CLI: Old vs. New
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
# Nix Primer
|
||||
|
||||
Nix is **two things bundled**:
|
||||
Nix is two things bundled:
|
||||
|
||||
1. **A pure, lazy functional language** for describing builds.
|
||||
2. **A package manager** that takes those descriptions and produces content-addressed store paths.
|
||||
1. A pure, lazy functional language for describing builds.
|
||||
2. A package manager that takes those descriptions and produces content-addressed store paths.
|
||||
|
||||
Confusingly, both are called "Nix." Separate them in your head and everything gets easier.
|
||||
|
||||
@ -20,8 +20,8 @@ Every `nix build` or `nix develop` runs in two phases:
|
||||
(pure, lazy) (on disk) (content-addressed)
|
||||
```
|
||||
|
||||
- **Evaluation** runs the Nix language. Pure, in-memory, fast. Produces `.drv` files.
|
||||
- **Realisation** actually builds things. Sandboxed. Slow. Outputs go to `/nix/store`.
|
||||
- Evaluation runs the Nix language. Pure, in-memory, fast. Produces `.drv` files.
|
||||
- Realisation actually builds things. Sandboxed. Slow. Outputs go to `/nix/store`.
|
||||
|
||||
You can evaluate without building (`nix eval`, `nix-instantiate`) and you can build without touching the language if you already have a `.drv`. Most errors you'll see are *eval* errors: typos in attrsets, missing arguments, and undefined names.
|
||||
|
||||
@ -107,7 +107,7 @@ import ./foo.nix { x = 1; } # if foo.nix is a function, also call it
|
||||
|
||||
## 3. Derivations: The Core Primitive
|
||||
|
||||
A **derivation** is "a build, described as data." You create one with `derivation { … }` (low-level) or `pkgs.stdenv.mkDerivation { … }` (the nixpkgs wrapper you'll actually use).
|
||||
A derivation is "a build, described as data." You create one with `derivation { … }` (low-level) or `pkgs.stdenv.mkDerivation { … }` (the nixpkgs wrapper you'll actually use).
|
||||
|
||||
Minimal example:
|
||||
|
||||
@ -129,10 +129,10 @@ pkgs.stdenv.mkDerivation {
|
||||
|
||||
Key ideas:
|
||||
|
||||
- **`$out`** is the output store path the build writes into.
|
||||
- **Phases** (`unpackPhase`, `buildPhase`, `installPhase`, …) are shell snippets `stdenv` runs in order. Override the ones you need.
|
||||
- **No network** in the build sandbox, except for fixed-output derivations (whose hash you declare up front).
|
||||
- **Every input** (source, compiler, env vars) becomes part of the hash. Change anything and you get a fresh store path.
|
||||
- `$out` is the output store path the build writes into.
|
||||
- Phases (`unpackPhase`, `buildPhase`, `installPhase`, …) are shell snippets `stdenv` runs in order. Override the ones you need.
|
||||
- No network in the build sandbox, except for fixed-output derivations (whose hash you declare up front).
|
||||
- Every input (source, compiler, env vars) becomes part of the hash. Change anything and you get a fresh store path.
|
||||
|
||||
---
|
||||
|
||||
@ -140,13 +140,13 @@ Key ideas:
|
||||
|
||||
Traditional package managers put files in `/usr/bin`, `/usr/lib`, and similar shared locations. One version at a time. Upgrades mutate shared state.
|
||||
|
||||
Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from **all its inputs** (recursively). That gives you:
|
||||
Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from all its inputs (recursively). That gives you:
|
||||
|
||||
- **Atomic upgrades and rollbacks**: switching "versions" is just switching symlinks.
|
||||
- **Multiple versions coexisting**: they live at different hashes.
|
||||
- **Reproducibility**: same inputs produce the same hash and the same output (in theory; in practice, 99% there).
|
||||
- **Binary caching**: if someone else already built exactly these inputs, you can download their output. This is what `cache.nixos.org` does.
|
||||
- **Garbage-collectable**: anything not referenced by a "GC root" can be deleted safely.
|
||||
- Atomic upgrades and rollbacks: switching "versions" is just switching symlinks.
|
||||
- Multiple versions coexisting: they live at different hashes.
|
||||
- Reproducibility: same inputs produce the same hash and the same output (in theory; in practice, 99% there).
|
||||
- Binary caching: if someone else already built exactly these inputs, you can download their output. This is what `cache.nixos.org` does.
|
||||
- Garbage-collectable: anything not referenced by a "GC root" can be deleted safely.
|
||||
|
||||
---
|
||||
|
||||
@ -154,12 +154,12 @@ Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from *
|
||||
|
||||
Ordered roughly by power-to-complexity:
|
||||
|
||||
1. **`nix run nixpkgs#cowsay -- moo`**: run a package without installing.
|
||||
2. **`nix shell nixpkgs#jq nixpkgs#ripgrep`**: throwaway shell with those tools.
|
||||
3. **Dev shell via `flake.nix`**: per-project pinned toolchain. *This is the most common thing flakes are used for.*
|
||||
4. **`nix build`**: produce artifacts (binaries, containers, and ISOs).
|
||||
5. **NixOS**: declare your whole OS config (packages, services, users, and network) in Nix.
|
||||
6. **home-manager**: same idea, at the user level.
|
||||
1. `nix run nixpkgs#cowsay -- moo`: run a package without installing.
|
||||
2. `nix shell nixpkgs#jq nixpkgs#ripgrep`: throwaway shell with those tools.
|
||||
3. Dev shell via `flake.nix`: per-project pinned toolchain. *This is the most common thing flakes are used for.*
|
||||
4. `nix build`: produce artifacts (binaries, containers, and ISOs).
|
||||
5. NixOS: declare your whole OS config (packages, services, users, and network) in Nix.
|
||||
6. home-manager: same idea, at the user level.
|
||||
|
||||
Each step up locks in more of your environment. You don't need to adopt all of it at once.
|
||||
|
||||
@ -189,12 +189,12 @@ nix why-depends ./result nixpkgs#glibc # trace dependency chains
|
||||
|
||||
## 7. Gotchas That Trip Up Newcomers
|
||||
|
||||
- **Lists use spaces, not commas.** `[ 1 2 3 ]`. Commas are a syntax error.
|
||||
- **`rec { }`** makes an attrset self-referential. Without `rec`, attrs can't reference each other. You'll see this in package definitions.
|
||||
- **Paths vs. strings.** `./foo` is a path (copied to store on use). `"./foo"` is a string (not auto-copied). Mixing them causes surprises.
|
||||
- **`builtins.*`** is the always-available low-level namespace. `lib.*` (from nixpkgs) is the high-level helper library. Both exist; they're different.
|
||||
- **Evaluation errors are lazy.** An error deep in an unused branch only fires when you actually use it. `nix-instantiate --eval --strict` forces full evaluation.
|
||||
- **Hash errors on first build of an FOD**: you'll see `got: sha256-…`. Copy that hash into your expression. This is expected workflow.
|
||||
- Lists use spaces, not commas. `[ 1 2 3 ]`. Commas are a syntax error.
|
||||
- `rec { }` makes an attrset self-referential. Without `rec`, attrs can't reference each other. You'll see this in package definitions.
|
||||
- Paths vs. strings. `./foo` is a path (copied to store on use). `"./foo"` is a string (not auto-copied). Mixing them causes surprises.
|
||||
- `builtins.*` is the always-available low-level namespace. `lib.*` (from nixpkgs) is the high-level helper library. Both exist; they're different.
|
||||
- Evaluation errors are lazy. An error deep in an unused branch only fires when you actually use it. `nix-instantiate --eval --strict` forces full evaluation.
|
||||
- Hash errors on first build of an FOD: you'll see `got: sha256-…`. Copy that hash into your expression. This is expected workflow.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# Flakes
|
||||
|
||||
A **flake** is a directory containing `flake.nix` (and, once evaluated, `flake.lock`). It's a packaging convention on top of regular Nix that gives you:
|
||||
A flake is a directory containing `flake.nix` (and, once evaluated, `flake.lock`). It's a packaging convention on top of regular Nix that gives you:
|
||||
|
||||
- **Pinned inputs** via `flake.lock` for reproducible builds.
|
||||
- **A fixed output schema** so tools know where to look (`devShells.<system>.default`, `packages.<system>.default`, and so on).
|
||||
- **Composability**, since flakes depend on other flakes by URL.
|
||||
- **Pure evaluation**, with no silent dependence on `NIX_PATH`, env vars, or random files.
|
||||
- Pinned inputs via `flake.lock` for reproducible builds.
|
||||
- A fixed output schema so tools know where to look (`devShells.<system>.default`, `packages.<system>.default`, and so on).
|
||||
- Composability, since flakes depend on other flakes by URL.
|
||||
- Pure evaluation, with no silent dependence on `NIX_PATH`, env vars, or random files.
|
||||
|
||||
Flakes are still marked "experimental" but are now the way most new Nix code is written.
|
||||
|
||||
@ -35,7 +35,7 @@ Two top-level keys matter: `inputs` and `outputs`. `description` is cosmetic.
|
||||
|
||||
### Inputs
|
||||
|
||||
Each input has a **flake reference** URL. Common forms:
|
||||
Each input has a flake reference URL. Common forms:
|
||||
|
||||
```
|
||||
github:owner/repo # latest default branch
|
||||
@ -81,10 +81,10 @@ Default attrs `default` are picked when you omit the name: `nix build` ≡ `nix
|
||||
|
||||
`flake.lock` pins the exact commit and NAR hash of every input, transitively. Generated or updated automatically on first use or via `nix flake update`.
|
||||
|
||||
- **Commit it.** Without it, "my flake" means different things on different machines.
|
||||
- **Update explicitly.** `nix flake update` (all inputs) or `nix flake update nixpkgs` (one input).
|
||||
- **Inspect.** `nix flake metadata` prints the resolved URLs and revisions.
|
||||
- **`follows`** deduplicates: if both `flake-utils` and `foo` want their own `nixpkgs`, `follows = "nixpkgs";` forces them to use yours. Without this, you can end up with multiple nixpkgs copies and subtle version skew.
|
||||
- Commit it. Without it, "my flake" means different things on different machines.
|
||||
- Update explicitly. `nix flake update` (all inputs) or `nix flake update nixpkgs` (one input).
|
||||
- Inspect. `nix flake metadata` prints the resolved URLs and revisions.
|
||||
- `follows` deduplicates: if both `flake-utils` and `foo` want their own `nixpkgs`, `follows = "nixpkgs";` forces them to use yours. Without this, you can end up with multiple nixpkgs copies and subtle version skew.
|
||||
|
||||
---
|
||||
|
||||
@ -149,9 +149,9 @@ Flakes evaluate in pure mode:
|
||||
- No reading `NIX_PATH` or `<nixpkgs>`.
|
||||
- No arbitrary `builtins.getEnv` or reading files outside the flake.
|
||||
- `builtins.currentTime` and `builtins.currentSystem` are blocked.
|
||||
- Source is pulled from the flake's input tree, not the working directory, unless you're in `self`. Files not tracked by git are **invisible** to the flake by default.
|
||||
- Source is pulled from the flake's input tree, not the working directory, unless you're in `self`. Files not tracked by git are invisible to the flake by default.
|
||||
|
||||
This last point surprises people: **`git add` a file before `nix build` can see it.** (`nix build --impure` escapes this, but then you're not reproducible.)
|
||||
This last point surprises people: `git add` a file before `nix build` can see it. (`nix build --impure` escapes this, but then you're not reproducible.)
|
||||
|
||||
In exchange: anyone with your `flake.nix` plus `flake.lock` gets bit-identical evaluation.
|
||||
|
||||
@ -223,11 +223,11 @@ overlays.default = final: prev: {
|
||||
|
||||
## 8. Debugging Tips
|
||||
|
||||
- **`nix flake show --all-systems`** to see outputs for every platform, not just yours.
|
||||
- **`nix eval .#packages.x86_64-linux.default.drvPath`** to get the `.drv` without building.
|
||||
- **`nix eval --json .#devShells.x86_64-linux.default.buildInputs | jq`** to inspect shell deps.
|
||||
- **`--show-trace`** on any command for full eval backtraces.
|
||||
- **`nix repl .`** loads your flake's outputs into a REPL: useful for poking at attrs.
|
||||
- `nix flake show --all-systems` to see outputs for every platform, not just yours.
|
||||
- `nix eval .#packages.x86_64-linux.default.drvPath` to get the `.drv` without building.
|
||||
- `nix eval --json .#devShells.x86_64-linux.default.buildInputs | jq` to inspect shell deps.
|
||||
- `--show-trace` on any command for full eval backtraces.
|
||||
- `nix repl .` loads your flake's outputs into a REPL: useful for poking at attrs.
|
||||
- If an input seems stuck on an old rev, check `flake.lock`; `nix flake update <name>` fixes it.
|
||||
|
||||
---
|
||||
@ -238,4 +238,4 @@ overlays.default = final: prev: {
|
||||
- A tiny personal script: plain `default.nix` plus `nix-build` is still fine.
|
||||
- Large monorepos with complex CI: consider `flake-parts` or hybrid setups (flake for the interface, regular Nix for guts).
|
||||
|
||||
Flakes are a **packaging interface**, not a requirement. The old non-flake world still works; flakes just compose better once you have more than one Nix project.
|
||||
Flakes are a packaging interface, not a requirement. The old non-flake world still works; flakes just compose better once you have more than one Nix project.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user