nix

This notes assume to have the experimental-features = nix-command flakes enabled.

nix.conf (ref)

nix config show
# experimental-features
#     enable additional experimental features
# flake-registery
#     url of global registry
# tarball-ttl
#     time the downloaded tarball is treated as valid

nix search (ref)

nix search <installable> <regex>
nix search nixpkgs glibc

nix profile (ref)

The history allows to install packages, update them and easily rollback.

# Install/remove/upgrade package(s) (creates a new history version).
nix profile install <installable>
nix profile remove
nix profile upgrade --all

# List installed packages.
nix profile list

# Show different hisitory versions.
nix profile history

# Rollback profile history.
nix profile rollback [--to N]

nix store (ref)

# Show all alive store paths (paths with active reference).
nix-store --gc --print-live
# Show all dead store paths (paths w/o active reference).
nix-store --gc --print-dead

# Run garbage collection on nix store. This deletes all paths which are no
# gcroots and any dependency of any gcroot.
nix store gc

# Delete single path from store.
nix store delete <path>

# gcroots are for example created automatically when installing packages into
# the profile or when building a flake locally.
# gcroots are located under /nix/var/nix/gcroots
#
# One can also create mamual gcroots (eg for tmp operation).
> nix-store --gc --print-dead | grep 'binutils-2.44$'
/nix/store/wcv8n2k53w8sbffpf716gxj6mjb5jf26-binutils-2.44

> mkdir -p /nix/var/nix/gcroots/tmp
> ln -s /nix/store/wcv8n2k53w8sbffpf716gxj6mjb5jf26-binutils-2.44 /nix/var/nix/gcroots/tmp/keep-binutils

> nix-store --gc --print-dead | grep 'binutils-2.44$'
> nix-store --gc --print-live | grep 'binutils-2.44$'
/nix/store/wcv8n2k53w8sbffpf716gxj6mjb5jf26-binutils-2.44

nix flake (ref)

Flakes provide a description for reproducible builds.

# Create a new flake in a folder with name.
nix flake new <name>
# Create a new flake from given template.
nix flake new -t <template> <name>
# Create a new flake with the c-hello output from the templates flake.
nix flake create -t templates#c-hello myflake

# Show the outputs of a flake
nix flake show <flake>
# Show outputs from the default "templates" flake.
nix flake show templates
# Assumes '.' as flake (which effectively means flake.nix in cwd).
nix flake show

# Show the derivation of an installable in pretty format.
nix derivation show <installable>
# Show the derivation of the glibc output from the nixpkg flake.
nix derivation show nixpkgs#glibc
# Assumes '.' as flake.
nix derivation show

# Show a flakes metadata (eg store path, inputs, description, ..)
nix flake metadata <flake>

# Update the versions in the flake.lock file.
nix flake update

Use nix registry list to list available global flakes.

Documentation for installable syntax.

a bare bones flake

The following shows a bare bones flake using the builtins.derivation function to define an output called moose. The builtin provides an empty environment.

In general, a derivation is a declarative description of how to run an executable (builder) on a set of inputs (input derivations aka dependencies) to produce a set of outputs (store paths).

# The flake is effectively an attribute set with a standard set of attributes.
{
  description = "bare bones (flake)";

  # Input(s) of the flake.
  # Each input attribute is passed into the output function below.
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  # The 'outputs' attribute is a function, accepting an attribute set with a
  # self reference and all the inputs specified above.
  # The function shall return an attribute set with different outputs.
  # There are standardized outputs for different commands such as:
  #   > nix build
  #   > nix develop
  outputs = { self, nixpkgs }: let
    # Define local variables with let .. in construct.
    system = "x86_64-linux";
    # Import another flake, passing an attribute set.
    # inherit system, is effectively system = system;
    pkgs = import nixpkgs { inherit system; };
  in
  # Return attribute set.
  {
    # Some output.
    moose = builtins.derivation {
      # -- Required attributes.
      inherit system;
      name = "bone";
      builder = "${pkgs.bash}/bin/bash";
      # -- Optional attributes.
      args = [ "-c" "echo hello > $out" ];
    };
  };
}

Understanding this example is crucial as any abstractions such as the nixpkgs.stdenv builds on top of this.

One can inspect and build the derivation as follows.

# Show the derivation for the output "moose".
> nix derivation show .#moose
{
  "/nix/store/q2l7g7myy5gmks7jml6hz2jd73yybplq-bone.drv": {
    ...
    "inputDrvs": {
      "/nix/store/d2gv7y7i7bw79dpfskzzk2g4h6mc0677-bash-interactive-5.2p37.drv": {
}

# Build the output 'moose' for the flake.nix in the cwd ('.').
> nix build .#moose ; cat result
hello

.#moose references the flake.nix in the cwd '.' and the output moose.

An interesting point to observe is that nix computed bash as input derivation (inputDrv) and hence as input dependency for this output.

a stdenv flake

The stdenv library from the nixpkgs flake provides the mkDerivation function to simplify building packages (defining derivations). Many tools commonly used to build software are available out of the box. Specifying build and runtime dependencies can also be done easiy.

The default builder provided by the mkDerivation function, organizes the build into multiple phases like config, build and install. When defining the derivation these phases can be easily overwritten.

The example show a simple build and installation of a C file.

{
  description = "stdenv (flake)";

  inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      # Enable support for 'nix fmt'.
      formatter.${system} = pkgs.nixfmt;

      # This is the default output that will be build & run if no output is
      # specified, when running the following in the flakes directory.
      # > nix build
      # > nix run
      packages.${system}.default = pkgs.stdenv.mkDerivation {
        # Either specify 'name' or 'pname & version'.
        # In the latter case, 'name' will be ${pname}-${version}.
        pname = "test";
        version = "0.1";

        # There are different builtins to also unpack or fetch tarballs.
        src = ./src;

        # The default builder of the stdenv defines many different phases that
        # can be overwritten.

        buildPhase = ''
          gcc -O2 -g -o test test.c
        '';

        installPhase = ''
          mkdir -p $out/bin
          cp test $out/bin
        '';
      };
    };
}

One can also define multiple output directories using the outputs attribute in the derivation. Each output turns into an environment variable of the same name. Use nix derivation show to inspect the outputs and the environment the builder inherits.

The for example the glibc package in the nixpkgs flake, which provides multiple outputs.

> nix derivation show nixpkgs#glibc
..
"outputs": {
      ..
      "out": {
        "path": "/nix/store/g3s0z9r7m1lsfxdk8bj88nw8k8q3dmmg-glibc-2.40-66"
      },
      "static": {
        "path": "/nix/store/lfj0w1r0ppj2swb11pz8vkppmsy1rf6y-glibc-2.40-66-static"
      }stdenv/flake.nix
    },
..

a development shell flake

The nixpkgs flake also provides the mkShell derivation function (specialization of mkDerivation), which can be used to easily define a development environment.

The examples shows an environment with the zig compiler and zls, the zig lsp server. Running nix develop will drop in a shell where these packages are available.

{
  description = "dev shell (flake)";

  inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in {
      devShells.${system}.default = pkgs.mkShell {
        buildInputs = [
          pkgs.zig
          pkgs.zls
        ];
      };
    };
}

nix lang basics

Nix is a functional language, where everything is an expression. The following shows enough nix lang to come quite far.

$ nix repl

nix-repl> 1 + 2
3

# String interpolation.
# (Assignment operation special to repl).
nix-repl> foo = "bar"
nix-repl> "hello ${foo}"
"hello bar"

# Simple function with argument.
nix-repl> f = a: a + 2
nix-repl> f 4
6

# Attribute sets (kv).
nix-repl> { a = 1; b = 2; }
{
  a = 1;
  b = 2;
}

# Function taking an attribute set.
nix-repl> f = { a, b }: a + b
nix-repl> f { a = 1; b = 2; }
3

# Defining local variables with "let .. in".
nix-repl> let x = 1; in x + 3
4
nix-repl> x
error: undefined variable 'x'

# Let .. in expression returning an attribute set (handy when defining derivations).
nix-repl> let x = "abc"; in { name = "bar ${x}"; }
{ name = "bar abc"; }

# Show all builtin functions.
nix-repl> builtins
{
  abort = «primop abort»;
  add = «primop add»;
  ..

References