Skip to content

Latest commit

 

History

History
479 lines (313 loc) · 27.5 KB

nix-usage-flakes.md

File metadata and controls

479 lines (313 loc) · 27.5 KB

About this document

This document explains how to take advantage of software provided by Nix for people new to the Nix package manager. This guide uses this project for examples but focuses on introducing general Nix usage, which also applies to other projects using Nix.

This project requires an experimental feature of Nix called flakes. To understand more about what flakes are and the consequences of using a still-experimental feature of Nix, please see the provided introduction to Nix.

How this project uses Nix

This project uses Nix to download all necessary dependencies and build everything from source. In this regard, Nix is helpful as not just a package manager but also a build tool. Nix helps us get from raw source files to built executables in a package we can install with Nix.

Within this project, the various files with a .nix extension are Nix files, each of which contains an expression written in the Nix expression language used by the Nix package manager to specify packages. If you get proficient with this language, you can use these expressions as a starting point to compose your own packages beyond what's provided in this project.

Prerequisites

If you're new to Nix consider reading the provided introduction.

This project supports Linux on x86-64 machines.

That may affect your ability to follow along with examples.

Otherwise, see the provided Nix installation and configuration guide if you have not yet set Nix up.

To continue following this usage guide, you will need Nix's experimental flakes feature. You can enable this globally or use an alias such as the following:

alias nix-flakes = nix --extra-experimental-features 'nix-command flakes'

Working with Nix

Though comprehensively covering Nix is beyond the scope of this document, we'll go over a few commands illustrating some usage of Nix with this project.

Referencing flake projects

Most of this document illustrates usage of the nix command, which provides a number of subcommands and centralizes Nix usage.

Many of the nix subcommands accept references to flake-enabled projects. A flake is specified with a Nix expression saved in a file named flake.nix. This file should be at the root of a project. We can reference both local and remote flake projects.

Here are some common forms we can use to reference flake projects:

Syntax Location
. flake in the current directory
<path> flake in some other file path (must have a slash)
<registry> reference to flake from the registry (see nix registry list)
git+<url> latest flake in the default branch of a Git repository
git+<url>?ref=<branch> latest flake in a branch of a Git repository
git+<url>?rev=<commit> flake in a specific commit of a Git repository
github:<owner>/<repo> latest flake in the default branch of a GitHub repository
github:<owner>/<repo>/<branch> latest flake in a branch of a GitHub repository
github:<owner>/<repo>/<commit> flake in a specific commit of a GitHub repository

This table introduces an angle-bracket notation for syntactic forms with components that change with context. This notation is used throughout this document.

Referencing local flake projects is easy enough with file paths. But the URL-like notation for remote flake projects can get verbose. Furthermore, some of these references are not fixed. For example, Git branches point to different commits over time.

To manage flake references, Nix provides a flakes registry. Upon installation this registry is prepopulated with some global entries:

nix registry list
…
global flake:nixos-homepage github:NixOS/nixos-homepage
global flake:nixos-search github:NixOS/nixos-search
global flake:nixpkgs github:NixOS/nixpkgs/nixpkgs-unstable
global flake:nur github:nix-community/NUR
global flake:patchelf github:NixOS/patchelf
global flake:poetry2nix github:nix-community/poetry2nix
global flake:pridefetch github:SpyHoodle/pridefetch
global flake:sops-nix github:Mic92/sops-nix
global flake:systems github:nix-systems/default
global flake:templates github:NixOS/templates

For example, rather than referencing the flake on the nixpkgs-unstable branch of the Nixpkgs GitHub repository with github:NixOS/nixpkgs/nixpkgs-unstable, we can use the simpler identifier nixpkgs.

If we want to point to a different branch but still use an identifier from the registry, we can do so by extending it with the branch. For example, the flakes identifier nixpkgs is the same as nixpkgs/nixpkgs-ustable, but we can also use nixpkgs/nixos-24.05 to override the branch and point to the NixOS 24.05 release branch.

Note that registries have mutable references, but Nix knows how to rebuild the snapshot referenced for some of these references deterministically. For example, when referencing a GitHub repository via a registry reference, Nix will take note of the commit ID of the snapshot retrieved. Nix typically stores this information required for reproducibility in a lock file called flake.lock adjacent to flake.nix.

Inspecting flake outputs

A flake can provide a variety of outputs that can be used in different contexts. A few of these outputs include the packages we can build and install.

We can use nix flake show to see the outputs provided by any flake, local or remote, by providing a flake reference discussed in the previous section. Here's an example of inspecting the flake of this project locally:

nix flake show .
git+file:///home/shajra/src/shajra-keyboards
├───apps
│   └───x86_64-linux
…
│       ├───flash-ergodoxez: app
│       ├───flash-model01: app
│       ├───flash-model100: app
│       ├───flash-moonlander: app
│       └───licenses-thirdparty: app
├───checks
│   └───x86_64-linux
│       └───ci: derivation 'shajra-keyboards-ci'
├───legacyPackages
│   └───x86_64-linux omitted (use '--legacy' to show)
├───overlays
│   └───default: Nixpkgs overlay
└───packages
    └───x86_64-linux
        ├───default: package 'shajra-keyboards-ci'
        ├───flash-ergodoxez: package 'flash-ergodoxez'
        ├───flash-model01: package 'flash-model01'
        ├───flash-model100: package 'flash-model100'
        ├───flash-moonlander: package 'flash-moonlander'
        └───licenses-thirdparty: package 'shajra-keyboards-licenses'

Flake outputs are organized in a tree of attributes. References to paths of attributes are dot-delimited. There is a standard schema for the output attribute tree of a flake. Nix permits outputs outside this schema.

This document focuses on packages provided by the packages output attribute. Notice that a flake offers packages for different (but rarely all) system architectures.

For commands like nix flake show that expect a flake reference as an argument, . is assumed as default if an argument isn't provided. So nix flake show is equivalent to nix flake show ..

Referencing flake outputs

Many of the nix subcommands work with references to flakes outputs. These references are called installables. There are many types of installables (hence the general name). In this document, we'll hone in on the following forms:

  • <flake> to select a default output from
  • <flake>#<output reference> for a reference to packages(s) within a flake.

In this notation, <flake> is the same reference to a local or remote flake project discussed in the prior section.

When the installable is just a flake reference, the called nix subcommand will generally look for the packages.<system>.default attribute path within the flake. So <flake> will generally be the same as <flake>#packages.<system>.default. One exception to this rule, nix run will first look for <flake>#apps.<system>.default, and then for <flake>#packages.<system>.default.

Installables can also reference an output of a flake (<output reference> above) directly in a couple of ways:

Output reference Example installable
<output attribute>.<system>.<name> .#packages.x86_64-linux.licenses-thirdparty
<name> .#licenses-thirdparty

The first way is the most explicit, by providing the full attribute path we can see with nix flake show. But this requires specifying the package's system architecture.

With the second form, with the shorter <name> package reference, Nix will detect the system we're on and also search some attributes in a precedence order for the provided name:

  1. apps.<system>.<name> (nix run only)
  2. packages.<system>.<name> (all subcommands accepting installables)
  3. legacyPackages.<system>.<name> (all subcommands accepting installables)

Which attributes are searched depends on the nix subcommand called.

For commands accepting installables as an argument, if none are provided, then . is assumed. Nix will attempt to read a flake.nix file in the current directory. If not found, Nix will recursively search parent directories to find a flake.nix file.

Searching flakes for packages

We can use the nix search command to see what package derivations a flake contains. For example, from the root directory of this project, we can execute:

nix search .
* packages.x86_64-linux.default

* packages.x86_64-linux.flash-ergodoxez
  Flash ZSA Technology Lab's Ergodox EZ Keyboard

* packages.x86_64-linux.flash-model01
  Flash Keyboardio's Model 01 Keyboard

* packages.x86_64-linux.flash-model100
…

If a flake has a lot of packages, you can pass regexes to prune down the search. Returned values will match all the regexes provided.

We can also search a remote repository for packages to install. For example, Nixpkgs is a central repository for Nix, providing several thousand packages. We can search the “nixpkgs-unstable” branch of Nixpkgs' GitHub repository for packages that match both “gpu|opengl|accel” and “terminal” as follows:

nix search github:NixOS/nixpkgs/nixpkgs-unstable 'gpu|opengl|accel' terminal

As discussed in a previous section, we can use the flakes registry identifier of nixpkgs instead of the longer github:NixOS/nixpkgs/nixpkgs-unstable to save some typing:

nix search nixpkgs 'gpu|opengl|accel' terminal
+end_src

#+name: nix-search-remote-concise
#+begin_src sh :dir .. :results output :exports results
nix search nixpkgs 'gpu|opengl|accel' terminal | ansifilter
* legacyPackages.x86_64-linux.alacritty (0.13.1)
  A cross-platform, GPU-accelerated terminal emulator

* legacyPackages.x86_64-linux.darktile (0.0.11)
  A GPU rendered terminal emulator designed for tiling window managers

* legacyPackages.x86_64-linux.kitty (0.32.0)
  A modern, hackable, featureful, OpenGL based terminal emulator

* legacyPackages.x86_64-linux.rio (0.0.34)
  A hardware-accelerated GPU terminal emulator powered by WebGPU

* legacyPackages.x86_64-linux.wezterm (20230712-072601-f4abf8fd)
  GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust

If we're curious about what version of WezTerm is available in NixOS's latest release, we can specialize the installable we're searching as follows:

nix search nixpkgs/nixos-24.05#wezterm
* legacyPackages.x86_64-linux.wezterm (20240203-110809-5046fc22)
  GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust

Here /nixos-24.05 overrides the default nixpkgs-unstable branch of the registry entry, and the #wezterm suffix searches not just the flake, but a specific package named wezterm, which will either be found or not (there's no need for regexes to filter further).

You may also notice that the Nixpkgs flake outputs packages under the legacyPackages attribute instead of the packages. The primary difference is that packages are flatly organized under packages, while legacyPackages can be an arbitrary tree. legacyPackages exists specifically for the Nixpkgs project, a central project to the Nix ecosystem that has existed long before flakes. Beyond Nixpkgs, you don't have to think much about legacyPackages. Packages from all other flakes should generally be found under packages.

Building installables

The following result is one returned by our prior execution of nix search .:

* packages.x86_64-linux.licenses-thirdparty
  License information for shajra-keyboards project

We can see that a package can be accessed with the packages.x86_64-linux.licenses-thirdparty output attribute path of the project's flake. Not shown in the search results above, this package happens to provide the executable bin/shajra-keyboards-licenses.

We can build this package with nix build from the project root:

nix build .#licenses-thirdparty

As discussed in prior sections, the positional arguments to nix build are installables. Here, the . indicates that our flake should be found from the current directory. Within this flake, we look for a package with an attribute name of licenses-thirdparty. We didn't have to use the full attribute path packages.x86_64-linux.licenses-thirdparty because nix build will automatically look in the packages attribute for the system it detects we're on.

If we omit the attribute path of our installable, Nix tries to build a default package, which it expects to find under the flake's packages.<system>.default. For example, if we ran just nix build ., Nix would expect to find a flake.nix in the current directory with an output providing a packages.<system>.default attribute with a package to build.

Furthermore, if we didn't specify an installable at all, Nix would assume we're trying to build the default package of the flake found from the current directory. So, the following invocations are all equivalent:

  • nix build
  • nix build .
  • nix build .#packages.<system>.default

All packages built by Nix are stored in /nix/store. Nix won't rebuild packages found there. Once a package is built, its content in /nix/store is read-only (until the package is garbage collected, discussed later).

After a successful call of nix build, you'll see one or more symlinks for each package requested in the current working directory. These symlinks, by default, have a name prefixed with “result” and point back to the respective build in /nix/store:

readlink result*
/nix/store/v6bnwj40fnxqrssvhjd6v10398bqyw43-shajra-keyboards-licenses

Following these symlinks, we can see the files the project provides:

tree -l result*
result
└── bin
    └── shajra-keyboards-licenses

2 directories, 1 file

It's common to configure these “result” symlinks as ignored in source control tools (for instance, for Git within a .gitignore file).

nix build has a --no-link switch in case you want to build packages without creating “result” symlinks. To get the paths where your packages are located, you can use nix path-info after a successful build:

nix path-info .#licenses-thirdparty
/nix/store/v6bnwj40fnxqrssvhjd6v10398bqyw43-shajra-keyboards-licenses

Running commands in a shell

We can run commands in Nix-curated environments with nix shell. Nix will take executables found in packages, put them in an environment's PATH, and then execute a user-specified command.

With nix shell, you don't have to build the package first with nix build or mess around with “result” symlinks. nix shell will build any necessary packages required.

For example, to get the help message for the shajra-keyboards-licenses executable provided by the package selected by the licenses-thirdparty attribute path output by this project's flake, we can call the following:

nix shell \
    .#licenses-thirdparty \
    --command shajra-keyboards-licenses --help
Third Party Licenses for "shajra-keyboards"
===========================================

This project is a build script that uses Nix to download third
party source codes, modify them as per their respective
…

Similarly to nix build, nix shell accepts installables as positional arguments to select packages to put on the PATH.

The command to run within the shell is specified after the --command switch. nix shell runs the command in a shell set up with a PATH environment variable that includes all the bin directories provided by the selected packages.

If you only want to enter an interactive shell with the configured PATH, you can drop the --command switch and following arguments.

nix shell also supports an --ignore-environment flag that restricts PATH to only packages selected, rather than extending the PATH of the caller's environment. With --ignore-environment, the invocation is more sandboxed.

As with nix build, nix shell will select default packages for any installable that is only a flake reference. If no installable is provided to nix shell, the invocation will look for the default package under the packages.<system>.default attribute output by a flake assumed to be in the current directory. So, the following invocations are all equivalent:

  • nix shell
  • nix shell .
  • nix shell .#packages.<system>.default

Running installables

The nix run command allows us to run executables from packages with a more concise syntax than nix shell with a --command switch.

The main difference from nix shell is that nix run detects which executable to run from a package. If we want something other than what can be detected, we must continue using nix shell with --command.

As with other nix subcommands, nix run accepts an installable as an argument (but only one). If none is provided, then . is assumed.

If the provided installable is only a flake reference with no package selected, then nix run searches the following flake output attribute paths, in order, for something to run:

  • apps.<system>.default
  • packages.<system>.default

In flakes, applications are just packages that have been expanded with metadata. This metadata includes explicitly specifying which executable to use with nix run. Otherwise, for plain packages, nix run looks at metadata on the package to guess the binary to execute within the package.

If only a name is provided, then nix run searches in order through the following output attribute paths:

  • apps.<system>.<name>
  • packages.<system>.<name>
  • legacyPackages.<system>.<name>

And as always, we can specify a full output attribute path explicitly if nix run's search doesn't find what we want to run.

Here's the nix run equivalent of the nix shell invocation from the previous section:

nix run .#licenses-thirdparty  -- --help
Third Party Licenses for "shajra-keyboards"
===========================================

This project is a build script that uses Nix to download third
party source codes, modify them as per their respective
…

We can see some of the metadata of this package with the --json switch of nix search:

nix search --json .#licenses-thirdparty | jq .
{
  "packages.x86_64-linux.licenses-thirdparty": {
    "description": "License information for shajra-keyboards project",
    "pname": "shajra-keyboards-licenses",
    "version": ""
…

The “pname” field in the JSON above indicates the package's name. In practice, the package's name may or may not differ from flake output name of the installable.

nix run works because the package selected by the output attribute name licenses-thirdparty selects a package with a package name “shajra-keyboards-licenses” that is the same as the executable provided at bin/shajra-keyboards-licenses.

If we want something other than what can be detected, then we have to continue using nix shell with --command.

nix run and nix shell with remote flakes

In the examples above, we've used selected packages from this project's flake, like .#licenses-thirdparty. But one benefit of flakes is that we can refer to remote flakes just as easily, like nixpkgs#hello. Referencing remote flakes helps us quickly build environments with nix shell or run commands with nix run without committing to install software.

Here's a small example.

nix run nixpkgs#hello
Hello, world!

When using nix shell, we can even mix local flake references with remote ones, all in the same invocation:

nix shell --ignore-environment \
    nixpkgs#which \
    .#licenses-thirdparty \
    --command which shajra-keyboards-licenses
/nix/store/v6bnwj40fnxqrssvhjd6v10398bqyw43-shajra-keyboards-licenses/bin/shajra-keyboards-licenses

What we do with local flake references can work just as well with remote flake references.

Installing and uninstalling programs

We've seen that we can build programs with nix build and execute them using the “result” symlink (result/bin/*). Additionally, we've seen that you can run programs with nix shell and nix run. But these additional steps and switches/arguments can feel extraneous. It would be nice to have the programs on our PATH. This is what nix profile is for.

nix profile maintains a symlink tree of installed programs called a profile. The default profile is at ~/.nix-profile. For non-root users, if this doesn't exist, nix profile will create it as a symlink pointing to /nix/var/nix/profiles/per-user/$USER/profile. But you can point nix profile to another profile at any writable location with the --profile switch.

This way, you can just put ~/.nix-profile/bin on your PATH, and any programs installed in your default profile will be available for interactive use or scripts.

To install the shajra-keyboards-licenses executable, which is provided by the licenses-thirdparty output of our flake, we'd run the following:

nix profile install .#licenses-thirdparty

We can see this installation by querying what's been installed:

nix profile list
Index:              0
Flake attribute:    packages.x86_64-linux.licenses-thirdparty
Original flake URL: git+file:///home/shajra/src/shajra-keyboards
Locked flake URL:   git+file:///home/shajra/src/shajra-keyboards
Store paths:        /nix/store/v6bnwj40fnxqrssvhjd6v10398bqyw43-shajra-keyboards-licenses

If we want to uninstall a program from our profile, we do so by the index from this list:

nix profile remove 0

We can also provide a regex matching the full attribute path of the flake:

nix profile remove '.*licenses-thirdparty'

Also, if you look at the symlink-resolved location for your profile, you'll see that Nix retains the symlink trees of previous generations of your profile. You can even roll back to an earlier profile with the nix profile rollback subcommand. You can delete old generations of your profile with the nix profile wipe-history subcommand.

Garbage collection

Every time you build a new version of your code, it's stored in /nix/store. You can call nix store gc to purge unneeded packages. Programs that should not be removed by nix store gc can be found by starting with symlinks stored as garbage collection (GC) roots under three locations:

  • /nix/var/nix/gcroots
  • /nix/var/nix/profiles
  • /nix/var/nix/manifests.

For each package, Nix is aware of all files that reference back to other packages in /nix/store, whether in text files or binaries. This dependency tracking helps Nix ensure that dependencies of packages reachable from GC roots won't be deleted by garbage collection.

Each “result” symlink created by a nix build invocation has a symlink in /nix/var/nix/gcroots/auto pointing back to it. So we've got symlinks in /nix/var/nix/gcroots/auto pointing to “result” symlinks in our projects, which then reference the actual built project in /nix/store. These chains of symlinks prevent packages built by nix build from being garbage collected.

If you want a package you've built with nix build to be garbage collected, delete the “result” symlink created before calling nix store gc. Breaking symlink chains under /nix/var/nix/gcroots removes protection from garbage collection. nix store gc will clean up broken symlinks when it runs.

Everything under /nix/var/nix/profiles is also considered a GC root. This is why users, by convention, use this location to store their Nix profiles with nix profile.

Also, note if you delete a “result*” link and call nix store gc, though some garbage may be reclaimed, you may find that an old profile is keeping the program alive. Use the nix profile wipe-history command to delete old profiles before calling nix store gc.

It's also good to know that nix store gc won't delete packages referenced by any running processes. In the case of nix run no garbage collection root symlink is created under /nix/var/nix/gcroots, but while nix run is running nix store gc won't delete packages needed by the running command. However, once the nix run call exits, any packages pulled from a substituter or built locally are candidates for deletion by nix store gc. If you called nix run again after garbage collecting, those packages may be pulled or built again.

Next steps

This document has covered a fraction of Nix usage, hopefully enough to introduce Nix in the context of this project.

An obvious place to start learning more about Nix is the official documentation.

Bundled with this project is a small tutorial on the Nix language.

All the commands we've covered have more switches and options. See the respective man pages for more.

We didn't cover much of Nixpkgs, the gigantic repository of community-curated Nix expressions.

The Nix ecosystem is vast. This project and documentation illustrate only a small sample of what Nix can do.