< Previous post | Next post >

[2025-03-08][Nix]

How to pick a specific package version in Nix

In this tutorial I will show you how to chose a specific version of a package using overlays. On my config I needed to use the last version of dwl window manager because all my patches were using the main branch of the repo. However, as of writing this, only the version 0.7 is available in nixpkgs. We can get this information from search.nixos.org/packages, but make sure to select correctly your nixpkgs branch.

nixpkgs results when searching for dwl, we can see that the
version is at 0.7 on branch unstable

By the way, you can check your nix channel with the following command:

andrew@nixos ~> sudo nix-channel --list
nixos https://nixos.org/channels/nixos-unstable

The one starting with "nixos" should indicate the version you are using. ⚠️ Omitting sudo will give you a different list, it seems like nix-channel can manage channels per users, so not using sudo will give you the channels for your user and not for the system ⚠️

Anyways, one way you could change the version of the package would be to copy the entire 📦source, change the branch that is used, and put the file in our config.

We will see that overlays are a better way to do this, but to begin with, we need to understand how **derivations** works. A derivation is like a recipe, it gives you the steps to ~cook~ build an application. Then this recipe can be evaluated by nix to actually build (and install) this application. Let's break the *dwl*'s derivation into multiple parts and explain it:

{
  lib,
  fetchFromGitea,
  installShellFiles,
  libX11,
  libinput,
  libxcb,
  libxkbcommon,
  pixman,
  pkg-config,
  stdenv,
  testers,
  wayland,
  wayland-protocols,
  wayland-scanner,
  wlroots,
  writeText,
  xcbutilwm,
  xwayland,
  # Boolean flags
  enableXWayland ? true,
  withCustomConfigH ? (configH != null),
  # Configurable options
  configH ?
    if conf != null then
      lib.warn ''
        conf parameter is deprecated;
        use configH instead
      '' conf
    else
      null,
  # Deprecated options
  # Remove them before next version of either Nixpkgs or dwl itself
  conf ? null,
}:

This part can be viewed as the arguments of the derivation (or the list of ingredients), it specifies the dependencies of our application (libX11, libinput, etc.), but is also able to take user defined arguments such as enableXWayland that defaults to true thanks to this notation ? true.

nix
stdenv.mkDerivation (finalAttrs: {
  pname = "dwl";
  version = "0.7";

  src = fetchFromGitea {
    domain = "codeberg.org";
    owner = "dwl";
    repo = "dwl";
    rev = "v${finalAttrs.version}";
    hash = "sha256-7SoCITrbMrlfL4Z4hVyPpjB9RrrjLXHP9C5t1DVXBBA=";
  };
  ...
  )

Here comes the part where we actually start writing our recipe. Notice that the whole derivation is wrapped into stdenv.mkDerivation which is actually a function to create derivations, you may take a look at this amazing documentation to learn more. In our case, this part is what we would like to modify: the version. variable is at 0.7 just as we saw on nixpkgs. However changing only this variable wouldn't work because the hash in the function fetchFromGitea is only valid for the branch 0.7. So we need to change to hash aswell, to do so we can leave it empty and nix will give it to us:

warning: found empty hash, assuming 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
...
error: hash mismatch in fixed-output derivation '/nix/store/gxrbli79q4wlpa33q1vhsvri4s10kwgz-source.drv':
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
            got:    sha256-qO7k2Sj4nWrXrM2FwNkgnAK2D76bIWa2q625k3jDBUA=

Now you can copy the hash, and we should obtain something like this:

stdenv.mkDerivation (finalAttrs: {
  pname = "dwl";
  version = "main";

  src = fetchFromGitea {
    domain = "codeberg.org";
    owner = "dwl";
    repo = "dwl";
    rev = finalAttrs.version;
    hash = "sha256-qO7k2Sj4nWrXrM2FwNkgnAK2D76bIWa2q625k3jDBUA=";
  };
  ...
  )

Here I specify the main version because this is the branch that I want. Notice that rev is actually the parameter that controls which branch is used, and it is getting the version value using finalAttrs.version. So as said previously, you could copy the whole source and put it in a local file accessible to your config:

# configuration.nix
{ ... }:
let 
  dwl-custom = pkgs.callPackage ./dwl/default.nix { configH = ./dwl-config.h; };
in
{
  environment.systemPackages = with pkgs; [
    dwl-custom
  ]

  # Rest of the config...
}

However, when the derivation will be updated, we will have to manually keep our local file updated. A better solution is to use overlays: they enable us modify just what we need on top of already existing derivations. The syntax is the following:

(
  final: prev: {

  }
)

final represent the result that we want to obtain (version attribute modified), and prev is the original state of our derivation (the source from nixpkgs).

(
  final: prev: {
      dwl = (prev.dwl.overrideAttrs {

      }
  }
))

Then in the body we can assign dwl to the previous value of dwl that we access via prev.dwl. But here we also call overrideAttrs which is a function that lets you override attributes as the name says.

( final: prev: {
  dwl = (prev.dwl.overrideAttrs {
    # Fetching main because my patches require the last version
    version = "main";

    src = pkgs.fetchFromGitea {
      domain = "codeberg.org";
      owner = "dwl";
      repo = "dwl";
      rev = final.dwl.version;
      hash = "sha256-qO7k2Sj4nWrXrM2FwNkgnAK2D76bIWa2q625k3jDBUA="; 
    };
  });
})

Then we add our modifications, notice that we access the modified version value with final.dwl.version.

# configuration.nix
{ ... }:
{
  nixpkgs.overlays = [
    ( final: prev: {
      dwl = (prev.dwl.overrideAttrs {
        # Fetching main because my patches require the last version
        version = "main";

        src = pkgs.fetchFromGitea {
          domain = "codeberg.org";
          owner = "dwl";
          repo = "dwl";
          rev = final.dwl.version;
          hash = "sha256-qO7k2Sj4nWrXrM2FwNkgnAK2D76bIWa2q625k3jDBUA="; 
        };
      });
    })
  ]

  environment.systemPackages = with pkgs; [
    dwl
    # ...
  ]
}

Finally, we can but this derivation in nixpkgs.overlays which takes a list of overlays, so you could do that for any number of packages that you want. And don't forget to add your package in the systemPackages! Note that it's possible, and maybe cleaner, to write your overlays in a separated file, just omit the parentheses around the block:

# overlays.nix
final: prev: {
  dwl = (prev.dwl.overrideAttrs {
    # Fetching main because my patches require the last version
    version = "main";

    src = pkgs.fetchFromGitea {
      domain = "codeberg.org";
      owner = "dwl";
      repo = "dwl";
      rev = final.dwl.version;
      hash = "sha256-qO7k2Sj4nWrXrM2FwNkgnAK2D76bIWa2q625k3jDBUA="; 
    };
  });
}

And import the file:

# configuration.nix
{ ... }:
{
  nixpkgs.overlays = [
    (import ./overlays.nix)
  ];

  environment.systemPackages = with pkgs; [
    dwl
    # ...
  ]
}

Here you go!

As a final note, sometimes you may also want to change the parameters of a derivation. This is what the override function does. In my example I want to specify the source of my dwl config file, dwl-config.h. In this derivation its controlled with the configH parameter (here its also handling an old option called simply conf).

{
# ...
configH ?
    if conf != null then
      lib.warn ''
        conf parameter is deprecated;
        use configH instead
      '' conf
    else
      null,
  # Deprecated options
  # Remove them before next version of either Nixpkgs or dwl itself
  conf ? null,
}:

So you can use override as follows:

dwl = ((prev.dwl.overrideAttrs {
    # Fetching main because my patches require the last version
    version = "main";

    src = prev.fetchFromGitea {
      domain = "codeberg.org";
      owner = "dwl";
      repo = "dwl";
      rev = final.dwl.version;
      hash = "sha256-qO7k2Sj4nWrXrM2FwNkgnAK2D76bIWa2q625k3jDBUA="; 
    };

}).override {
  # My own config
  configH = ./dwl-config.h;
});

I just added a pair of parentheses to wrap everything. In conclusion:

< Previous post | Next post >