% Nix: under the hood % Gabriella Gonzalez % December 7, 2017
Motivation
This talk covers how to use and debug Nix effectively in a commercial setting
We'll do so by learning how Nix works under the hood
This talk will focus more on breadth rather than depth
This talk will emphasize the Nix toolchain and de-emphasize the Nix language
Introduction
Nix workflows go through this pipeline:
Nix expression → Derivation → Build product
Everything in Nix (packages, operating systems, clusters) works this way
We'll organize this talk around those three stages in the pipeline
Organization
I like to document my Haskell libraries using the following standard format:
- For each type in the API:
- Document what each type represents
- Document how to produce a value of that type
- Document how to transform a value of that type
- Document how to consume a value of that type
We'll use a similar approach to systematically explore the Nix pipeline
Overview
- Nix expressions
- What are Nix expressions?
- How do we produce Nix expressions?
- How do we transform Nix expressions?
- How do we consume Nix expressions?
- Derivations
- What is a derivation?
- How do we produce a derivation?
- How do we transform a derivation?
- How do we consume a derivation?
- Build products
- What is a build product?
- How do we produce a build product?
- How do we transform a build product?
- How do we consume a build product?
- Putting it all together
Overview
- Source code
- Derivations
- Build products
- Putting it all together
What is a Nix expression?
A Nix expressions is a purely functional code written in the Nix language
A Nix expression may import
other Nix expressions stored within files
Reading a Nix expression from a file is a special case of import
For example, these two commands are identical:
$ nix-instantiate example.nix
$ nix-instantiate --expr 'import ./example.nix'
Strings, not files, are the universal interface to the Nix programming language
Producing a Nix expression
Anything that can produce a string can produce a Nix expression, including:
- Text editors
- Command lines (such as
nix-repl
or--expr
arguments to Nix commands) - Nix expressions!
- This is known as "import from derivation" (or "IFD" for short)
- Check out text files from a
git
repository- You can combine this with import from derivation
Not covered in this talk:
nix-channel
s- I discourage the use of channels for commercial development
- HTML form input
- Example: Hydra web user interface
Example: text editor
Use your editor to save this expression to a file named add.nix
:
2 + 2
Then evaluate the expression:
$ nix-instantiate --eval add.nix
4
Example: string literal
$ nix-instantiate --eval --expr '2 + 2'
4
$ nix-instantiate --eval --expr 'import ./add.nix'
4
$ nix-repl
Welcome to Nix version 1.11.4. Type :? for help.
nix-repl> 2 + 2
4
Example: import from derivation
$ cat derivation.nix
let
pkgs = import <nixpkgs> { };
in
pkgs.writeText "result.nix" "2 + 2"
$ nix-instantiate --read-write-mode --eval --expr 'let derivation = import ./derivation.nix; in import derivation'
building path(s) ‘/nix/store/0nskx7yj8glb2kbqa1lz5alvw4ngz25a-result.nix’
4
$ cat /nix/store/0nskx7yj8glb2kbqa1lz5alvw4ngz25a-result.nix
2 + 2
nixpkgs
using import from derivation
Example: obtaining Awake Security open sourced a derivation for retrieving nixpkgs
:
{ rev # The Git revision of nixpkgs to fetch
, sha256 # The SHA256 of the downloaded data
, system ? builtins.currentSystem # This is overridable if necessary
}:
with {
ifThenElse = { bool, thenValue, elseValue }: (
if bool then thenValue else elseValue);
};
ifThenElse {
bool = (0 <= builtins.compareVersions builtins.nixVersion "1.12");
# In Nix 1.12, we can just give a `sha256` to `builtins.fetchTarball`.
thenValue = (
builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
});
# This hack should at least work for Nix 1.11
elseValue = (
(rec {
tarball = import <nix/fetchurl.nix> {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
};
builtin-paths = import <nix/config.nix>;
script = builtins.toFile "nixpkgs-unpacker" ''
"$coreutils/mkdir" "$out"
cd "$out"
"$gzip" --decompress < "$tarball" | "$tar" -x --strip-components=1
'';
nixpkgs = builtins.derivation {
name = "nixpkgs-${builtins.substring 0 6 rev}";
builder = builtins.storePath builtin-paths.shell;
args = [ script ];
inherit tarball system;
tar = builtins.storePath builtin-paths.tar;
gzip = builtins.storePath builtin-paths.gzip;
coreutils = builtins.storePath builtin-paths.coreutils;
};
}).nixpkgs);
}
nixpkgs
using import from derivation
Example: obtaining You can import from a nixpkgs
derivation instead of a <nixpkgs>
channel:
# hello.nix
let
fetchNixpkgs = import ./fetchNixpkgs.nix;
nixpkgs = fetchNixpkgs {
rev = "76d649b59484607901f0c1b8f737d8376a904019";
sha256 = "01c2f4mj4ahir0sxk9kxbymg2pki1pc9a3y6r9x6ridry75fzb8h";
};
pkgs = import nixpkgs { config = {}; };
in
pkgs.hello
$ nix-build hello.nix
building path(s) ‘/nix/store/likfhl38gcaxl5s6zywlspaln32lqa2x-nixpkgs-76d649’
/nix/store/4bc0vx8j66wrm6g44qsswmy060k61qnh-hello-2.10
This is better for commercial development where perfect reproducibility matters
Transforming a Nix expression
The main way to transform a Nix expression is to evaluate the expression
Main ways to evaluate an expression:
nix-instantiate --eval
nix-repl
- implicitly as part of another command (such as
nix-build
)
nix-instantiate
Example: $ nix-instantiate --eval --expr '2 + 2'
4
Evaluating a derivation:
$ nix-instantiate --eval --expr 'let pkgs = import <nixpkgs> { }; in pkgs.hello'
{ __ignoreNulls = true; __impureHostDeps = <CODE>; __propagatedImpureHostDeps =
<CODE>; __propagatedSandboxProfile = <CODE>; __sandboxProfile = <CODE>; all = <C
ODE>; args = <CODE>; buildInputs = <CODE>; builder = <CODE>; doCheck = true; drv
Attrs = { __ignoreNulls = true; __impureHostDeps = <CODE>; __propagatedImpureHos
tDeps = <CODE>; __propagatedSandboxProfile = <CODE>; __sandboxProfile = <CODE>;
args = <CODE>; buildInputs = <CODE>; builder = <CODE>; doCheck = true; name = "h
ello-2.10"; nativeBuildInputs = <CODE>; propagatedBuildInputs = <CODE>; propagat
edNativeBuildInputs = <CODE>; src = <CODE>; stdenv = { __impureHostDeps = <CODE>
; __sandboxProfile = <CODE>; all = <CODE>; allowedRequisites = <CODE>; args = <C
...
924537755ce9164fd5b5f81ce16a1c3-src/pkgs/stdenv/generic/setup.sh; shell = <CODE>
; shellPackage = <CODE>; system = "x86_64-darwin"; type = "derivation"; }; syste
m = <CODE>; type = "derivation"; userHook = <CODE>; }
A derivation is just a record with field named type
set to "derivation"
nix-repl
Example: $ nix-repl
Welcome to Nix version 1.11.4. Type :? for help.
nix-repl> 2 + 2
4
nix-repl> let pkgs = import <nixpkgs> { }; in pkgs.hello
«derivation /nix/store/w3a5xqc8zjamz01qqnziwasalbkzyskc-hello-2.10.drv»
nix-repl> let pkgs = import <nixpkgs> { }; in pkgs.hello.type
"derivation"
The nix-repl
automatically shortens derivation records to «derivation …»
Example: implicit evaluation
$ nix-instantiate --expr 'let pkgs = import <nixpkgs> { }; in pkgs.hello'
…
/nix/store/w3a5xqc8zjamz01qqnziwasalbkzyskc-hello-2.10.drv
Here nix-instantiate
is actually performing two separate steps:
- evaluate the Nix expression to a derivation
- creating the derivation file from the Nix expression
Many Nix commands bundle several orthogonal steps in one invocation
Keep these orthogonal steps mentally separate when reasoning about Nix
Consuming a Nix expression
The only way to consume a Nix expression is to transform into a derivation
Transforming a Nix expression into a derivation is known as "instantiation"
Instantiation only works if the Nix expression evaluates to a derivation
i.e. a record with a field named type
whose value is "derivation"
Main ways to instantiation a Nix expression are:
nix-instantiate
- implicitly as part of Nix evaluation
nix-instantiate
Example: You can instantiate a Nix expression from command line with nix-instantiate
:
$ nix-instantiate --expr 'let pkgs = import <nixpkgs> { }; in pkgs.hello'
…
/nix/store/w3a5xqc8zjamz01qqnziwasalbkzyskc-hello-2.10.drv
Example: implicit as part of Nix evaluation
Nix automatically generates derivations that the root derivation references:
For example, the both
derivation references hello
and goodbye
derivations:
# both.nix
let
pkgs = import <nixpkgs> { };
hello = pkgs.writeText "hello.txt" ''
Hello, world!
'';
goodbye = pkgs.writeText "goodbye.txt" ''
Goodbye, world!
'';
both = pkgs.runCommand "both.txt" {} ''
mkdir $out
${pkgs.coreutils}/bin/cp ${hello} ${goodbye} $out
'';
in
both
Example: implicit as part of Nix evaluation
Generating the both
derivation also generates the hello
/goodbye
derivations:
$ nix-instantiate both.nix
…
/nix/store/a67d2b60vx30isvpgfnyk4qap92sn7zq-both.txt.drv
$ pretty-derivation < /nix/store/a67d2b60vx30isvpgfnyk4qap92sn7zq-both.txt.drv
Derivation
{ …
, inputDrvs =
fromList
[ …
, ( FilePath
"/nix/store/f37sv45m6b3nhxw7hk1jd2iqyhqaq7bp-hello.txt.drv"
, fromList [ "out" ]
)
, ( FilePath
"/nix/store/h1phr2qq4as23rd9dgy5ccqg4ysdf0lf-goodbye.txt.drv"
, fromList [ "out" ]
)
, …
]
, …
}
$ test -e /nix/store/f37sv45m6b3nhxw7hk1jd2iqyhqaq7bp-hello.txt.drv && echo $?
0
$ test -e /nix/store/h1phr2qq4as23rd9dgy5ccqg4ysdf0lf-goodbye.txt.drv && echo $?
0
Overview
- Source code
- Derivations
- Build products
- Putting it all together
What is a derivation?
A derivation is a language-independent recipe for how to build a path
- "Language-independent" means derivations are also Nix-independent
- A "path" you build can be a single file or a directory tree
Derivations strive to be "reproducible" and "deterministic"
- "Reproducible" means that you can always build the derivation from scratch
- "Deterministic" means that the result is always bit-for-bit identical
They are not perfectly reproducible/deterministic in practice, but better than most alternatives
Derivation files
Derivations are stored as *.drv
files underneath the /nix/store
directory:
$ find /nix/store -name '*.drv' | head
/nix/store/0008hdcdvkrr5mcqahy416hv6rmb5fwg-void-0.7.1.tar.gz.drv
/nix/store/000p9frz8wf8sns2jhn0bj94hl7ksdyq-bash43-030.drv
/nix/store/001d062xhg7bvypci1pa7h5x4ml5kz72-http-client-tls-0.3.5.1.tar.gz.drv
/nix/store/003cbxwyv9k2ihpxygxn523kxjlkpjbd-data-default-instances-dlist-0.0.1.drv
/nix/store/00548b6iwgwrrhblbyv3xqg15ycq8rcp-expat-2.2.2.drv
/nix/store/005bxi36hyy4hq889qbgkks8rv28l769-asciidoc-8.6.9.drv
/nix/store/005lcsd2xlczr9pp30qdgnml636vjz9r-gawk-4.1.4.drv
/nix/store/005vswmy4cjyjprzjjw5gmjvbll60zn2-x509-1.6.5.drv
/nix/store/00742vvlcwi00cryiwm24i75y43jvjan-dbus-conf.drv
/nix/store/0077zd7wg4rc38nppn0als642yp5sdif-Jinja2-2.8.tar.gz.drv
Derivation format
Derivations are serialized as one long line of text:
$ fold /nix/store/0008hdcdvkrr5mcqahy416hv6rmb5fwg-void-0.7.1.tar.gz.drv
Derive([("out","/nix/store/fbbqa4x05q9x0w6s1fqmx7k676d2zyz1-void-0.7.1.tar.gz","
sha256","c9f0fd93680c029abb9654b5464be260652829961b18b7046f96a0df95e825f4")],[("
/nix/store/cwnn2alfww3six2ywph5hnnlmxwhv9c7-curl-7.52.1.drv",["dev"]),("/nix/sto
re/kzs0g1ch3a59ar14xnms1wj22p2bnr9l-stdenv.drv",["out"]),("/nix/store/qq7pqyfn98
314fd30xspb1hi3rqda2lh-bash-4.3-p48.drv",["out"]),("/nix/store/r1b0rbna957biiy63
m75yxsw3aphps9b-mirrors-list.drv",["out"])],["/nix/store/5pqfb6ik1cxqq1d0irlx306
0jx1qjmsn-builder.sh"],"x86_64-linux","/nix/store/gabjbkwga2dhhp2wzyaxl83r8hjjfc
37-bash-4.3-p48/bin/bash",["-e","/nix/store/5pqfb6ik1cxqq1d0irlx3060jx1qjmsn-bui
lder.sh"],[("buildInputs",""),("builder","/nix/store/gabjbkwga2dhhp2wzyaxl83r8hj
jfc37-bash-4.3-p48/bin/bash"),("curlOpts",""),("downloadToTemp",""),("executable
",""),("impureEnvVars","http_proxy https_proxy ftp_proxy all_proxy no_proxy NIX_
CURL_FLAGS NIX_HASHED_MIRRORS NIX_CONNECT_TIMEOUT NIX_MIRRORS_apache NIX_MIRRORS
_bioc NIX_MIRRORS_bitlbee NIX_MIRRORS_cpan NIX_MIRRORS_debian NIX_MIRRORS_fedora
NIX_MIRRORS_gcc NIX_MIRRORS_gentoo NIX_MIRRORS_gnome NIX_MIRRORS_gnu NIX_MIRROR
S_gnupg NIX_MIRRORS_hackage NIX_MIRRORS_hashedMirrors NIX_MIRRORS_imagemagick NI
X_MIRRORS_kde NIX_MIRRORS_kernel NIX_MIRRORS_metalab NIX_MIRRORS_mozilla NIX_MIR
RORS_mysql NIX_MIRRORS_oldsuse NIX_MIRRORS_openbsd NIX_MIRRORS_opensuse NIX_MIRR
ORS_postgresql NIX_MIRRORS_pypi NIX_MIRRORS_roy NIX_MIRRORS_sagemath NIX_MIRRORS
_samba NIX_MIRRORS_savannah NIX_MIRRORS_sourceforge NIX_MIRRORS_sourceforgejp NI
X_MIRRORS_steamrt NIX_MIRRORS_ubuntu NIX_MIRRORS_xfce NIX_MIRRORS_xorg"),("mirro
rsFile","/nix/store/ab4zh0ga99y5xj441arp89zl8s4jfc7y-mirrors-list"),("name","voi
d-0.7.1.tar.gz"),("nativeBuildInputs","/nix/store/3ngwsbzhibvc434nqwq6jph6w7c2wa
s6-curl-7.52.1-dev"),("out","/nix/store/fbbqa4x05q9x0w6s1fqmx7k676d2zyz1-void-0.
7.1.tar.gz"),("outputHash","c9f0fd93680c029abb9654b5464be260652829961b18b7046f96
a0df95e825f4"),("outputHashAlgo","sha256"),("outputHashMode","flat"),("postFetch
",""),("preferHashedMirrors","1"),("preferLocalBuild","1"),("propagatedBuildInpu
ts",""),("propagatedNativeBuildInputs",""),("showURLs",""),("stdenv","/nix/store
/985d95clq0216a6pcp3qzw4igp84ajvr-stdenv"),("system","x86_64-linux"),("urls","mi
rror://hackage/void-0.7.1.tar.gz")])
Pretty-printing derivations
You pretty-print derivations using pretty-derivation
You can install pretty-derivation
from Haskell's nix-derivation
package:
$ nix-shell --packages haskellPackages.pretty-derivation
$ pretty-derivation < /nix/store/0008hdcdvkrr5mcqahy416hv6rmb5fwg-void-0.7.1.tar.gz.drv
Derivation
{ outputs =
fromList
[ ( "out"
, DerivationOutput
{ path =
FilePath
"/nix/store/fbbqa4x05q9x0w6s1fqmx7k676d2zyz1-void-0.7.1.tar.gz"
, hashAlgo = "sha256"
, hash =
"c9f0fd93680c029abb9654b5464be260652829961b18b7046f96a0df95e825f4"
}
)
]
, inputDrvs =
fromList
[ ( FilePath
"/nix/store/cwnn2alfww3six2ywph5hnnlmxwhv9c7-curl-7.52.1.drv"
, fromList [ "dev" ]
)
, ( FilePath
"/nix/store/kzs0g1ch3a59ar14xnms1wj22p2bnr9l-stdenv.drv"
, fromList [ "out" ]
)
, ( FilePath
"/nix/store/qq7pqyfn98314fd30xspb1hi3rqda2lh-bash-4.3-p48.drv"
, fromList [ "out" ]
)
, ( FilePath
"/nix/store/r1b0rbna957biiy63m75yxsw3aphps9b-mirrors-list.drv"
, fromList [ "out" ]
)
]
, inputSrcs =
fromList
[ FilePath "/nix/store/5pqfb6ik1cxqq1d0irlx3060jx1qjmsn-builder.sh"
]
, platform = "x86_64-linux"
, builder =
"/nix/store/gabjbkwga2dhhp2wzyaxl83r8hjjfc37-bash-4.3-p48/bin/bash"
, args =
[ "-e" , "/nix/store/5pqfb6ik1cxqq1d0irlx3060jx1qjmsn-builder.sh" ]
, env =
fromList
[ ( "buildInputs" , "" )
, ( "builder"
, "/nix/store/gabjbkwga2dhhp2wzyaxl83r8hjjfc37-bash-4.3-p48/bin/bash"
)
, ( "curlOpts" , "" )
, ( "downloadToTemp" , "" )
, ( "executable" , "" )
, ( "impureEnvVars"
, "http_proxy https_proxy ftp_proxy all_proxy no_proxy NIX_CURL_FLAGS NIX_HASHED_MIRRORS NIX_CONNECT_TIMEOUT NIX_MIRRORS_apache NIX_MIRRORS_bioc NIX_MIRRORS_bitlbee NIX_MIRRORS_cpan NIX_MIRRORS_debian NIX_MIRRORS_fedora NIX_MIRRORS_gcc NIX_MIRRORS_gentoo NIX_MIRRORS_gnome NIX_MIRRORS_gnu NIX_MIRRORS_gnupg NIX_MIRRORS_hackage NIX_MIRRORS_hashedMirrors NIX_MIRRORS_imagemagick NIX_MIRRORS_kde NIX_MIRRORS_kernel NIX_MIRRORS_metalab NIX_MIRRORS_mozilla NIX_MIRRORS_mysql NIX_MIRRORS_oldsuse NIX_MIRRORS_openbsd NIX_MIRRORS_opensuse NIX_MIRRORS_postgresql NIX_MIRRORS_pypi NIX_MIRRORS_roy NIX_MIRRORS_sagemath NIX_MIRRORS_samba NIX_MIRRORS_savannah NIX_MIRRORS_sourceforge NIX_MIRRORS_sourceforgejp NIX_MIRRORS_steamrt NIX_MIRRORS_ubuntu NIX_MIRRORS_xfce NIX_MIRRORS_xorg"
)
, ( "mirrorsFile"
, "/nix/store/ab4zh0ga99y5xj441arp89zl8s4jfc7y-mirrors-list"
)
, ( "name" , "void-0.7.1.tar.gz" )
, ( "nativeBuildInputs"
, "/nix/store/3ngwsbzhibvc434nqwq6jph6w7c2was6-curl-7.52.1-dev"
)
, ( "out"
, "/nix/store/fbbqa4x05q9x0w6s1fqmx7k676d2zyz1-void-0.7.1.tar.gz"
)
, ( "outputHash"
, "c9f0fd93680c029abb9654b5464be260652829961b18b7046f96a0df95e825f4"
)
, ( "outputHashAlgo" , "sha256" )
, ( "outputHashMode" , "flat" )
, ( "postFetch" , "" )
, ( "preferHashedMirrors" , "1" )
, ( "preferLocalBuild" , "1" )
, ( "propagatedBuildInputs" , "" )
, ( "propagatedNativeBuildInputs" , "" )
, ( "showURLs" , "" )
, ( "stdenv"
, "/nix/store/985d95clq0216a6pcp3qzw4igp84ajvr-stdenv"
)
, ( "system" , "x86_64-linux" )
, ( "urls" , "mirror://hackage/void-0.7.1.tar.gz" )
]
}
Anatomy of a derivation
The key components of a derivation are:
- Input derivations
- This is used to compute what needs to be built before this derivation
- The build instructions, consisting of:
- A command (i.e.
/nix/store/...-bash-4.3-p48/bin/bash
) - The command's arguments (i.e.
-e /nix/store/...-builder.sh
) - The command's environment
- A command (i.e.
- The output paths
- The build succeeds if and only if the command creates all of these paths
Derivation
{ outputs =
fromList
[ ( "out"
, DerivationOutput
{ path =
FilePath
"/nix/store/fbbqa4x05q9x0w6s1fqmx7k676d2zyz1-void-0.7.1.tar.gz"
, hashAlgo = "sha256"
, hash =
"c9f0fd93680c029abb9654b5464be260652829961b18b7046f96a0df95e825f4"
}
)
]
, inputDrvs =
fromList
[ ( FilePath
"/nix/store/cwnn2alfww3six2ywph5hnnlmxwhv9c7-curl-7.52.1.drv"
, fromList [ "dev" ]
)
, ( FilePath
"/nix/store/kzs0g1ch3a59ar14xnms1wj22p2bnr9l-stdenv.drv"
, fromList [ "out" ]
)
, ( FilePath
"/nix/store/qq7pqyfn98314fd30xspb1hi3rqda2lh-bash-4.3-p48.drv"
, fromList [ "out" ]
)
, ( FilePath
"/nix/store/r1b0rbna957biiy63m75yxsw3aphps9b-mirrors-list.drv"
, fromList [ "out" ]
)
]
, inputSrcs =
fromList
[ FilePath "/nix/store/5pqfb6ik1cxqq1d0irlx3060jx1qjmsn-builder.sh"
]
, platform = "x86_64-linux"
, builder =
"/nix/store/gabjbkwga2dhhp2wzyaxl83r8hjjfc37-bash-4.3-p48/bin/bash"
, args =
[ "-e" , "/nix/store/5pqfb6ik1cxqq1d0irlx3060jx1qjmsn-builder.sh" ]
, env =
fromList
[ ( "buildInputs" , "" )
, ( "builder"
, "/nix/store/gabjbkwga2dhhp2wzyaxl83r8hjjfc37-bash-4.3-p48/bin/bash"
)
, ( "curlOpts" , "" )
, ( "downloadToTemp" , "" )
, ( "executable" , "" )
, ( "impureEnvVars"
, "http_proxy https_proxy ftp_proxy all_proxy no_proxy NIX_CURL_FLAGS NIX_HASHED_MIRRORS NIX_CONNECT_TIMEOUT NIX_MIRRORS_apache NIX_MIRRORS_bioc NIX_MIRRORS_bitlbee NIX_MIRRORS_cpan NIX_MIRRORS_debian NIX_MIRRORS_fedora NIX_MIRRORS_gcc NIX_MIRRORS_gentoo NIX_MIRRORS_gnome NIX_MIRRORS_gnu NIX_MIRRORS_gnupg NIX_MIRRORS_hackage NIX_MIRRORS_hashedMirrors NIX_MIRRORS_imagemagick NIX_MIRRORS_kde NIX_MIRRORS_kernel NIX_MIRRORS_metalab NIX_MIRRORS_mozilla NIX_MIRRORS_mysql NIX_MIRRORS_oldsuse NIX_MIRRORS_openbsd NIX_MIRRORS_opensuse NIX_MIRRORS_postgresql NIX_MIRRORS_pypi NIX_MIRRORS_roy NIX_MIRRORS_sagemath NIX_MIRRORS_samba NIX_MIRRORS_savannah NIX_MIRRORS_sourceforge NIX_MIRRORS_sourceforgejp NIX_MIRRORS_steamrt NIX_MIRRORS_ubuntu NIX_MIRRORS_xfce NIX_MIRRORS_xorg"
)
, ( "mirrorsFile"
, "/nix/store/ab4zh0ga99y5xj441arp89zl8s4jfc7y-mirrors-list"
)
, ( "name" , "void-0.7.1.tar.gz" )
, ( "nativeBuildInputs"
, "/nix/store/3ngwsbzhibvc434nqwq6jph6w7c2was6-curl-7.52.1-dev"
)
, ( "out"
, "/nix/store/fbbqa4x05q9x0w6s1fqmx7k676d2zyz1-void-0.7.1.tar.gz"
)
, ( "outputHash"
, "c9f0fd93680c029abb9654b5464be260652829961b18b7046f96a0df95e825f4"
)
, ( "outputHashAlgo" , "sha256" )
, ( "outputHashMode" , "flat" )
, ( "postFetch" , "" )
, ( "preferHashedMirrors" , "1" )
, ( "preferLocalBuild" , "1" )
, ( "propagatedBuildInputs" , "" )
, ( "propagatedNativeBuildInputs" , "" )
, ( "showURLs" , "" )
, ( "stdenv"
, "/nix/store/985d95clq0216a6pcp3qzw4igp84ajvr-stdenv"
)
, ( "system" , "x86_64-linux" )
, ( "urls" , "mirror://hackage/void-0.7.1.tar.gz" )
]
}
Producing derivations
Main ways to produce derivations:
- Instantiate the derivation using
nix-instantiate
- Manually
$ nix-instantiate --expr 'let pkgs = import <nixpkgs> { }; in pkgs.hello'
…
/nix/store/w3a5xqc8zjamz01qqnziwasalbkzyskc-hello-2.10.drv
Producing derivations: minimal derivation
// touch.c
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv) {
char *out;
FILE *fp;
out = getenv("out");
fp = fopen(out, "w");
fclose(fp);
}
$ gcc ./touch.c -o ./touch
Producing derivations: minimal derivation
Required reading: https://nixos.org/nix/manual/#ssec-derivation
# empty.nix
derivation {
name = "empty";
system = "x86_64-darwin";
builder = ./touch;
}
$ nix-instantiate empty.nix
…
/nix/store/g34m25sy69dxd7ly7fk2nw4zhjs55jp7-empty.drv
$ pretty-derivation < /nix/store/g34m25sy69dxd7ly7fk2nw4zhjs55jp7-empty.drv
Derivation
{ outputs =
fromList
[ ( "out"
, DerivationOutput
{ path =
FilePath "/nix/store/cacmfnwdls57zd13hj5v2ydzh2gbwyl5-empty"
, hashAlgo = ""
, hash = ""
}
)
]
, inputDrvs = fromList []
, inputSrcs =
fromList
[ FilePath "/nix/store/jvf7isim1fhrppywazgxzx9h1hhzvkkb-touch" ]
, platform = "x86_64-darwin"
, builder = "/nix/store/jvf7isim1fhrppywazgxzx9h1hhzvkkb-touch"
, args = []
, env =
fromList
[ ( "builder"
, "/nix/store/jvf7isim1fhrppywazgxzx9h1hhzvkkb-touch"
)
, ( "name" , "empty" )
, ( "out" , "/nix/store/cacmfnwdls57zd13hj5v2ydzh2gbwyl5-empty" )
, ( "system" , "x86_64-darwin" )
]
}
nix-store --add
Producing derivations: Note that this approach is NOT safe:
$ cat > empty.drv <<EOF
Derive([("out","/nix/store/wm2xkgrf072h2rkgdbaym700rvrgvrp0-empty","","")],[],["/nix/store/j65p7rrvmk6zhhbn19il02gsfrwcsgf9-touch"],"x86_64-darwin","/nix/store/j65p7rrvmk6zhhbn19il02gsfrwcsgf9-touch",[],[("builder","/nix/store/j65p7rrvmk6zhhbn19il02gsfrwcsgf9-touch"),("name","empty"),("out","/nix/store/wm2xkgrf072h2rkgdbaym700rvrgvrp0-empty"),("system","x86_64-darwin")])
EOF
$ nix-store --add empty.drv
/nix/store/c8k1v7k7w349pz5lin1234fh2vhd394l-empty.drv
Transforming a derivation
The main transformation is to copy the derivation between machines using
nix-copy-closure
:
$ nix-copy-closure --to gabriella@example.com /nix/store/21wp6p5cd55kwf6f5p91w2ac031ngyv5-empty.drv
nix-copy-closure
also copies derivations that are build-time dependencies of
the copied derivation
This is how Nix delegates build instructions to other machines
Consuming derivations
Main ways to consume a derivation:
- Build the derivation using
nix-store --realise
- Debug the derivation using
nix-store --query
- Garbage collection using
nix-store --gc
Consuming derivations: build the derivation
You can use nix-store --realise
to build a derivation:
For each derivation output, nix-store --realise
will:
- Check if the path to build already exists locally
- If not, then check if the build product can be downloaded from a remote cache
- If not, then build the derivation
$ nix-store --realise /nix/store/c8k1v7k7w349pz5lin1234fh2vhd394l-empty.drv
…
/nix/store/wm2xkgrf072h2rkgdbaym700rvrgvrp0-empty
Exercise
Will adding a comment to a Nix expression cause a cache miss?
Why or why not?
Consuming derivations: Debugging
You can query a derivation's dependencies:
$ nix-store --query --tree /nix/store/jkhmav7kp8yrch4qx67276gsbzad0wbd-hello-2.10.drv
/nix/store/jkhmav7kp8yrch4qx67276gsbzad0wbd-hello-2.10.drv
+---/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh
+---/nix/store/z347hsajryw593h802ggb63lbr3gpv2b-standard-sandbox.sb
+---/nix/store/1ksvs625n8lwjhjxld446gn9ql23v5k8-bash-4.4-p5.drv
| +---/nix/store/z347hsajryw593h802ggb63lbr3gpv2b-standard-sandbox.sb [...]
| +---/nix/store/hh429nvzmkzvgq4w55bi3ihw5l5zzhwl-bootstrap-tools.drv
| | +---/nix/store/46qc2v6h7cicpcbchpk7ijprfwhzxxkk-cpio.drv
| | +---/nix/store/4cyixalr14bc2zmnvn665b3x13vq3vhd-bootstrap-tools.cpio.bz2.drv
| | +---/nix/store/4gn863k65zyramwjydiv32l79nw3mazi-bzip2.drv
| | +---/nix/store/8c8jgksrsr0xiq1ijyx7qkxcvlswhf4m-mkdir.drv
…
These are your build-time dependencies
Consuming derivations: Debugging
You can query the derivation's reverse dependencies:
$ nix-store --query --referrers-closure /nix/store/8c8jgksrsr0xiq1ijyx7qkxcvlswhf4m-mkdir.drv
/nix/store/8c8jgksrsr0xiq1ijyx7qkxcvlswhf4m-mkdir.drv
/nix/store/hh429nvzmkzvgq4w55bi3ihw5l5zzhwl-bootstrap-tools.drv
/nix/store/fswx214d183zqv3x3l4bxhz7ams9rb3r-stdenv-darwin-boot-0.drv
/nix/store/3rp0p5xs8x9hljzvdvd6aa7915xf3b0q-mirrors-list.drv
/nix/store/751d1f1459f48ypfzq4wg2r9m9vdf6da-libffi-3.2.1.tar.gz.drv
/nix/store/2kl1f4g2ix0ypf99vz6fj7rwkfbhpv3p-bash44-004.drv
/nix/store/3cwnh05fc4l0xwh7h6bh3qikf8qbj07s-bash44-002.drv
/nix/store/alim4jg8300blbjzlj9q1m4r4k7fngz7-bash-4.4-popd-offset-overflow.patch?id=1bf1ceeb04a2f57e1e5e1636a8c288c4d0db6682.drv
/nix/store/kiy5cl6a166samb58001gc4x3hxm2bj3-bash44-001.drv
/nix/store/13f9kca130ybpfjsjymrxn9bnad461za-bootstrap-libcxx.drv
…
Consuming derivations: Debugging
You can do a rich diff of two derivations using nix-diff
:
This comes in handy when figuring out why Nix won't garbage collect something
Consuming derivations: Garbage collection
nix-store --gc
will delete any derivations not reachable from a garbage
collection root
You can list any garbage collection roots using:
$ nix-store --gc --print-roots
/Users/gabriella/proj/docs/result -> /nix/store/ih5wgz02rhhr327bvdbzmdv3g5rads47-node-awake-customer-docs-0.1.0
…
/nix/var/nix/profiles/default-845-link -> /nix/store/mw9jj3f0gy05gyck3s3yyhx97damxf80-user-environment
…
/nix/var/nix/profiles/per-user/gabriella/channels-14-link -> /nix/store/064hh5d6s5qrvnhrqdjgcvxv9s4j9gg8-user-environment
…
Consuming derivations: Garbage collection
Things that create garbage collection roots by default:
nix-build
- Garbage collection root for latest buildnix-env
/nix-channel
- Garbage collection root for each profile change- NixOS / NixOps - Garbage collection root for each configuration change
- Hydra - Garbage collection roots for most recent N builds
You can delete garbage collection roots using rm
Overview
- Source code
- Derivations
- Build products
- Putting it all together
What is a build product
A build product is anything in the /nix/store
that is not a derivation
A build product can be a single file:
$ cat /nix/store/sp0jf57pkwaihyalr2xribvrimq7vy6v-openssl-config.txt
[req]
prompt = no
distinguished_name = dn
req_extension = ext
[dn]
CN = localhost.localdomain
emailAddress = noreply@localhost.localdomain
O = Private Company
L = California
C = US
[ext]
... or a directory tree:
$ tree /nix/store/vy38yvpnb89zwrf0ndd7y0qg52a088ik-criterion
/nix/store/vy38yvpnb89zwrf0ndd7y0qg52a088ik-criterion
├── Criterion
│ ├── Analysis.hs
│ ├── EmbeddedData.hs
│ ├── IO
│ │ └── Printf.hs
│ ├── IO.hs
│ ├── Internal.hs
│ ├── Main
│ │ └── Options.hs
│ ├── Main.hs
│ ├── Measurement.hs
│ ├── Monad
│ │ └── Internal.hs
│ ├── Monad.hs
│ ├── Report.hs
│ ├── Types
│ │ └── Internal.hs
│ └── Types.hs
├── Criterion.hs
├── LICENSE
├── README.markdown
├── Setup.lhs
├── app
│ ├── Options.hs
│ └── Report.hs
├── cbits
│ ├── cycles.c
│ ├── time-osx.c
│ ├── time-posix.c
│ └── time-windows.c
├── changelog.md
├── criterion.cabal
├── examples
│ ├── BadReadFile.hs
│ ├── Comparison.hs
│ ├── ConduitVsPipes.hs
│ ├── Fibber.hs
│ ├── GoodReadFile.hs
│ ├── Judy.hs
│ ├── Maps.hs
│ ├── Overhead.hs
│ ├── criterion-examples.cabal
│ └── tiny.html
├── stack.yaml
├── templates
│ ├── criterion.css
│ ├── default.tpl
│ ├── js
│ │ └── jquery.criterion.js
│ └── json.tpl
├── tests
│ ├── Cleanup.hs
│ ├── Properties.hs
│ ├── Sanity.hs
│ └── Tests.hs
└── www
├── Makefile
├── background.jpg
├── bootstrap-custom.css
├── fibber-screenshot.png
├── fibber.html
├── index.md
├── report.html
├── template.html
└── tutorial.md
12 directories, 53 files
Producing build products
Main ways to product a build product
- Build a derivation using
nix-store --realise
- Add a path directly to the store using
nix-store --add
Producing build products: build a derivation
nix-store --realise
runs a derivation to build the output path:
$ nix-store --realise /nix/store/g34m25sy69dxd7ly7fk2nw4zhjs55jp7-empty.drv
…
/nix/store/cacmfnwdls57zd13hj5v2ydzh2gbwyl5-empty
$ cat /nix/store/cacmfnwdls57zd13hj5v2ydzh2gbwyl5-empty # It's empty
Producing build products: add a path directly to the store
You can use nix-store --add
to add files directly to the /nix/store
:
$ echo foo > example.txt
$ nix-store --add example.txt
/nix/store/qrxgbry3bk4jxlywa2c9qybm4kq4lyzl-example.txt
$ cat /nix/store/qrxgbry3bk4jxlywa2c9qybm4kq4lyzl-example.txt
foo
You usually only do this for self-contained files, such as:
- Text that doesn't reference any other files
- Object code with no dependencies
For anything with dependencies, you can create a derivation
Transforming build products
The main transformation is to copy the build product between machines using
nix-copy-closure
$ nix-copy-closure --from gabriella@example.com /nix/store/wm2xkgrf072h2rkgdbaym700rvrgvrp0-empty
This is just like copying a derivation to another system
The difference is that the closure includes run-time dependencies
Consuming build products
Main ways to consume a build product:
- A build product can be anything, such as:
- An executable you can run
- An HTML page you can open
- A text file you can view
- Debug the derivation using
nix-store --query
- Garbage collection using
nix-store --gc
Consuming build products: using them directly
$ nix-build hello.nix
/nix/store/4bc0vx8j66wrm6g44qsswmy060k61qnh-hello-2.10
$ /nix/store/4bc0vx8j66wrm6g44qsswmy060k61qnh-hello-2.10/bin/hello
Hello, world!
Also, nix-build
creates a convenient result
symlink to the build product:
$ result/bin/hello
Hello, world!
Consuming build products: using them directly
Caveat: don't reference /nix/store
paths in directions:
# BAD!
let
fetchNixpkgs = import ./fetchNixpkgs.nix;
nixpkgs = fetchNixpkgs {
rev = "76d649b59484607901f0c1b8f737d8376a904019";
sha256 = "01c2f4mj4ahir0sxk9kxbymg2pki1pc9a3y6r9x6ridry75fzb8h";
};
pkgs = import nixpkgs { config = {}; };
in
pkgs.runCommand "hello" {} ''
/nix/store/4bc0vx8j66wrm6g44qsswmy060k61qnh-hello-2.10/bin/hello > $out
''
... instead interpolate Nix expressions that evaluate to derivations:
# GOOD!
let
fetchNixpkgs = import ./fetchNixpkgs.nix;
nixpkgs = fetchNixpkgs {
rev = "76d649b59484607901f0c1b8f737d8376a904019";
sha256 = "01c2f4mj4ahir0sxk9kxbymg2pki1pc9a3y6r9x6ridry75fzb8h";
};
pkgs = import nixpkgs { config = {}; };
in
pkgs.runCommand "hello" {} ''
${pkgs.hello}/bin/hello > $out
''
Consuming build products: Debugging
You can debug build products using nix-store --query
(just like derivations):
The difference is you query run-time relationships instead of built-time relationships
nix-store --query --tree
gives you runtime dependency graphnix-store --query --referrers-closure
gives you runtime reverse dependencies- NEW:
nix-store --query --deriver
gives you the corresponding derivation
Overview
- Source code
- Derivations
- Build products
- Putting it all together
nix-build
Putting it all together: The following nix-build
command:
$ nix-build --expr 'let pkgs = import <nixpkgs> { }; in pkgs.hello'
/nix/store/h5paliil3r6m70na37ymba1f007mm28k-hello-2.10
... just combines nix-instantiate
and nix-store --realise
:
$ nix-instantiate --expr 'let pkgs = import <nixpkgs> { }; in pkgs.hello'
…
/nix/store/w3a5xqc8zjamz01qqnziwasalbkzyskc-hello-2.10.drv
$ nix-store --realise /nix/store/w3a5xqc8zjamz01qqnziwasalbkzyskc-hello-2.10.drv
…
/nix/store/h5paliil3r6m70na37ymba1f007mm28k-hello-2.10
Putting it all together: Distributed builds
Conceptually a distributed build consists of the following three steps:
- Instantiate the Nix expression to create a derivation
$ nix-instantiate empty.nix
…
/nix/store/g34m25sy69dxd7ly7fk2nw4zhjs55jp7-empty.drv
- Copy the derivation to another machine
$ nix-copy-closure --to gabriella@example.com /nix/store/21wp6p5cd55kwf6f5p91w2ac031ngyv5-empty.drv
- Realise the derivation on that machine to create a build product
$ ssh gabriella@example.com nix-store --realise /nix/store/21wp6p5cd55kwf6f5p91w2ac031ngyv5-empty.drv
…
/nix/store/wm2xkgrf072h2rkgdbaym700rvrgvrp0-empty
- Copy the build product back to your machine
$ nix-copy-closure --from gabriella@example.com /nix/store/wm2xkgrf072h2rkgdbaym700rvrgvrp0-empty
Awake Security open sourced an executable that simplifies distributed builds for you:
nix-delegate
: https://github.com/awakesecurity/nix-delegate
Putting it all together: NixOS
Conceptually a NixOps deploy consists of the following steps:
- A NixOS system configuration is just a Nix expression:
# system.nix
let
nixos = import <nixpkgs/nixos> {
system = "x86_64-linux";
configuration = {
boot.loader.grub.device = "/dev/sda";
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
};
};
in
nixos.system
- You instantiate the expression to create a derivation:
$ nix-instantiate system.nix
…
/nix/store/d8xi7adaljkj39sfz7s7bg72qybywq2l-nixos-system-nixos-16.09pre-git.drv
- You realise the derivation to create a build product:
$ nix-store --realise /nix/store/d8xi7adaljkj39sfz7s7bg72qybywq2l-nixos-system-nixos-16.09pre-git.drv
- You copy the derivation to the target system:
$ nix-copy-closure --to target /nix/store/vc2sb3i0mmsgj91w6a97f96g7p3lna5f-nixos-system-nixos-16.09pre-git
- Then you run a script within the copied closure to deploy the system:
$ ssh target /nix/store/vc2sb3i0mmsgj91w6a97f96g7p3lna5f-nixos-system-nixos-16.09pre-git/bin/switch-to-configuration switch
Awake security open sourced a nix-deploy
tool that simplifies the
last two steps for you:
nix-deploy
: https://github.com/awakesecurity/nix-deploy
Conclusion
Nix conceptually operates at three layers:
- Nix expressions
- Derivations
- Build products
Being fluent at all three levels will improve your effectiveness at:
- Debugging build failures and cache misses
- Integrating with other tools
- Using Nix more idiomatically