# Nix Pills - notes ###### tags: `INFO5` `Nix` **Author: @Coko** Nix Pills by **Luca Bruno** (aka Lethalman): https://nixos.org/guides/nix-pills ## Chapter 1: [Why You Should Give it a Try](https://nixos.org/guides/nix-pills/why-you-should-give-it-a-try.html) ## Chapter 2: [Install on Your Running System](https://nixos.org/guides/nix-pills/install-on-your-running-system.html) ### Install Nix: ```bash curl -L https://nixos.org/nix/install | sh ``` ### Useful directories: - ~/.nix-profile - ~/.nix-defexpr/channels - ~/.profile - /nix/store/ - /nix/var/nix/gcroots - /nix/var/nix/gcroots/auto/ Enter the Nix env: `source ~/.nix-profile/etc/profile.d/nix.sh` Nix operators list: https://nixos.org/manual/nix/stable/expressions/language-operators.html ### Common `nix repl` built-ins: - `builtins.div <a> <b>`: Division of **a** by **b**. - `builtins.trace <msg> <value>`: Function that takes two arguments. The first is the **message to display**, the second is the **value to return**. It's usually used for debugging purpose. - `builtins.currentSystem`: Shows host system. (ex: *"x86_64-linux"*) - `builtins.isAttrs <a>`: Returns true if the argument is a set. - `builtins.attrNames <set>`: Returns a list of keys of the given set. - `builtins.toString <obj>`: Converts object to a string. - `builtins.functionArgs <fun>`: Lists the argument names for a given function and show whether they are optional. - `builtins.intersectAttrs <set1> <set2>`: Returns a set whose names are the intersection, and the attribute values are taken from the second set. ### Other nix commands: - `nix-instantiate <d>`: Instantiate store derivations from Nix expressions. - `nix-store --dump <path>`: Produce a NAR (Nix ARchive) file containing the contents of the file system tree rooted at path. The archive is written to standard output. - `nix-store --restore <path>`: Unpack a NAR archive to path, which must not already exist. The archive is read from standard input. - `nix-shell`: Start an interactive shell based on a Nix expression. - `nix-collect-garbage`: Delete unreachable store paths. - `nix-collect-garbage -d`: Delete older generations on all profiles. Used to clean up the system. - `nix-hash --type sha256 <file>`: Compute the sha256 of a file. - `nix-store --dump <file>|sha256sum`: Same as above. ## Chapter 3: [Enter the Environment](https://nixos.org/guides/nix-pills/enter-environment.html#enter-environment) ### Install stuff install package hello: `nix-env -i hello` remove package hello: `nix-env -e hello` list generations: `nix-env --list-generations` list generations (+ actual location): `nix-env -q --out-path` list installed derivations: `nix-env -q` rollback to previous generation: `nix-env --rollback` select specific generation (3): `nix-env -G 3` */.nix-profile/bin is at the HEAD of $PATH. So, installing a package which is already installed on the host system will result in the nix package to be used instead of the default system installation. Try installing `man` and run `which man`. The current installation of `man` should point to `/home/user/.nix-profile/bin/man`.* ### Querying the store show the direct runtime dependencies of hello: ```nix-store -q --references `which hello` ``` show reverse dependencies of hello: ```nix-store -q --referrers `which hello` ``` ### Closures The closures of a derivation is a list of all its dependencies, recursively, including absolutely everything necessary to use that derivation. ```nix-store -qR `which man` ``` ```nix-store -q --tree `which man` ``` ```nix-store -q --tree ~/.nix-profile``` ### Recovery remove everything: `nix-env -e '*'` rollback manually: `/nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env --rollback` To restore Nix, you can also use Nix to install a new version of Nix: `/nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env -i /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env ` ### Channels List Nix channels (to download package): `nix-channel --list` Same as viewing content of file at `~/.nix-channels`. update channel: `nix-channel --update` upgrade all packages: `nix-env -u` ## Chapter 4: [The Basics of the Language](https://nixos.org/guides/nix-pills/basics-of-language.html) ### Value types Launch nix-repl (CLI to test out the Nix language): `nix repl` (`:?` for help, `:q` to quit) Structures: - Identifiers (can be with dash, ex: `a-b` is a valid var name) - Strings = "test" (use `${var}` to print value of var in str, you cannot use integers directly though: ~~`${5+5}`~~) To print integers directly in string, we need to use the **builtins toString function**: `"this is the number ${builtins.toString 5}"` - Lists = [true 725 "test" (7*7)+1] - Sets: - = { firstname = "john"; lastname = "doe"; age = 25; } - `a.firstname => "john"` - `a."firstname" => "john"` - Use recursive attr sets when elements must reference other elements: ```nix s = { a = 12; b = 8 + a; } # invalid s = rec { a = 12; b = 8 + a; } # valid ``` - If expressions (else branch always required): ```nix nix-repl> a = 3 nix-repl> b = 4 nix-repl> if a > b then "yes" else "no" "no" ``` - Let - With (some kind of import function) Nix is lazy, expressions not used are not evaluated: ```nix nix-repl> let a = builtins.div 4 0; b = 6; in b 6 # no error even though we devided by 0 ``` ## Chapter 5: [Functions and Imports](https://nixos.org/guides/nix-pills/functions-and-imports.html) ### Functions Nix docs on functions: [here](https://nixos.org/manual/nix/stable/expressions/language-constructs.html) ```nix nix-repl> hello = s: "Hello ${s.firstname} ${s.lastname}!" nix-repl> hello { firstname = "John"; lastname = "Doe"; } "Hello John Doe!" ``` default values : `mul = { a, b ? 2 }: a*b` variadic (optional attributes): `mul = { a, b, ... }: a*b` access variadic: `mul = s@{ a, b, ... }: a*b*s.c` (you need to name the set => prefix with **@-pattern**) ### Imports Simple: import atomic expressions ```nix nix-repl> a = import ./a.nix nix-repl> b = import ./b.nix nix-repl> mul = import ./mul.nix nix-repl> mul a b 12 ``` Import more complex expressions (functions) ```nix # file: 'test.nix' { a, b ? 3, trueMsg ? "yes", falseMsg ? "no" }: if a > b then builtins.trace trueMsg true else builtins.trace falseMsg false ``` ```nix nix-repl> import ./test.nix { a = 5; trueMsg = "ok"; } trace: ok true ``` ## Chapter 6: [Our First Derivation](https://nixos.org/guides/nix-pills/our-first-derivation.html) ### Derivation function The derivation function receives a set as first argument. This set requires at least the following three attributes: - **name:** the name of the derivation. In the nix store the format is hash-name, that's the name. - **system:** is the name of the system in which the derivation can be built. For example, x86_64-linux. - **builder:** it is the binary program that builds the derivation. ```nix # example with fake system d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } ``` ### What are .drv files? Before continuing, some analogies with the C language: - .nix files are like .c files - .drv files are intermediate files like .o files. The .drv describes how to build a derivation, it's the bare minimum information. - out paths are then the product of the build Pretty print .drv file: ```nix nix show-derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv ``` ```nix { "/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv": { "outputs": { "out": { "path": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" } }, "inputSrcs": [], "inputDrvs": {}, "system": "mysystem", "builder": "mybuilder", "args": [], "env": { "builder": "mybuilder", "name": "myname", "out": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname", "system": "mysystem" } } } ``` **Important:** the hash of the out path is based solely on the input derivations in the current version of Nix, not on the contents of the build product. It's possible however to have content-addressable derivations for e.g. tarballs as we'll see later on. summary of .drv format: 1. Output paths (there can be multiple ones). By default nix creates one out path called "out". 2. List of input derivations. It's empty because we are not referring to any other derivation. Otherwise, there would be a list of other .drv files. 3. System and the builder executable. 4. List of environment variables passed to the builder. ### Build derivation - In `nix repl`: ```nix :b <drv> # build derivation drv ``` - Outside `nix repl`: ```sh nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv ``` Man page information on `nix-store -r` ```sh nix-store --help | grep -E "\--realise" ``` ### Referring to other derivations `ls /nix/store/*coreutils*/bin` ## Chapter 7: [Working Derivation](https://nixos.org/guides/nix-pills/working-derivation.html) ### Using a script as a builder `declare -xp` lists exported variables (`declare` is a builtin bash function) ```bash # builder.sh declare -xp echo foo > $out ``` ```nix nix-repl> d = derivation { name = "foo"; builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; system = builtins.currentSystem; } nix-repl> :b d This derivation produced the following outputs: out -> /nix/store/pd1v1m42bna50p8ap9caaf74v09mkyqk-foo [1 built (2 failed), 0.0 MiB DL] ``` ### Packaging a simple C program ```c= // simple.c void main() { puts("Simple!"); } ``` ```bash= export PATH="$coreutils/bin:$gcc/bin" mkdir $out gcc -o $out/simple $src ``` ```nix nix-repl> :l <nixpkgs> nix-repl> simple = derivation { name = "simple"; builder = "${bash}/bin/bash"; args = [ ./simple_builder.sh ]; gcc = gcc; coreutils = coreutils; src = ./simple.c; system = builtins.currentSystem; } nix-repl> :b simple This derivation produced the following outputs: out -> /nix/store/9mzvf0aw37y2pq8fqrs2p2iyqlgx6bw0-simple [2 built (3 failed), 0.0 MiB DL] ``` ```bash $ /nix/store/9mzvf0aw37y2pq8fqrs2p2iyqlgx6bw0-simple/simple Simple! ``` ### Outside of nix repl ```nix= # simple.nix # import <nixpkgs> and execute it on the empty set {}. # "import <nixpkgs> {}" returns a set of derivations. # these derivations are brought into scope thanks to the "with" keyword. # in the end, the line is equivalent to typing ":l <nixpkgs>" in nix repl. with (import <nixpkgs> {}); derivation { name = "simple"; builder = "${bash}/bin/bash"; args = [ ./simple_builder.sh ]; # inherit copies variables from into the scope. # "inherit x" is equivalent to "x = x". inherit gcc coreutils; src = ./simple.c; system = builtins.currentSystem; } ``` Build derivation with `nix-build simple.nix`. What it does behind the hood: - `nix-instantiate`: parse and evaluate simple.nix and return the .drv file corresponding to the parsed derivation set - `nix-store -r`: realise the .drv file, which actually builds it. Finally, it creates the symlink. ## Chapter 8: [Generic Builders](https://nixos.org/guides/nix-pills/generic-builders.html) ### A generic builder ```bash= # builder.sh # set to exit the build on any error set -e # reset PATH unset PATH # fill the PATH variable using $buildInputs for p in $buildInputs; do export PATH=$p/bin${PATH:+:}$PATH done # unpack the source tar -xf $src # find directory where source has been unpacked and cd into it for d in *; do if [ -d "$d" ]; then cd "$d" break fi done ./configure --prefix=$out # compile and install make make install ``` ```nix= with (import <nixpkgs> {}); derivation { name = "hello"; builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; buildInputs = [ gnutar gzip gnumake gcc binutils-unwrapped coreutils gawk gnused gnugrep ]; src = ./hello-2.10.tar.gz; system = builtins.currentSystem; } ``` ### A more convenient derivation function ```nix= # autotools.nix # define a function which accepts a parameter "pkgs" and returns a function which accepts a parameter "attrs" pkgs: attrs: # bring pkgs into the scope with pkgs; # define helper variable "defaultAttrs" which serves as a set of common attributes used in derivations let defaultAttrs = { builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped coreutils gawk gnused gnugrep ]; buildInputs = []; system = builtins.currentSystem; }; in # create the derivation derivation (defaultAttrs // attrs) ``` **NB:** The [// operator](https://nixos.org/manual/nix/stable/expressions/language-operators.html) is an operator between two sets. The result is the union of the two sets. In case of conflicts between attribute names, the value on the right set is preferred. Modified **builder.sh** that takes `$baseInputs` into account: ```bash= # builder.sh # set to exit the build on any error set -e # reset PATH unset PATH # fill the PATH variable using $baseInputs for p in $baseInputs; do export PATH=$p/bin${PATH:+:}$PATH done # fill the PATH variable using $buildInputs for p in $buildInputs; do export PATH=$p/bin${PATH:+:}$PATH done # unpack the source tar -xf $src # find directory where source has been unpacked and cd into it for d in *; do if [ -d "$d" ]; then cd "$d" break fi done ./configure --prefix=$out # compile and install make make install ``` Rewrite `hello.nix`: ```nix= # hello.nix let pkgs = import <nixpkgs> {}; # import custom function from "autotools.nix" mkDerivation = import ./autotools.nix pkgs; # execute function mkDerivation and provide "hello set" as attrs in mkDerivation { name = "hello"; src = ./hello-2.10.tar.gz; } ``` **NB:** Note that `buildInputs` remains empty because we do not need other dependencies to be in PATH. If we needed other dependencies, we could specify them by adding `buildInputs` in `mkDerivation {...}`. A simple example below: ```nix= # hello.nix let pkgs = import <nixpkgs> {}; # import custom function from "autotools.nix" mkDerivation = import ./autotools.nix pkgs; # execute function mkDerivation and provide "hello set" as attrs in mkDerivation { name = "hello"; src = ./hello-2.10.tar.gz; # we indicate that we want to use additional dependencies "foo" and "bar" buildInputs = [ foo bar ] } ``` ## Chapter 9: [Automatic Runtime Dependencies](https://nixos.org/guides/nix-pills/automatic-runtime-dependencies.html) ### Build dependencies ```bash $ nix-instantiate hello.nix /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv $ nix-store -q --references /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv /nix/store/0q6pfasdma4as22kyaknk4kwx4h58480-hello-2.10.tar.gz /nix/store/1zcs1y4n27lqs0gw4v038i303pb89rw6-coreutils-8.21.drv /nix/store/2h4b30hlfw4fhqx10wwi71mpim4wr877-gnused-4.2.2.drv /nix/store/39bgdjissw9gyi4y5j9wanf4dbjpbl07-gnutar-1.27.1.drv /nix/store/7qa70nay0if4x291rsjr7h9lfl6pl7b1-builder.sh /nix/store/g6a0shr58qvx2vi6815acgp9lnfh9yy8-gnugrep-2.14.drv /nix/store/jdggv3q1sb15140qdx0apvyrps41m4lr-bash-4.2-p45.drv /nix/store/pglhiyp1zdbmax4cglkpz98nspfgbnwr-gnumake-3.82.drv /nix/store/q9l257jn9lndbi3r9ksnvf4dr8cwxzk7-gawk-4.1.0.drv /nix/store/rgyrqxz1ilv90r01zxl0sq5nq0cq7v3v-binutils-2.23.1.drv /nix/store/qzxhby795niy6wlagfpbja27dgsz43xk-gcc-wrapper-4.8.3.drv /nix/store/sk590g7fv53m3zp0ycnxsc41snc2kdhp-gzip-1.6.drv ``` ### Runtime dependencies Steps: - Dump the derivation as NAR, a serialization of the derivation output. Works fine whether it's a single file or a directory. - For each build dependency .drv and its relative out path, search the contents of the NAR for this out path. - If found, then it's a runtime dependency. ```bash $ nix-instantiate hello.nix /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv $ nix-store -r /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv /nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello $ nix-store -q --references /nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19 /nix/store/8jm0wksask7cpf85miyakihyfch1y21q-gcc-4.8.3 /nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello ``` The build process adds that gcc lib path thinking it may be useful at runtime, but really it's not. How do we get rid of it? Nix authors have written another magical tool called [patchelf](https://nixos.org/patchelf.html), which is able to reduce the [rpath](https://en.wikipedia.org/wiki/Rpath) to the paths that are really used by the binary. ### Another phase in the builder Add the following at the end of `builder.sh` (after `make install`) ```bash # fixup: for each file in $out we run patchelf --shrink-rpath and strip # this well help remove any unnecessary runtime dependency find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null ``` However, since we are using two new commands: **find** and **patchelf**, we must add them to the `baseInputs` of `autotools.nix`: ```nix # define a function which accepts a parameter "pkgs" and returns a function which accepts a parameter "attrs" pkgs: attrs: # bring pkgs into the scope with pkgs; # define helper variable "defaultAttrs" which serves as a set of common attributes used in derivations let defaultAttrs = { builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; # add findutils and patchelf for the fixup phase in builder.sh baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped coreutils gawk gnused gnugrep findutils patchelf ]; buildInputs = []; system = builtins.currentSystem; }; in # create the derivation derivation (defaultAttrs // attrs) ``` Thanks to the newly added **fixup** phase in our builder, we only have two runtime dependencies: ```bash $ nix-build hello.nix [...] $ nix-store -q --references result /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19 /nix/store/md4a3zv0ipqzsybhjb8ndjhhga1dj88x-hello ``` The hello binary will use that exact version of glibc library and interpreter, not the system one: ```bash $ ldd result/bin/hello linux-vdso.so.1 (0x00007fff11294000) libc.so.6 => /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/libc.so.6 (0x00007f7ab7362000) /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/ld-linux-x86-64.so.2 (0x00007f7ab770f000) ``` It's all self-contained! That's what's cool with Nix :) ## Chapter 10: [Developing with nix-shell](https://nixos.org/guides/nix-pills/developing-with-nix-shell.html) ### What's nix-shell ```bash $ nix-shell hello.nix [nix-shell]$ make make: *** No targets specified and no makefile found. Stop. [nix-shell]$ which make /usr/bin/make # looks like nix-shell inherited $PATH [nix-shell]$ echo $baseInputs /nix/store/jff4a6zqi0yrladx3kwy4v6844s3swpc-gnutar-1.27.1 [...] ``` The following did not work on my machine because my user is missing permission to write to the Nix store: ```bash [nix-shell]$ source builder.sh ... /nix/store/vizjhz04x6xl57x2vrpqa52j8q6rkjfh-coreutils-9.0/bin/install: cannot remove '/nix/store/2d3a7glhfr1fk6xzkaynp2p9vq0r6pzn-hello/bin/hello': Permission denied ... ``` ### A builder for nix-shell ```nix= # autotools.nix # define a function which accepts a parameter "pkgs" and returns a function which accepts a parameter "attrs" pkgs: attrs: # bring pkgs into the scope with pkgs; # define helper variable "defaultAttrs" which serves as a set of common attributes used in derivations let defaultAttrs = { builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; setup = ./setup.sh; baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped coreutils gawk gnused gnugrep patchelf findutils ]; buildInputs = []; system = builtins.currentSystem; }; in # create the derivation derivation (defaultAttrs // attrs) ``` ```bash= # builder.sh # set to exit the build on any error set -e # source setup and call the genericBuild function source $setup genericBuild ``` ```bash= # fixup.sh # reset PATH unset PATH # fill the PATH variable using $buildInputs for p in $baseInputs $buildInputs; do export PATH=$p/bin${PATH:+:}$PATH done function unpackPhase() { # unpack the source tar -xzf $src # find directory where source has been unpacked and cd into it for d in *; do if [ -d "$d" ]; then cd "$d" break fi done } function configurePhase() { ./configure --prefix=$out } function buildPhase() { make } function installPhase() { make install } # fixup: for each file in $out we run patchelf --shrink-rpath and strip # this well help remove any unnecessary runtime dependency function fixupPhase() { find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null } function genericBuild() { unpackPhase configurePhase buildPhase installPhase fixupPhase } ``` ```nix= # hello.nix let pkgs = import <nixpkgs> {}; mkDerivation = import ./autotools.nix pkgs; in mkDerivation { name = "hello"; src = ./hello-2.10.tar.gz; } ``` ## Chapter 11: [Garbage Collector](https://nixos.org/guides/nix-pills/garbage-collector.html) ### How does it work Programming languages with a garbage collector have an important concept in order to keep track of live objects: GC roots. A GC root is an object that is always alive (unless explicitly removed as GC root). All objects recursively referred to by a GC root are live. Therefore, the garbage collection process starts from GC roots, and recursively mark referenced objects as live. All other objects can be collected and deleted. In Nix there's this same concept. Instead of being objects, of course, [GC roots are store paths](https://nixos.org/manual/nix/stable/package-management/garbage-collector-roots.html). The implementation is very simple and transparent to the user. GC roots are stored under **/nix/var/nix/gcroots**. If there's a symlink to a store path, then that store path is a GC root. Nix allows this directory to have subdirectories: it will simply recurse directories in search of symlinks to store paths. So we have a list of GC roots. At this point, deleting dead store paths is as easy as you can imagine. We have the list of all live store paths, hence the rest of the store paths are dead. In particular, Nix first moves dead store paths to **/nix/store/trash** which is an atomic operation. Afterwards, the trash is emptied. ### Playing with the GC ```bash $ nix-collect-garbage ... note: currently hard linking saves -0.00 MiB 2704 store paths deleted, 670.44 MiB freed ``` 🥳 **670.44 MiB freed!!** 🥳 Installing bsd-games: ```bash $ nix-env -iA nixpkgs.bsdgames $ readlink -f `which fortune` /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17/bin/fortune $ nix-store -q --roots `which fortune` /nix/var/nix/profiles/default-9-link $ nix-env --list-generations [...] 9 2014-08-20 12:44:14 (current) ``` ### Remove everything ```bash $ nix-channel --update $ nix-env -u --always $ rm /nix/var/nix/gcroots/auto/* $ nix-collect-garbage -d ... deleting unused links... note: currently hard linking saves -0.00 MiB 2430 store paths deleted, 612.93 MiB freed ``` 🥳 **612.93 MiB freed!!** 🥳 ## Chapter 12: [Inputs Design Pattern](https://nixos.org/guides/nix-pills/inputs-design-pattern.html) ### The single repository pattern Nix Packages repository: [nixpkgs](https://github.com/NixOS/nixpkgs) The repository is not heavy because the packages are only built when needed. Modifying `setup.sh` to include **pkg-config**: ```sh for p in $baseInputs $buildInputs; do if [ -d $p/bin ]; then export PATH="$p/bin${PATH:+:}$PATH" fi if [ -d $p/lib/pkgconfig ]; then export PKG_CONFIG_PATH="$p/lib/pkgconfig${PKG_CONFIG_PATH:+:}$PKG_CONFIG_PATH" fi done ``` Pass **gd** to the **buildInputs**: ```nix # graphviz.nix let pkgs = import <nixpkgs> {}; mkDerivation = import ./autotools.nix pkgs; in mkDerivation { name = "graphviz"; src = ./graphviz-2.49.3.tar.gz; buildInputs = with pkgs; [ pkg-config (pkgs.lib.getLib gd) (pkgs.lib.getDev gd) ]; } ``` The **[-A]** argument is used to access an attribute of the set from the given .nix expression. The **[-f]** option is used to specify the expression to use, in this case the current directory, therefore `./default.nix`. The **[-i]** stands for installation. The **[-A]** is the same as above for nix-build. ### The inputs pattern The **"inputs"** pattern allows our expressions to be easily customizable through a set of arguments. These arguments could be flags, derivations, or whatever else. Our package expressions are functions, don't think there's any magic in there. It also makes the expressions independent of the repository. Given that all the needed information is passed through arguments, it is possible to use that expression in any other context. ## Chapter 13: [Callpackage Design Pattern](https://nixos.org/guides/nix-pills/callpackage-design-pattern.html) ### The callPackage function The function takes two arguments: - An argument set with values - A function It then intersects the argument set with the function and return the result. It is defined as follow: `nix-repl> callPackage = set: f: f (builtins.intersectAttrs (builtins.functionArgs f) set)` Simple example: ```nix nix-repl> values = { a = 3; b = 5; c = 10; } nix-repl> callPackage = set: f: f (builtins.intersectAttrs (builtins.functionArgs f) set) nix-repl> callPackage values add 8 ``` Modify **callPackage** to allow overrides: ```nix nix-repl> callPackage = set: f: overrides: f ((builtins.intersectAttrs (builtins.functionArgs f) set) // overrides) nix-repl> callPackage values add { } 8 nix-repl> callPackage values add { b = 12; } 15 ``` ### Final version of `default.nix` ```nix let nixpkgs = import <nixpkgs> {}; allPkgs = nixpkgs // pkgs; # define the callPackage function callPackage = path: overrides: let f = import path; # import the file beforehand in f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides); pkgs = with nixpkgs; { mkDerivation = import ./autotools.nix nixpkgs; hello = callPackage ./hello.nix { }; graphviz = callPackage ./graphviz.nix { }; graphvizCore = callPackage ./graphviz.nix { gdSupport = false; }; }; in pkgs ``` ## Chapter 14: [Override Design Pattern](https://nixos.org/guides/nix-pills/override-design-pattern.html) **The idea:** Defining overrides without modifying the original package. ### The override implementation ```nix # lib.nix { makeOverridable = f: origArgs: let origRes = f origArgs; in origRes // { override = newArgs: f (origArgs // newArgs); }; } ``` ```nix $ nix repl nix-repl> :l lib.nix Added 1 variables. nix-repl> f = { a, b }: { result = a+b; } nix-repl> f { a = 3; b = 5; } { result = 8; } nix-repl> res = makeOverridable f { a = 3; b = 5; } nix-repl> res { override = «lambda»; result = 8; } nix-repl> res.override { a = 10; } { result = 15; } ``` The problem is that once we call the override of a set, we lose the ability to override (the last set does have a `result` but is missing an `override`). A solution is to call `makeOverridable` recursively: ```nix rec { makeOverridable = f: origArgs: let origRes = f origArgs; in origRes // { override = newArgs: makeOverridable f (origArgs // newArgs); }; } ``` Now we can successfully use `override` recursively: ```nix nix-repl> res = makeOverridable f { a = 3; b = 5; } nix-repl> res2 = res.override { a = 10; } ``` ## Chapter 15: [Nix Search Paths](https://nixos.org/guides/nix-pills/nix-search-paths.html) ```bash $ nix-instantiate --eval -E '<ping>' error: file `ping` was not found in the Nix search path (add it using $NIX_PATH or -I) $ NIX_PATH=$PATH nix-instantiate --eval -E '<ping>' /bin/ping $ nix-instantiate -I /bin --eval -E '<ping>' /bin/ping ``` Note that the [-I] option accepts a single directory. Paths added with [-I] take precedence over NIX_PATH. ```nix $ nix-instantiate --eval -E '<nixpkgs>' /home/coko/.nix-defexpr/channels/nixpkgs $ echo $NIX_PATH ``` It seems **NIX_PATH** is not defined. I checked in **~/.nix-profile/etc/profile.d/nix.sh** and it turns out that there is no mention of **NIX_PATH** in here. Turns out it is normal on non-NixOS. Check out [issue #149791](https://github.com/NixOS/nixpkgs/issues/149791) on GitHub or [this post](https://discourse.nixos.org/t/where-is-nix-path-supposed-to-be-set/16434/6). First copy `default.nix` and `graphviz.nix` in **/home/coko/mypkgs**. Then do the following: ```nix $ export NIX_PATH=mypkgs=/home/coko/mypkgs:/home/coko/.nix-defexpr/channels/nixpkgs $ nix-instantiate --eval '<mypkgs>' { graphviz = <code>; graphvizCore = <code>; hello = <code>; mkDerivation = <code>; } ``` Contrary to **nix-instantiate** and **nix-build**, **nix-env** does not rely on `NIX_PATH` to find the `nixpkgs` repository. Instead, it uses `~/.nix-defexpr`. To specify an alternative to `~/.nix-defexpr`, we can use the [-f] option: `nix-env -f '<mypkgs>' -i graphviz`. `nix-env -f '<mypkgs>' -qaP` `nix-env -f '<mypkgs>' -i -A graphviz`: [-A] option when you want to specify an attribute name instead of a derivation. ## Chapter 16: [Nixpkgs Parameters](https://nixos.org/guides/nix-pills/nixpkgs-parameters.html) ### General structure of nixpkgs - `default.nix`: File loaded when referring to `<nixpkgs>`. Checks whether the `nix` version is at least **2.2** (see below) and if it is, it will import `./pkgs/top-level/impure.nix`. ```nix # ~/.nix-defexpr/channels/nixpkgs/default.nix let requiredVersion = import ./lib/minver.nix; in if ! builtins ? nixVersion || builtins.compareVersions requiredVersion builtins.nixVersion == 1 then abort '' This version of Nixpkgs requires Nix >= ${requiredVersion}, please upgrade: - If you are running NixOS, `nixos-rebuild' can be used to upgrade your system. - Alternatively, with Nix > 2.0 `nix upgrade-nix' can be used to imperatively upgrade Nix. You may use `nix-env --version' to check which version you have. - If you installed Nix using the install script (https://nixos.org/nix/install), it is safe to upgrade by running it again: curl -L https://nixos.org/nix/install | sh For more information, please see the NixOS release notes at https://nixos.org/nixos/manual or locally at ${toString ./nixos/doc/manual/release-notes}. If you need further help, see https://nixos.org/nixos/support.html '' else import ./pkgs/top-level/impure.nix ``` If we take at look at `~/.nix-defexpr/channels/nixpkgs/lib/minver.nix`: ```bash $ cat ~/.nix-defexpr/channels/nixpkgs/lib/minver.nix # Expose the minimum required version for evaluating Nixpkgs "2.2" ``` - `all-packages.nix` (located at `~/.nix-defexpr/channels/nixpkgs/pkgs/top-level/all-packages.nix`): File that composes all the packages. It contains a function that accepts multiple parameters such as: - **system**: Defaults to the current system - **config**: Defaults to null - others... The **system** parameter, as per comment in the expression, it's the system for which the packages will be built. It allows for example to install i686 packages on amd64 machines. The **config** parameter is a simple attribute set. Packages can read some of its values and change the behavior of some derivations. ### The system parameter ```nix # myrelease.nix { system ? builtins.currentSystem }: let pkgs = import <nixpkgs> { inherit system; }; ... ``` This parameter allows to easily select a set of packages for a particular system. For example: ```bash nix-build -A psmisc --argstr system i686-linux ``` This will build the psmisc derivation for i686-linux instead of x86_64-linux. ### The config parameter The `all-packages.nix` expression accepts the `config` parameter. If it's `null`, then it reads the `NIXPKGS_CONFIG` environment variable. If not specified, `nixpkgs` will peek `$HOME/.nixpkgs/config.nix`. ```nix nix-repl> pkgs = import <nixpkgs> {} nix-repl> pkgs.config { doCheckByDefault = false; warnings = [ ... ]; } nix-repl> pkgs = import <nixpkgs> { config = { foo = "bar"; }; } nix-repl> pkgs.config { doCheckByDefault = false; foo = "bar"; warnings = [ ... ]; } ``` ## Chapter 17: [Nixpkgs Overriding Packages](https://nixos.org/guides/nix-pills/nixpkgs-overriding-packages.html) Defining a recursive function (`let ... in ...`): ```nix nix-repl> let factorial = n: if n == 0 then 1 else n * factorial (n - 1); in factorial 5 120 ``` ### The problem We want to build an override of the package **graphviz**. But how do we tell a package **P** that depends on **graphviz** to use the override version? ### In an imperative world... ```nix pkgs = import <nixpkgs> {}; pkgs.graphviz = pkgs.graphviz.override { xorg = null; }; build(pkgs.P) ``` Cool but it won't be that easy in our case. Nix is a functional language and you can assign to variables only once. ### [Fixed point](https://en.wikipedia.org/wiki/Fixed_point_%28mathematics%29) ```nix # Take a function and evaluate it with its own returned value. fix = f: let result = f result; in result; ``` Thanks to lazy evaluation, we are not trapped in an infinite loop. ```nix nix-repl> fix = f: let result = f result; in result nix-repl> pkgs = self: { a = 3; b = 4; c = self.a+self.b; } nix-repl> fix pkgs { a = 3; b = 4; c = 7; } ``` Without the rec keyword, we were able to refer to`a` and `b` of the same set. - First `pkgs` gets called with an unevaluated thunk `(pkgs(pkgs(...)` - To set the value of `c` then `self.a` and `self.b` are evaluated. - The `pkgs` function gets called again to get the value of `a` and `b`. The trick is that `c` is not needed to be evaluated in the inner call, thus it doesn't go in an infinite loop. This helped me to understand dynamic binding better: http://r6.ca/blog/20140422T142911Z.html We can almost think of `rec { bindings }` as syntactic sugar for `fix (self: with self; { bindings })`. ### Overriding a set with fixed point ```nix nix-repl> overrides = { a = 1; b = 2; } nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs { a = 3; b = 4; c = 3; } nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs // overrides { a = 1; b = 2; c = 3; } ``` In the first case we computed pkgs with the overrides, in the second case we also included the overriden attributes in the result. ### Overriding nixpkgs packages ```nix config.nix { packageOverrides = pkgs: { graphviz = pkgs.graphviz.override { xorg = null; }; }; } ``` ```nix nix-repl> pkgs = import <nixpkgs> { config = import ./config.nix; } nix-repl> :b pkgs.asciidocFull ``` ## Chapter 18: [Nix Store Paths](https://nixos.org/guides/nix-pills/nix-store-paths.html) ### Source paths ```bash echo mycontent > myfile $ nix repl nix-repl> derivation { system = "x86_64-linux"; builder = ./myfile; name = "foo"; } «derivation /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv» ``` #### Step 1. Compute the hash of the file ```bash $ nix-hash --type sha256 myfile 2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3 # or $ nix-store --dump myfile|sha256sum 2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3 ``` #### Step 2. Build the string description Then nix uses a special string which includes the hash, the path type and the file name. We store this in another file: ```bash $ echo -n "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile" > myfile.str ``` #### Step 3. Compute the final hash Finally the comments tell us to compute the base-32 representation of the first 160 bits (truncation) of a sha256 of the above string: ```bash $ nix-hash --type sha256 --truncate --base32 --flat myfile.str xv2iccirbrvklck36f1g7vldn5v58vck ``` ### Output paths At the time nix computes the out path, the .drv contains an empty string for each out path. So what we do is getting our .drv and replacing the out path with an empty string: ```bash $ cp -f /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv myout.drv $ cat myout.drv Derive([("out","/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo","","")],[],["/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile"],"x86_64-linux","/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile",[],[("builder","/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile"),("name","foo"),("out","/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo"),("system","x86_64-linux")]) $ sed -i 's,/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo,,g' myout.drv $ cat myout.drv Derive([("out","","","")],[],["/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile"],"x86_64-linux","/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile",[],[("builder","/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile"),("name","foo"),("out",""),("system","x86_64-linux")]) ``` The `myout.drv` is the .drv state in which nix is when computing the out path for our derivation: ```bash $ sha256sum myout.drv 1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5 myout.drv $ echo -n "output:out:sha256:1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5:/nix/store:foo" > myout.str $ nix-hash --type sha256 --truncate --base32 --flat myout.str hs0yi5n5nw6micqhy8l1igkbhqdkzqa1 ``` ### Fixed-output paths ```bash $ echo mycontent > myfile $ sha256sum myfile f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb myfile nix-repl> derivation { name = "bar"; system = "x86_64-linux"; builder = "none"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb"; } «derivation /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv» ``` ```bash $ nix show-derivation /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv { "/nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv": { "outputs": { "out": { "path": "/nix/store/a00d5f71k0vp5a6klkls0mvr1f7sx6ch-bar", "hashAlgo": "sha256", "hash": "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb" } }, [...] } ``` Because we specified the hashing algorithm and provided a hash, they appear in the `.drv` file. ### Conclusion Nix first hashes the contents, then creates a string description, and the final store path is the hash of this string. The hash of a derivation only depends on its input. Fixed-output derivations are especially used by the nixpkgs repository for downloading and verifying source tarballs. ## Chapter 19: [Fundamentals of Stdenv](https://nixos.org/guides/nix-pills/fundamentals-of-stdenv.html) Only has two folders: `nix-support/` and `setup/`. The latter contains hardcoded dependecies of all the toolchain and basic tools. `stdenv setup` is similar to the `builder.sh` we did in a previous pill, although this one is much more complete and contains over a thousand lines of code. `stdenv setup` works in phases: - unpackPhase - patchPhase - configurePhase - buildPhase - checkPhase - installPhase - fixupPhase - installCheckPhase - distPhase All these phases can be found in the `genericBuild` function in `setup`. How to use `stdenv setup`: ```bash $ nix-shell -E 'derivation { name = "fake"; builder = "fake"; system = "x86_64-linux"; }' nix-shell$ unset PATH nix-shell$ source /nix/store/k4jklkcag4zq4xkqhkpy156mgfm34ipn-stdenv/setup nix-shell$ tar -xf hello-2.10.tar.gz nix-shell$ cd hello-2.10 nix-shell$ configurePhase ... nix-shell$ buildPhase ... ``` To be more pratical, the packages in `<nixpkgs>` use a wrapper called `stdenv.mkDerivation` instead of the raw derivation. Let us remake `hello.nix` using `stdenv`: ```nix with import <nixpkgs> {}; stdenv.mkDerivation { name = "hello"; src = ./hello-2.10.tar.gz; } ``` It builds, and runs fine: ```bash $ nix-build hello.nix ... /nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello $ result/bin/hello Hello, world! ``` The overall process is simple: - `nix-build` - `bash -e default-builder.sh` - `source $stdenv/setup` - `genericBuild` ## Chapter 20: [Basic Dependencies and Hooks](https://nixos.org/guides/nix-pills/basic-dependencies-and-hooks.html) ### The buildInputs Attribute The **buildInputs** covers direct dependencies. ```nix # hello.nix (again) let nixpkgs = import <nixpkgs> {}; inherit (nixpkgs) stdenv fetchurl which; actualHello = stdenv.mkDerivation { name = "hello-2.3"; src = fetchurl { url = mirror://gnu/hello/hello-2.3.tar.bz2; sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1"; }; }; wrappedHello = stdenv.mkDerivation { name = "hello-wrapper"; buildInputs = [ actualHello which ]; unpackPhase = "true"; installPhase = '' mkdir -p "$out/bin" echo "#! ${stdenv.shell}" >> "$out/bin/hello" echo "exec $(which hello)" >> "$out/bin/hello" ''; }; in wrappedHello ``` Notice that the `wrappedHello` derivation finds the `hello` binary from the `PATH`. This works because `stdenv` goes through each `buildInput` and call the function `findInputs`. It also relies on a function `addToEnv` to add them to the `PATH`. ### The propagatedBuildInputs Attribute The **propagatedBuildInputs** covers indirect dependencies. ```nix let nixpkgs = import <nixpkgs> {}; inherit (nixpkgs) stdenv fetchurl which; actualHello = stdenv.mkDerivation { name = "hello-2.3"; src = fetchurl { url = mirror://gnu/hello/hello-2.3.tar.bz2; sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1"; }; }; intermediary = stdenv.mkDerivation { name = "middle-man"; propagatedBuildInputs = [ actualHello ]; unpackPhase = "true"; installPhase = '' mkdir -p "$out" ''; }; wrappedHello = stdenv.mkDerivation { name = "hello-wrapper"; buildInputs = [ intermediary which ]; unpackPhase = "true"; installPhase = '' mkdir -p "$out/bin" echo "#! ${stdenv.shell}" >> "$out/bin/hello" echo "exec $(which hello)" >> "$out/bin/hello" ''; }; in wrappedHello ``` Only the `intermediary` package needs `propagatedBuildInputs`. How does it work? Look at the **fixupPhase**: ```bash fixupPhase() { ## Elided if test -n "$propagatedBuildInputs"; then mkdir -p "$out/nix-support" echo "$propagatedBuildInputs" > "$out/nix-support/propagated-build-inputs" fi ## Elided } ``` As a result, all propagated build inputs reside in `$out/nix-support/`. There is actually more to the **findInputs** function: ```bash findInputs() { local pkg=$1 ## More goes here in reality that we can ignore for now. if test -f $pkg/nix-support/propagated-build-inputs; then for i in $(cat $pkg/nix-support/propagated-build-inputs); do findInputs $i done fi } ``` Recursively, it looks at the propagated build inputs of each dependency, as well as those dependencies' propagated build inputs and so on. ### Setup Hooks The **Setup hooks** are bash callbacks used to handle arbitrary interactions. If a package includes the path `pkg/nix-support/setup-hook`, it will be sourced by any stdenv-based build including that as a dependency. This is strictly more general than any of the other mechanisms introduced in this chapter. For example, try writing a setup hook that has the same effect as a *propagatedBuildInputs* entry. One can almost think of this as an escape hatch around Nix's normal isolation guarantees, and the principle that dependencies are immutable and inert. We're not actually doing something unsafe or modifying dependencies, but we are allowing arbitrary ad-hoc behavior. For this reason, setup-hooks should only be used as a last resort.