Basically nix flakes only consist of three top level elements, namely:
- description (optional but recommended)
- inputs (required for declaring dependencies)
- outputs (defines what the flake produces)
The description is just a short string, giving users a hint of what the flake is intended for. The inputs on the other hand are attribute sets and outputs is a function that returns an attribute set.
Inputs
The inputs above declare the sources of the desired flakes whose outputs you would like to use inside your own flake. In this case we are using nix packages, flake-utils and devshell. These are the inputs you will find in almost all of the flakes I write as they provide everything I need to get started.
Outputs
Flake are only as useful as their outputs and as such to be of use to anyone it should at a minimum provide a package to build/install/run.
The outputs attribute defines a function that returns an attribute set. The argument takes the inputs as parameters, we need to declare those to be able to use the functionality provided by the inputs we declared earlier on.
Okay, there is quite a bit going on here, lets start from the beginning.
...
flake-utils.lib.eachDefaultSystem (
system: let
...
This uses a handy function from flake-utils to cater for all the default supported systems, executing the build for the one you are running on.
...
system: let
pkgs = import nixpkgs {
inherit system;
overlays = [
devshell.overlays.default
(prev: next: {
hello = hello';
})
];
};
hello' = pkgs.stdenvNoCC.mkDerivation {
name = "hello";
src = ./bin;
buildPhase = ''
mkdir -p $out/bin
cp ./hello $out/bin
'';
};
in {
...
Within thelet in
block we define pkgs
and hello'
variables to be used within the flake.
...
in {
devShells.default = pkgs.devshell.mkShell {
imports = [
(pkgs.devshell.importTOML ./devshell.toml)
];
};
packages.default = hello';
formatter = pkgs.alejandra;
}
...
The item following in
is in fact what is returned, in this case an attribute set defining devShells
, packages
and formatter
attributes.
- Devshells are used to provide a way to share developer shells that behave in a predictable and reproducible manner across various developer's machines.
- The packages section is most likely the item consumers of flakes are most interested in; it provides the artefact(s) the flake is designed to build, package and distribute in the first place.
- The formatter is defined to enforce what formatter nix will be using when
nix fmt
is called, formatting all the nix files in a predefined way.
Using The Flake
Running nix develop
will target devShells.default
, whereas nix develop #.myShell
will target devShells.myShell
. The exact same goes for nix build
and nix run
, both would build/run packages.default
. When using nix build .#myPackage
and nix run .#myPackage
the package packages.myPackage
would be built and run respectively.
The flake above is available on GitHub. You can also run the default package directly using nix run "github:ck3mp3r/nixamples?dir=hello"
.
Similarly you can drop into the default developer shell by running nix develop "github:ck3mp3r/nixamples?dir=hello"
.
One More Thing
Flakes come in pairs, for every flake.nix
there is a flake.lock
. The lock file ensures that versions are pinned and everyone using the flake has the same versions of inputs and their dependencies. This is analog to a packages.lock
for nodejs or cargo.lock
for rust.
If things seem a bit confusing, don't worry, it just means now is the right time to learn the nix language before progressing any further. Don't ask me how I know...