Skip to content

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:

nix flake init --template github:nix4noobs/starters#starter-22-11

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:

package entries in flake.nix
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:

./packages/hello-derivation/default.nix
{ 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:

./packages/hello-derivation/builder.sh
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:

$ nix show-derivation .#hello-derivation

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