Since its introduction to the Nix ecosystem, the usage of
Flakes has been steadily
increasing. Almost all projects I have been working on in the past 5
years include a flake.nix
at the root of the repository, defining
packages, modules and development environments.1
One of these projects is a Go tool that I wrote in 2019, and it still
has a working environment when I call nix develop
or nix build
.
However, I noticed that the flake.nix
file has a significantly
different structure compared to the most recent ones.
Let's take a look into it starting with the description
. Usually a
small string that defines what this flake represents.
description = "foobarer - a project that foos the bar."
After the description, the next step consists of defining the external
dependencies of our flake. These are called inputs
and usually
consist of the unstable
nixpkgs branch and
devenv on my projects.
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
devenv.url = "github:cachix/devenv";
devenv.inputs.nixpkgs.follows = "nixpkgs";
};
When I first started using Flakes, the
<dependency>.inputs.nixpkgs.follows
attribute confused me as it was
not clear what it was doing. It overrides the nixpkgs
attribute
on devenv
's flake and tells it to follow our own nixpkgs
. We
basically propagate our current nixpkgs
state to any references of
it on our dependencies.
The only missing part now are the outputs. We declare the "public API" of our flake file here. This section defines packages, development environments (also called shells), modules and more.
outputs = { self, nixpkgs, devenv, ... } @ inputs:
let
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
in {
# our code comes here
};
There are a handful of default outputs defined on the flake schema. Some of them are:
packages.<system>.<name>
devShells.<system>.<name>
The forAllSystems
function helps generate these attributes so we
don't need to declare the system
multiple times. We accomplish this
through the lib.genAttrs
function receiving
lib.systems.flakeExposed
.
But... how do we use that? Let's declare a small python environment to
learn how.
devShells = forAllSystems (system:
let
pkgs = nixpkgs.legacyPackages."${system}";
in {
default = devenv.lib.mkShell {
inherit inputs pkgs;
modules = [
({ pkgs, ... }: {
languages.python = {
enable = true;
venv.enable = true;
venv.requirements = builtins.readFile ./requirements.txt;
};
})
];
};
});
This is enough to give us a full python environment when we run nix develop --impure
on our shell. Thankfully, devenv
takes care of
most of the complexity for each language setup! It's been a long time
since I configured anything for a new programming environment.
-
The distributed nature of Flakes is one of its strongest features.↩