Convenient Management of Inputs with Nix Flakes¶
In the previous sections we showed Nix derivations pinning a specific commit of nixpkgs
.
To ensure the reproducibility of the package, we also verify that the sha256
of the fetched tarball is the one expected.
This approach is however quite cumbersome to maintain when managing several inputs.
Activating Nix Flakes¶
At the time of this tutorial (July 2022), Nix Flakes are still an experimental feature and require some extra configuration to activate.
echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf
or
echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
You should now be able to use the nix
command.
nix flake --version
Creating a new flake¶
Nix Flake provide a convenient template system that makes starting new project much easier. There exist default templates, and users can also define their own.
nix flake show templates
github:NixOS/templates/2f86534428917d96d414964c69a5cfe353500ad5
├───defaultTemplate: template: A very basic flake
└───templates
├───bash-hello: template: An over-engineered Hello World in bash
├───c-hello: template: An over-engineered Hello World in C
├───compat: template: A default.nix and shell.nix for backward compatibility with Nix installations that don't support flakes
├───full: template: A template that shows all standard flake outputs
├───go-hello: template: A simple Go package
├───haskell-hello: template: A Hello World in Haskell with one dependency
├───hercules-ci: template: An example for Hercules-CI, containing only the necessary attributes for adding to your project.
├───pandoc-xelatex: template: A report built with Pandoc, XeLaTex and a custom font
├───python: template: Python template, using poetry2nix
├───rust: template: Rust template, using Naersk
├───rust-web-server: template: A Rust web server including a NixOS module
├───simpleContainer: template: A NixOS container running apache-httpd
└───trivial: template: A very basic flake
Based on the nature of the project, users can pick a template to kickstart their project.
In our case, we will use the trivial
template.
To import a template, we can use the following commands:
nix flake init -t templates#trivial
The nix flake init
command will instantiate the template in the current directory.
To create a new directory with the content of the template instead, we can use the nix flake new -t templates#trivial myfolder
.
The -t
flag indicates to look for templates in the templates
flake, and in particular the trivial
template.
But Nix Flakes can have default values, like templates.
In this case, the default template (defaultTemplate
) is the trivial
template.
Thus, we can actually just call:
nix flake init -t templates
Actually, the flake templates
is the default one for templates.
So you could even get the trivial
template by running:
nix flake init
Nix Flakes require to be in a Git repository to work
git init && git add flake.nix
You can now try to show the content of the flake:
nix flake show
git+file:///tmp/tuto-nix
├───defaultPackage
│ └───x86_64-linux: package 'hello-2.12'
└───packages
└───x86_64-linux
└───hello: package 'hello-2.12'
This flake contains one package available for the x86_64-linux
targets: the hello
package.
To build a package, run:
nix build .#hello
This tells nix to build the hello
package from the flake in the current directory .
.
It will create a result
folder linking to the result of the derivation in the store.
Execute the built hello
package:
./result/bin/hello
Hello, world!
As the produced package has a binary with the same name of the derivation (hello
here) we can even use the nix run
command to execute the hello
package:
nix run .#hello
Hello, world!
It is now time to open this mysterious flake.nix
file.
{
description = "A very basic flake";
outputs = { self, nixpkgs }: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
defaultPackage.x86_64-linux = self.packages.x86_64-linux.hello;
};
}
A flake.nix
file has 3 attributes:
description
a string for commenting the flakeinputs
a set defining the dependencies of the flakeoutputs
a function, taking the dependencies as inputs, and returning a set of the outputs
In the example above, the flake.nix
file does not have a inputs
field and will thus rely on the nixpkgs
registry of the host machine (see below).
This is not as dirty as using channels because the version of nixpkgs
used is store in the flake.lock
file, thus ensuring tracability and reproducibility.
Declaring Inputs¶
Let us add an input to the flake.
We will add a specific version of nixpkgs
.
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/22.05";
};
outputs = { self, nixpkgs }: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
defaultPackage.x86_64-linux = self.packages.x86_64-linux.hello;
};
}
Here, we tell the flake to use the version of nixpkgs
from the github repository with the tag 22.05
.
Now, the content of the flake.lock
is no more coherent with the desired version of nixpkgs
.
Nix will realise this at the next evaluation of the flake and update the lock file:
nix flake show
warning: updating lock file '/tmp/tuto-nix/flake.lock':
• Updated input 'nixpkgs':
'github:NixOS/nixpkgs/95e79164be1f7d883ed9ffda8b7d4ad3a17e6c1e' (2022-07-01)
'github:nixos/nixpkgs/ce6aa13369b667ac2542593170993504932eb836' (2022-05-30)
git+file:///tmp/tuto-nix
├───defaultPackage
│ └───x86_64-linux: package 'hello-2.12'
└───packages
└───x86_64-linux
└───hello: package 'hello-2.12'
In the flake.lock
file, we can now see the updated lock of the nixpkgs
input:
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1653936696,
"narHash": "sha256-M6bJShji9AIDZ7Kh7CPwPBPb/T7RiVev2PAcOi4fxDQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ce6aa13369b667ac2542593170993504932eb836",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "22.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
The commit of nixpkgs
is ce6aa13
which is the one associated to the 22.05
tag (https://github.com/NixOS/nixpkgs/tree/22.05).
Updating Inputs¶
To update the version of all the inputs, run nix flake update
.
In the case where you only want to update a single input, use nix flake lock --update-input <INPUT_NAME>
.
For the nixpkgs
input, this will be nix flake lock --update-input nixpkgs
.
Defining Outputs¶
A difference between the classical Nix expressions and the Nix Flake is the way the outputs are organized.
The outputs
field is a function taking as input the inputs
and returning a set.
This set should have a specific hierarchy.
First the type of output (packages
, devShells
, checks
,…), then the target architecture (x86_64-linux
, aarch64-linux
, x86_64-darwin
,…) and finally the name of the output.
In the example in the template above, you can see that it defines a package for x86_64-linux
architecture named hello
.
Note that some type of outputs, like templates
or overlays
, do not require a target architecture as they are common to all of them.
Let us add our packages defining previously. In the repository of your packages repository, start a Nix Flake:
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/22.05";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
packages.${system} = rec {
chord = pkgs.callPackage ./pkgs/chord {};
chord_custom_sg = pkgs.callPackage ./pkgs/chord { simgrid = custom_simgrid; };
custom_simgrid = pkgs.callPackage ./pkgs/simgrid/custom.nix {};
};
};
}
Backwards Compatibility¶
As Nix Flakes are still an experimental feature (at the time of this tutorial), you may want to provide a way for the users to interact with a flake via the classical nix CLI (i.e., nix-build
, nix-shell
, …).
In the folder containing your flake.nix
and flake.lock
, add the following in a file named default.nix
.
(import (
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/12c64ca55c1014cdc1b16ed5a804aa8576601ff2.tar.gz";
sha256 = "0jm6nzb83wa6ai17ly9fzpqc40wg1viib8klq8lby54agpl213w5"; }
) {
src = ./.;
}).defaultNix
You can now interact with the Nix Flake through the classical Nix CLI.
Note however that the name of the target attribute will be the full output name.
In the example above, nix-build -A hello
will not work as there is no output named hello
, you should instead use nix-build -A packages.x86_64-linux.hello
.
Registries¶
Similar to channels
presented previously, Nix Flakes have what is called registry
.
You can see the list of available registries with
nix registry list
global flake:agda github:agda/agda
global flake:blender-bin github:edolstra/nix-warez?dir=blender
global flake:dreampkgs github:nix-community/dreampkgs
global flake:dwarffs github:edolstra/dwarffs
global flake:emacs-overlay github:nix-community/emacs-overlay
global flake:fenix github:nix-community/fenix
global flake:flake-utils github:numtide/flake-utils
global flake:gemini github:nix-community/flake-gemini
global flake:home-manager github:nix-community/home-manager
global flake:hydra github:NixOS/hydra
global flake:mach-nix github:DavHau/mach-nix
global flake:nimble github:nix-community/flake-nimble
global flake:nix github:NixOS/nix
global flake:nix-darwin github:LnL7/nix-darwin
global flake:nixops github:NixOS/nixops
global flake:nixos-hardware github:NixOS/nixos-hardware
global flake:nixos-homepage github:NixOS/nixos-homepage
global flake:nixos-search github:NixOS/nixos-search
global flake:nur github:nix-community/NUR
global flake:nixpkgs github:NixOS/nixpkgs/nixpkgs-unstable
global flake:templates github:NixOS/templates
global flake:patchelf github:NixOS/patchelf
global flake:poetry2nix github:nix-community/poetry2nix
global flake:nix-serve github:edolstra/nix-serve
global flake:nickel github:tweag/nickel
global flake:bundlers github:NixOS/bundlers
You can inspect any of those flake by simply referencing their name, for example the flake-utils
flake:
nix flake show flake-utils
github:numtide/flake-utils/7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249
├───defaultTemplate: template: A flake using flake-utils.lib.eachDefaultSystem
├───lib: unknown
└───templates
├───check-utils: template: A flake with tests
├───each-system: template: A flake using flake-utils.lib.eachDefaultSystem
└───simple-flake: template: A flake using flake-utils.lib.simpleFlake
You can add user-defined registries, which can be really helpful for end users. For example, we have defined a flake for our own packages repository (as seen previously). Here is an example: https://gitlab.inria.fr/qguillot/mypkgs_example.
nix registry add mypkgs git+https://gitlab.inria.fr/qguillot/mypkgs_example
The previous command adds our flake defining our own packages to the list of registries.
It can now be accessed by the name mypkgs
.
nix flake show mypkgs
git+https://gitlab.inria.fr/qguillot/mypkgs_example?ref=master&rev=1ca007939e4dbcc9ca80830301ea1e120c3fac2d
├───packages
│ └───x86_64-linux
│ ├───chord: package 'chord-0.1.0'
│ └───chord-docker: package 'docker-image-chord-docker.tar.gz'
└───templates
├───article: template: A template for an article repository
├───default: template: A template to use as a flake starting point
└───flake: template: A template to use as a flake starting point
You can now enter a shell with chord
via the command:
nix shell mypkgs#chord
Use our flake for experiments¶
To use the packages, or other outputs, in another flake, we just need to add it to the inputs
set.
Let us create a flake for an experiments repository that will create a shell with the chord
package available.
{
description = "My Experiments repo";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/22.05";
mypkgs.url = "git+https://gitlab.inria.fr/qguillot/mypkgs_example";
};
outputs = { self, nixpkgs, mypkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
devShells.${system} = {
chordShell = pkgs.mkShell {
buildInputs = [
mypkgs.packages.${system}.chord
];
};
};
};
}
Don’t forget to add mypkgs
as a parameter of the outputs
function.
To access a package of our flake, we use its full name (i.e., type of output + system + name).
In the case of chord
, it is mypkgs.packages.${system}.chord
.
nix flake show
└───devShells
└───x86_64-linux
└───chordShell: development environment 'nix-shell'
We can take a look at the flake.lock
file:
{
"nodes": {
"mypkgs": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1655369898,
"narHash": "sha256-KjNK0lwGwwpFBwX7cPGg3iERc//rOkUqYzsvobSUCUY=",
"ref": "master",
"rev": "1ca007939e4dbcc9ca80830301ea1e120c3fac2d",
"revCount": 5,
"type": "git",
"url": "https://gitlab.inria.fr/qguillot/mypkgs_example"
},
"original": {
"type": "git",
"url": "https://gitlab.inria.fr/qguillot/mypkgs_example"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1655278232,
"narHash": "sha256-H6s7tnHYiDKFCcLADS4sl1sUq0dDJuRQXCieguk/6SA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "8b538fcb329a7bc3d153962f17c509ee49166973",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-22.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1653936696,
"narHash": "sha256-M6bJShji9AIDZ7Kh7CPwPBPb/T7RiVev2PAcOi4fxDQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ce6aa13369b667ac2542593170993504932eb836",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "22.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"mypkgs": "mypkgs",
"nixpkgs": "nixpkgs_2"
}
}
},
"root": "root",
"version": 7
}
We can see that there are much more information now.
There is the information about our mypkgs
flake, as well as its inputs (in this case, just nixpkgs
).
As we have now two versions of nixpkgs
the one of the experiments flake and the one of the mypkgs
flake, the lock file will save the information both of them for a better traceability.
We can now enter the shell environment by using the nix develop
command with the name of the shell.
nix develop .#chordShell