Flakes

Revision as of 13:09, 26 July 2020 by imported>Mic92

Nix flakes is some upcoming feature in the Nix package manager. It allows to download nix expressions from other sources in a declarative way by specifying them a flake.nix file. Those sources are called flakes and also have a flake.nix where they can describe their own dependencies. Sources can be tarballs, git, local directories or mercurial repositories. It makes evaluation reproducible with providing a lock called flake.lock. This lock file describes provides hashes for sources and locks the revision of external version control system. Nix flakes intents to replace nix channels and the nix search path (NIX_PATH).

Installing nix flakes

Right now nix flakes are only available in the unstable nix version and need to be enabled in nix.conf as well. In NixOS this can be achieved with the following line in configuration.nix

{ pkgs, ... }: {
   nix = {
    package = pkgs.nixUnstable;
    extraOptions = ''
      experimental-features = nix-command flakes
    '';
   };
}

On non-nixos system install `nixUnstable` in your environment:

$ nix-env -iA nixUnstable

Edit either ~/.config/nix/nix.conf or /etc/nix/nix.conf and add:

experimental-features = nix-command flakes

This is needed to expose the Nix 2.0 CLI and flakes support that are hidden behind feature-flags.

Finally, if the Nix installation is in multi-user mode, don’t forget to restart the nix-daemon.

There is no official installer yet, but it is possible to download the latest snapshot nix from hydra as shown in here github action.

Basic project usage

Warning: flake makes a strong assumption that the folder is a git or mercurial repository. It doesn’t work outside of them.

In your repo, run nix flake init to generate the flake.nix file. Then run git add flake.nix to add it to the git staging area, otherwise nix will not recognize that the file exists.

See also https://www.tweag.io/blog/2020-05-25-flakes/

Flake schema

The flake.nix file is a Nix file but that has special restrictions (more on that later).

It has 3 top-level attributes:

  • description which is self…describing
  • input is an attribute set of all the dependencies of the flake. The schema is described below.
  • output is a function of one argument that takes an attribute set of all the realized inputs, and outputs another attribute set which schema is described below.

Input schema

This is not a complete schema but should be enough to get you started:

{
  inputs.bar = { url = "github:foo/bar/branch"; flake = false; }
}

The bar input is then passes to the output schema

Output schema

This is described in the nix package manager src/nix/flake.cc in CmdFlakeCheck.

Where:

  • <system> is something like "x86_64-linux", "aarch64-linux", "i686-linux", "x86_64-darwin"
  • <attr> is an attribute name like "hello".
  • <flake> is a flake name like "nixpkgs".
  • <store-path> is a /nix/store.. path
{ self, ... }@inputs:
{
  # Executed by `nix flake check`
  checks."<system>"."<attr>" = derivation;
  # Executed by `nix build .#<name>`
  packages."<system>"."<attr>" = derivation;
  # Executed by `nix build .`
  defaultPackage."<system>" = derivation;
  # Executed by `nix run .#<name>
  apps."<system>"."<attr>" = {
    type = "app";
    program = "<store-path>";
  };
  defaultApp."<system>" = { type = "app"; program = "..."; };
  
  # Used for nixpkgs packages, also accessible via `nix build .#<name>`
  legacyPackages."<system>"."<attr>" = derivation;
  # Default overlay, for use in dependent flakes
  overlay = final: prev: { };
  # Same idea as overlay but a list or attrset of them.
  overlays = {};
  # Default module, for use in dependent flakes
  nixosModule = { config }: { options = {}; config = {}; };
  # Same idea as nixosModule but a list or attrset of them.
  nixosModules = {};
  # Attrset of nixos configurations by hostname.
  nixosConfigurations."<hostname>" = {};
  # TODO: Derivations?
  hydraJobs = TODO;
  # Used by `nix flake init -t <flake>`
  defaultTemplate = {
    path = "<store-path>";
    description = "template description goes here?";
  };
  # Used by `nix flake init -t <flake>#<attr>`
  templates."<attr>" = { path = "<store-path>"; description = ""; );
}

The nix flakes command

The nix flake subcommand is described here.

Using nix flakes with NixOS

nixos-rebuild switch will reads its configuration from /etc/nixos/flake.nix if is present.

A basic nixos flake.nix could look like this:

{
  outputs = { self, nixpkgs }: {
     # replace 'joes-desktop' with your hostname here.
     nixosConfigurations.joes-desktop = nixpkgs.lib.nixosSystem {
       system = "x86_64-linux";
       modules = [ ./configuration.nix ];
     }
  };
}

nixos-rebuild also allows to specify different flake using the --flake flag (# is optional):

$ sudo nixos-rebuild switch --flake '.#'

By default nixos-rebuild will use the currents system hostname to lookup the right nixos configuration in nixosConfigurations. You can also override this by using appending it to the flake parameter:

$ sudo nixos-rebuild switch --flake '/etc/nixos#joes-desktop'

To switch a remote configuration, use:

$ nixos-rebuild --flake .#mymachine \
  --target-host mymachine-hostname --build-host localhost \
  switch
Warning: Remote building seems to be broken at the moment, which is why the build host is set to “localhost”.

Super fast nix-shell

One of the nix feature of the Flake edition is that Nix evaluations are cached.

Let’s say that your project has a shell.nix file that looks like this:

{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell {
  buildInputs = [
    nixpkgs-fmt
  ];

  shellHook = ''
    # ...
  '';
}

Running nix-shell can be a bit slow and take 1-3 seconds.

Now create a flake.nix file in the same repository:

{
  description = "my project description";

  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem
      (system:
        let pkgs = nixpkgs.legacyPackages.${system}; in
        {
          devShell = import ./shell.nix { inherit pkgs; };
        }
      );
}

Run git add flake.nix so that Nix recognizes it.

And finally, run nix develop. This is what replaces the old nix-shell invocation.

Exit and run again, this command should now be super fast.

Warning: TODO: there is an alternative version where the defaultPackage is a pkgs.buildEnv that contains all the dependencies. And then nix shell is used to open the environment.

Direnv integration

Assuming that the flake defines a devShell output attribute and that you are using direnv. Here is how to replace the old use nix stdlib function with the faster flake version:

use_flake() {
  watch_file flake.nix
  watch_file flake.lock
  eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile")"
}

Copy this in ~/.config/direnv/lib/use_flake.sh or in ~/.config/direnv/direnvrc or directly in your project specific .envrc.

With this in place, you can now replace the use nix invocation in the .envrc file with use flake:

# .envrc
use flake

The nice thing about this approach is that evaluation is cached, and that the project’s shell is now protected from the nix garbage-collector.

Optimize the reloads

Nix Flakes has a Nix evaluation caching mechanism. Is it possible to expose that somehow to automatically trigger direnv reloads?

With the previous solution, direnv would only reload iff the flake.nix or flake.lock files have changed. This is not completely precise as the flake.nix file might import other files in the repository.

Pushing Flake inputs to Cachix

Flake inputs can also be cached in the Nix binary cache!

$ nix flake archive --json \
  | jq -r '.path,(.inputs|to_entries[].value.path)' \
  | cachix push $cache_name

Build specific attributes in a flake repository

When in the repository top-level, run nix build .#<attr>. It will look in the legacyPackages and packages output attributes for the corresponding derivation.

Eg, in nixpkgs:

$ nix build .#hello

See also