.. _section_flakes: 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. .. code-block:: bash echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf or .. code-block:: bash echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf You should now be able to use the ``nix`` command. .. code-block:: bash 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. .. code-block:: bash nix flake show templates .. code-block:: bash 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: .. code-block:: bash 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: .. code-block:: bash 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: .. code-block:: bash nix flake init Nix Flakes require to be in a Git repository to work .. code-block:: bash git init && git add flake.nix You can now try to show the content of the flake: .. code-block:: bash 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: .. code-block:: bash 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: .. code-block:: bash ./result/bin/hello .. code-block:: bash 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: .. code-block:: bash nix run .#hello .. code-block:: bash Hello, world! It is now time to open this mysterious ``flake.nix`` file. .. code-block:: nix { 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 flake - ``inputs`` a set defining the dependencies of the flake - ``outputs`` 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``. .. code-block:: nix { 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: .. code-block:: bash 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: .. code-block:: json { "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 ``. 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: .. code-block:: nix { 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``. .. code-block:: 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 .. code-block:: bash nix registry list .. code-block:: bash 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: .. code-block:: bash nix flake show flake-utils .. code-block:: bash 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. .. code-block:: bash 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``. .. code-block:: bash nix flake show mypkgs .. code-block:: bash 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: .. code-block:: bash 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. .. code-block:: nix { 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``. .. code-block:: bash nix flake show .. code-block:: bash └───devShells └───x86_64-linux └───chordShell: development environment 'nix-shell' We can take a look at the ``flake.lock`` file: .. code-block:: json { "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. .. code-block:: bash nix develop .#chordShell