Hello World - our first derivation
A package is the product of building a derivation. A derivation is the product
of some nix build script, which typically calls derivation
or mkDerivation
.
Note
Instructions here suppose a functioning Nix flake. If you want to get started quickly, then create a new flake using the starter-22.11 template:
Note
The repository for this example is published here
We won't be spending more time on the actual package entries. Suffice it to
say that we will create two example packages, hello-derivation
and
hello-mkderivation
, and that their entries will look like so:
outputs = { ... }: {
# ...
packages = forAllSystems ({ pkgs }: {
hello-derivation = pkgs.callPackage ./packages/hello-derivation {
system = pkgs.system;
};
hello-mkderivation = pkgs.callPackage ./packages/hello-mkderivation {
};
});
}
a basic derivation
At their core, build scripts are typically written as a function which takes some input arguments and produces a derivation. For example:
{ bash
, system
}:
derivation {
name = "hello-derivation-1";
builder = "${bash}/bin/bash";
args = [./builder.sh];
system = system;
}
Nix provides helpers for building packages, but at their core, a derivation captures some inputs (builders, build inputs (packages), input sources (code)) and produces an output, a package.
Name
Each derivation must have a name, system and a builder. Note how the name is
both the intended package name (hello-derivation
) and its version (1
)
concatenated by a hyphen. This is by convention, and the mkDerivation
helper
which we will see next will produce a name
of this format provided you set
the pname
(package name) and version
attributes.
System
We first encountered the system variable
when covering dev shells. To recap, a system is a combination of hardware
architecture and operating system.
The hardware architecture could be aarch64
(64-bit Arm) or x86_64
and the
operating system could be darwin
(macOS) or linux
.
Taken together, a system is specified in the format <arch>-<os>
so
x86_64-linux
or aarch64-darwin
.
Some software may only work for certain operating systems and/or hardware
architectures and this is why each derivation takes a system argument.
For scripts/resources you would loop over all system variables and create a
package entry for each - there is no equivalent to a noarch
RPM.
Builder
Each derivation must also specify a builder. A builder can be any script or
program, and any additional arguments to the builder can be passed through the
args
attribute.
Our example build script, builder.sh
, is defined like so:
echo "hello, world" > $out
echo -e "BUILD ENV:\n=============" >> $out
declare -xp >> $out
echo "=============" >> $out
This script will record every exported variable and write them to $out
, which
is the store location nix has provided for our package.
Examining the build
Build the package by running nix build
, then read the resulting file:
$ nix build .#hello-derivation
$ cat result
hello, world
BUILD ENV:
=============
declare -x HOME="/homeless-shelter"
declare -x NIX_BUILD_CORES="16"
declare -x NIX_BUILD_TOP="/build"
declare -x NIX_LOG_FD="2"
declare -x NIX_STORE="/nix/store"
declare -x OLDPWD
declare -x PATH="/path-not-set"
declare -x PWD="/build"
declare -x SHLVL="1"
declare -x TEMP="/build"
declare -x TEMPDIR="/build"
declare -x TERM="xterm-256color"
declare -x TMP="/build"
declare -x TMPDIR="/build"
declare -x builder="/nix/store/96ky1zdkpq871h2dlk198fz0zvklr1dr-bash-5.1-p16/bin/bash"
declare -x name="hello-derivation-1"
declare -x out="/nix/store/qm3rbvvqdjwhh66nl3nz3aj2yq6ib8vg-hello-derivation-1"
declare -x system="x86_64-linux"
=============
Note that our build script could have compiled source files, created a directory
in $out
and copied over the compiled programs/libraries. This example merely
serves to highlight what is available in the build context.
Note
$out
points to the nix store entry for this package. A build only succeeds
if something is written to $out
. You may write a single file, or create a
directory and write multiple files into the store entry.
Note
Every package gets a unique location in the nix store of the form
/nix/store/<hash>-<pkg name>-<version>
. The hash is derived from the
inputs to a derivation. Inputs are source files, patches, build
dependencies (e.g. libfoo
, bash
, ...).
Examining the derivation
You can see the resulting derivation by running the show-derivation
command:
The derivation is rendered as JSON:
{
"/nix/store/lvx1p594q6r1n1j6laa4dic0j73a5xyg-hello-derivation-1.drv": {
"outputs": {
"out": {
"path": "/nix/store/qm3rbvvqdjwhh66nl3nz3aj2yq6ib8vg-hello-derivation-1"
}
},
"inputSrcs": [
"/nix/store/yyaq33bq5gqr4a7knd2p2wbzn8yyf00g-builder.sh"
],
"inputDrvs": {
"/nix/store/1zqjwafvqbdj5l0ahb35bq4lmbxq54hv-bash-5.1-p16.drv": [
"out"
]
},
"system": "x86_64-linux",
"builder": "/nix/store/96ky1zdkpq871h2dlk198fz0zvklr1dr-bash-5.1-p16/bin/bash",
"args": [
"/nix/store/yyaq33bq5gqr4a7knd2p2wbzn8yyf00g-builder.sh"
],
"env": {
"builder": "/nix/store/96ky1zdkpq871h2dlk198fz0zvklr1dr-bash-5.1-p16/bin/bash",
"name": "hello-derivation-1",
"out": "/nix/store/qm3rbvvqdjwhh66nl3nz3aj2yq6ib8vg-hello-derivation-1",
"system": "x86_64-linux"
}
}
}
Of particular interest is inputSrcs
which lists the sources which go into the
build, and inputDrvs
, listing other derivations (~packages) on which this
derivation depends.
Note that Nix automatically inferred that our derivation depends on bash,
because "${bash}/bin/bash"
refers to the bash
package.
If any of these inputs were to change, then Nix would rebuild the package when
next you type nix build <package>
and the store path would change.
References
- Nix Pills - Chapter 7 - Working Derivation
- Primary inspiration for this paper. Examples do not use flakes, however.
- Gabriella Gonzalez - How to write a nix derivation
- Excellent introduction to derivations and the finer details.