Installing a Specific Version of a Package with Nix
A common question when starting out with nix is "how do I install some particular version of some package?" This is not only a surprisingly complicated problem to solve for new nix users, but also a surprisingly difficult question to find answers to on your search engine of choice.
Here, I'm going to cover a few different ways of installing a particular package version using nix, highlighting the pros and cons of each. I'll be focusing on declarative nix configuration, with the nix repository pinned to a particular version using either the older niv or the newer flakes. The examples I show will be for installing a package into a development environment. See my other post here for instructions on how to set up a basic dev environment. I'll cover some basic setup again here, but see that post for rationale and discussion.
Contents
- Philosophy: Why Is this so Hard?
- Setup: a Reproducible Dev Environment
- Option One: A Different Nixpkgs Version
- Option Two: Someone Else's Overlay
- Option Three: Override Package Attributes
- Overlays: Using Your New Pin Everywhere
- Summary
Philosophy: Why Is this so Hard?
Before we get to the examples, I think it's worth discussing why this is not more straightforward.
My personal theory here is that people (like myself) come to nix thinking about it as though it's equivalent to their other experiences with "declarative, reproducible" version management: lockfiles. I can tell cargo, pip, or node to install a specific set of versions for all of the packages that I need, so that my application will behave consistently. I have all versions of all packages at my disposal, because I am the one verifying that they all work together to produce some useful result.
Nixpkgs though is actually more like (and indeed is) the package manager in a
Linux distribution, such as apt
or yum
. These package managers give you
access to a suite of known good versions of packages, which have been tested
to work well together and to provide a functional system. In the lockfile
analogy, the Linux system itself is the final useful result, and the set of
packages used to produce that are what you gain access to via the package
manager. So, nixpkgs gives you the vetted lockfile of packages needed to produce
some version of NixOS (the operating system).
Just like in your day-to-day Linux desktop installation, installing a custom
version of a package that's not in the standard repositories involves a bit more
ceremony than apt install rustup
. You often will need to clone the source,
build the package from scratch, and then copy the build artifacts into the
appropriate locations on your system. If you're lucky, you can just download a
pre-built binary. Nix allows you to do both of these things, in addition to some
fancy tricks not available on standard Linux distros. Not only that, it gives
you the tools to do the exact same thing declaratively, and in a way that can't
bork your entire system by breaking system dependencies!
However, the trick is that nix was built to be a package manager for a Linux distribution. When you step into the role of building derivations, you're stepping into a role like a debian maintainer, not the role of a user installing packages on the Linux desktop. Most of the nix docs are written with this audience in mind, and so it can be a huge slog to figure out how to do some (supposedly) simple task. Luckily, it's getting easier, and the docs are getting better all the time. And in the meantime, there are helpful blogs like this one!
Setup: a Reproducible Dev Environment
First, you'll want to have your reproducible dev environment with pinned versions of the nixpkgs repository. Again, see here for a more in depth discussion of the general project structure. While I'll cover both flakes and niv throughout, I would recommend going with flakes unless you have a good reason not to.
Setup: Flakes
Run nix flake init
in your project directory, and then git add flake.nix
.
You'll almost always want the following boilerplate:
{
description = "some project";
inputs = {
# Your preferred primary nix relesae
nixpkgs.url = "nixpkgs/release-22.11";
# Proivdes legacy compatibility for nix-shell
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
# Provides some nice helpers for multiple system compatibility
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils, flake-compat }:
# Calls the provided function for each "default system", which
# is the standard set.
flake-utils.lib.eachDefaultSystem
(system:
# instantiate the package set for the supported system, with our
# rust overlay
let pkgs = import nixpkgs { inherit system; };
in
# "unpack" the pkgs attrset into the parent namespace
with pkgs;
{
devShell = mkShell {
# Packages required for development.
buildInputs = [
# Add your system dependencies here
bashInteractive
coreutils
fd
gnumake
gnused
];
};
});
}
Add this to shell.nix
for legacy compatibility with nix-shell
commands, if
needed (if you do not need nix-shell
compatibility, you can remove
flake-compat
from the flake inputs):
# Legacy compat for folks not on nix with flakes.
#
# flake-compat reads the flake and provides shellNix (for nix-shell) and defaultNix
# (for nix-build). We only need shell here, since we're only using nix for the
# dev environment atm.
(import (
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
) {
src = ./.;
}).shellNix
And I highly recommend using direnv to make activating the nix environment
seamless (don't forget to install an editor plugin!). All you need is the
following in your .envrc
:
use flake
Setup: Niv
First, you'll need to install niv on your system. If you're using nix
, you can
do that with nix-env -iA nixpkgs.niv
. Follow the instructions at the
repo, and then add the following in shell.nix
:
# This file returns the evaluation of `mkShell`, which is the same thing we
# set to the `devShell` propety in the flake output above. The difference
# is that instead of our inputs being pinned by a flake, they are pinned in
# the `nix/sources.nix` file that niv generates.
let
sources = import ./nix/sources.nix;
# this is assuming you used the default `nixpkgs` name for nixpkgs. We can
# call this function with no arguments, since we don't have any overlays.
pkgs = import sources.nixpkgs {};
in
# "unpack" the pkgs attrset into the parent namespace, so we can use like
# `mkShell` instead of `pkgs.mkShell`
with pkgs;
mkShell {
# Your desired packages go here
buildInputs = [ bashInteractive fd gnumake ];
}
Option One: A Different Nixpkgs Version
Often the easiest option, one way to go about installing a particular version of
a package is just to use whichever version of nixpkgs contained that package
version! You can use this both for pinning to an older version, by pinning to
some previous revision of nixpkgs, and for using a more current version, by
pinning to unstable
or even master.
The general pattern here is to add an input (flakes) or a source (niv) pointing to the alternative revision in question, and then install the package from that input/source.
When looking for versions of packages in the previous stable, current stable, or unstable branches, you can check here. When looking for an older or specific version of a package, you can check here to find a nix revision with the version you're looking for.
In these examples, we'll include examples of both installing a package from an
alternative nix branch (unstable
) and for installing from a particular
revision. We'll install rust-analyze
from unstable
. For the particular
revision, we'll be using a revision that includes version 8.3.2 of the fd
utility for finding files:
bf972dc380f36a3bf83db052380e55f0eaa7dcb6.
Different Nixpkgs Version: Flakes
Add the new sources to the inputs
:
inputs = {
nixpkgs.url = "nixpkgs/release-22.11";
# Unstable nix. You can name this whatever you want. We'll call it nixpkgs-unstable.
nixpkgs-unstable.url = "nixpkgs/nixpkgs-unstable";
# Some particular revision for installing fd
nixpkgs-fd = "github:NixOS/nixpkgs/bf972dc380f36a3bf83db052380e55f0eaa7dcb6";
};
And then use the new inputs in the buildInputs
for the dev shell after
instantiating the package sets:
outputs = { self, nixpkgs, nixpkgs-unstable, nixpkgs-fd, flake-utils, flake-compat }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs { inherit system; };
pkgs-unstable = import nixpkgs-unstable { inherit system; };
pkgs-fd = import nixpkgs-fd { inherit system; };
in
with pkgs;
{
devShell = mkShell {
# Packages required for development.
buildInputs = [
pkgs-unstable.rust-analyzer # install rust-analyzer from unstable
pkgs-fd.fd # install fd from the appropriate revision
];
};
});
Different Nixpkgs Version: Niv
Niv is also straightforward. We'll use the CLI to add both the unstable
branch
and a particular revision for fd
:
$ niv add NixOS/nixpkgs --name nixpkgs-unstable --branch nixpkgs-unstable
$ niv add NixOS/nixpkgs --name nixpkgs-fd --rev bf972dc380f36a3bf83db052380e55f0eaa7dcb6
From there, we now have nixpkgs-unstable
and nixpkgs-fd
attributes in our
sources.nix
. We'll import them in the same way we do regular nixpkgs
and use
them in our build inputs:
let
sources = import ./nix/sources.nix;
pkgs = import sources.nixpkgs {};
pkgs-unstable = import sources.nixpkgs-unstable {};
pkgs-fd = import sources.nixpkgs-fd {};
in
with pkgs;
mkShell {
# Your desired packages go here
buildInputs = [
pkgs-unstable.rust-analyzer # install rust-analyzer from unstable
pkgs-fd.fd # install fd from the appropriate revision
];
}
Option Two: Someone Else's Overlay
Sometimes, someone else already maintains an overlay to help you install alternative versions of a package. This is limited to a small set of packages for which installing alternative versions is common enough to justify the community effort, but if your package is among that set, this is generally as easy as using an alternative nixpkgs revision. Some packages that have well-supported overlays include:
An overlay provides additional or overridden attributes in the package set to
which the overlay is applied. So, for example, the emacs overlay provides
a new attribute emacs-nox
, which is emacs compiled with no support for
non-terminal operation, as well as the standard emacs
attribute. The rust
overlay provides a rust-bin
attribute that includes the Rust compiler, cargo,
and so on.
We'll use the rust overlay as an example.
Someone Else's Overlay: Flakes
Add the overlay to your inputs:
inputs = {
rust-overlay.url = "github:oxalica/rust-overlay";
};
Then use it in your outputs:
outputs = { self, nixpkgs, flake-utils, rust-overlay, flake-compat }:
flake-utils.lib.eachDefaultSystem
(system:
let pkgs = import nixpkgs {
inherit system;
# Include the overlay in one of your nixpkgs configs. It is
# recommended to use the same nixpkgs set from which you're
# installing rust-analyzer!
overlays = [ rust-overlay.overlay ];
};
in
with pkgs;
{
devShell = mkShell {
buildInputs = [
rust-bin.stable.latest.default # this package is provided by the overlay
rust-analyzer
];
};
});
Someone Else's Overlay: Niv
Essentially the same deal. Add the overlay to your sources:
$ niv add oxalica/rust-overlay --name rust-overlay
And then import and use from sources.nix
:
let
sources = import ./nix/sources.nix;
rust-overlay = import sources.rust-overlay;
pkgs = import sources.nixpkgs {
overlays = [ rust-overlay ]
};
in
with pkgs;
mkShell {
# Your desired packages go here
buildInputs = [
rust-bin.stable.latest.default # this package provided by the overlay
];
}
Option Three: Override Package Attributes
If you can't find an existing revision or overlay, the next easiest option is to override an existing package derivation's attributes to have it build a different version of the package in question, and then to install that new version in your build inputs.
This option requires some knowledge of the nix language and the ability to
introspect existing derivations. It is my strong recommendation that you check
out the nixpkgs
repository locally in order to examine existing derivations,
but you can also use the nix search website
to find a package and then use the "Source" link to get to the derivation.
Different kinds of packages have slightly different tooling and will require different attributes to be overridden, so we'll look at a few classes of them. Since most of the variation here comes from the type of package being installed, we'll quickly cover the differences between flakes and niv first, and then dive into specific examples.
For flakes:
outputs = { self, nixpkgs, flake-utils, rust-overlay, flake-compat }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs { inherit system; };
# We define a new derivation by overriding attributes of an existing package
my-pkg-my-version = pkgs.my-pkg.overrideAttrs (oldAttrs: {
newAttr = "new-value";
});
in
with pkgs;
{
devShell = mkShell {
buildInputs = [
# And install it
my-pkg-my-version
];
};
});
For niv:
let
sources = import ./nix/sources.nix;
pkgs = import sources.nixpkgs {};
my-pkg-my-version = pkgs.my-pkg.overrideAttrs (oldAttrs: {
newAttr = "new-value";
});
in
with pkgs;
mkShell {
# Your desired packages go here
buildInputs = [
my-pkg-my-version
];
}
Override Package Attributes: "Standard" C/C++ Package
Most C/C++ packages are installed by cloning the repository and running
./configure
, make
, and make install
. This is a very common pattern and is
well abstracted in nix via the
mkDerivation
function.
As an example, let's consider installing a particular version of
jq
. You can find its derivation
here.
This turns out to be a great example, because it's not quite as straightforward
as a version override usually is, so we get to explore a little build debugging.
Let's say we want to install version 1.5rc2.
When we look at the derivation, most of it is not version specific. The only part we need to change is here:
version = "1.6";
src = fetchFromGitHub {
owner = "stedolan";
repo = "jq";
rev = "${pname}-${version}";
hash = "sha256-CIE8vumQPGK+TFAncmpBijANpFALLTadOvkob0gVzro";
};
We can do this like so, putting this in the appropriate place for your flake or niv setup:
let
jq-1_5rc2 = pkgs.jq.overrideAttrs (oldAttrs: rec {
pname = "jq";
version = "1.5rc2";
src = pkgs.fetchFromGitHub {
owner = "stedolan";
repo = "jq";
rev = "${pname}-${version}";
hash = "";
};
});
We put an empty string for the hash so that we can get the correct one from the error output:
warning: found empty hash, assuming 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
error: hash mismatch in fixed-output derivation '/nix/store/vlbp5iijglh2vx6crcng1gjazsbr10z7-source.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-JsgJhwI3cpVbkJdGECEnA8CDDeqaCktoEgfSbf4vhvg=
We can then put that in for the hash, and everything should just work when we go to enter our dev shell...
error: builder for '/nix/store/nfzfdmksfajaxsh11q7kj3l9z2awwhp3-jq-1.5rc2.drv' failed with exit code 1;
last 10 log lines:
> unpacking source archive /nix/store/93d946ryl3khzfr9r9b7v0hb9j589qh6-source
> source root is source
> patching sources
> applying patch /nix/store/s0nsdqgd0x6ivb2kzgdzxz700irvvi69-fix-tests-when-building-without-regex-supports.patch
> patching file Makefile.am
> Hunk #1 FAILED at 130.
> 1 out of 1 hunk FAILED -- saving rejects to file Makefile.am.rej
> patching file configure.ac
> Hunk #1 FAILED at 278.
> 1 out of 1 hunk FAILED -- saving rejects to file configure.ac.rej
For full logs, run 'nix log /nix/store/nfzfdmksfajaxsh11q7kj3l9z2awwhp3-jq-1.5rc2.drv'.
Oh no! What's the deal?? It looks like we're failing to apply a patch specified in the original derivation. Let's take a look and see what it is:
patches = [
(fetchpatch {
name = "fix-tests-when-building-without-regex-supports.patch";
url = "https://github.com/stedolan/jq/pull/2292/commits/f6a69a6e52b68a92b816a28eb20719a3d0cb51ae.patch";
sha256 = "pTM5FZ6hFs5Rdx+W2dICSS2lcoLY1Q//Lan3Hu8Gr58=";
})
];
If we look at that PR, we can see that patch comes from 2021, while the version of jq we're trying to install comes from 2015. Let's remove it and see if it builds! We can update our override like:
let
jq-1_5rc2 = pkgs.jq.overrideAttrs (oldAttrs: rec {
pname = "jq";
version = "1.5rc2";
src = pkgs.fetchFromGitHub {
owner = "stedolan";
repo = "jq";
rev = "${pname}-${version}";
hash = "sha256-JsgJhwI3cpVbkJdGECEnA8CDDeqaCktoEgfSbf4vhvg=";
};
patches = []; # no patches!
});
Ah, another failure:
error: builder for '/nix/store/zrjl079rry6p62aarvjb1a7fjjzfkq35-jq-1.5rc2.drv' failed with exit code 1;
last 10 log lines:
> checking for remainder... yes
> checking for thread-local storage... yes
> checking whether byte ordering is bigendian... no
> checking that generated files are newer than configure... done
> configure: creating ./config.status
> config.status: creating Makefile
> config.status: executing depfiles commands
> config.status: executing libtool commands
> building
> rm: cannot remove './modules/oniguruma': No such file or directory
For full logs, run 'nix log /nix/store/zrjl079rry6p62aarvjb1a7fjjzfkq35-jq-1.5rc2.drv'.
Okay, it's easy to find where this is coming from in the derivation:
# paranoid mode: make sure we never use vendored version of oniguruma
# Note: it must be run after automake, or automake will complain
preBuild = ''
rm -r ./modules/oniguruma
'';
Probably this is another thing that's been introduced since version 1.5, so
let's override preBuild
in our override:
let
jq-1_5rc2 = pkgs.jq.overrideAttrs (oldAttrs: rec {
pname = "jq";
version = "1.5rc2";
src = pkgs.fetchFromGitHub {
owner = "stedolan";
repo = "jq";
rev = "${pname}-${version}";
hash = "sha256-JsgJhwI3cpVbkJdGECEnA8CDDeqaCktoEgfSbf4vhvg=";
};
patches = []; # no patches!
preBuild = ""; # no pre-build commands
});
Woo! It works! Okay, let's check and make sure we have the right version of jq in our dev shell:
$ jq --version
jq-1.6
...huh. That's weird. We know we had to have build the right one, because we
specified the SHA for the jq-1.5rc2
revision. So what gives? Let's look at the
derivation again. When we do, we see something suspicious:
# Upstream script that writes the version that's eventually compiled
# and printed in `jq --help` relies on a .git directory which our src
# doesn't keep.
preConfigure = ''
echo "#!/bin/sh" > scripts/version
echo "echo ${version}" >> scripts/version
patchShebangs scripts/version
'';
Oh ho! There's some weirdness going on with the version. This is a tricky thing
with overrideAttrs
, that other attributes that reference the overridden
attributes aren't necessarily reevaluated. Let's try patching that, setting it
to exactly the same thing, but in a context where it will use our version
:
let
jq-1_5rc2 = pkgs.jq.overrideAttrs (oldAttrs: rec {
pname = "jq";
version = "1.5rc2";
src = pkgs.fetchFromGitHub {
owner = "stedolan";
repo = "jq";
rev = "${pname}-${version}";
hash = "sha256-JsgJhwI3cpVbkJdGECEnA8CDDeqaCktoEgfSbf4vhvg=";
};
patches = []; # no patches!
preBuild = ""; # no pre-build commands
preConfigure = ''
echo "#!/bin/sh" > scripts/version
echo "echo ${version}" >> scripts/version
patchShebangs scripts/version
'';
});
Now, when we enter our dev shell:
$ jq --version
jq-1.5rc2
At last!
Technically, the last step is unnecessary (the actual installed jq
was the
right version even when it was saying 1.6), but it's probably worth it to avoid
confusion.
So now we're done! I'll note that this was a fairly complicated example of a
package override, but hopefully the walkthrough of the debugging process is
helpful. Usually, you only need the first step of overriding the pname
,
version
, and src
attributes!
Just to check, let's arbitrarily pick another typical package and give it a
shot real quick. We'll install an alternative version of gnused
. You can see
its derivation
here.
Already, we can see this one is likely to be simpler, since there are are no
patches and no fancy attributes like preBuild
. The current src
distribution
looks like this:
src = fetchurl {
url = "mirror://gnu/sed/sed-${version}.tar.xz";
sha256 = "0cznxw73fzv1n3nj2zsq6nf73rvsbxndp444xkpahdqvlzz0r6zp";
};
I went and dug around in the mirror and randomly picked version 4.2.2 (from 2012) to install. Here's our override:
let
gnused-4_2_2 = pkgs.gnused.overrideAttrs (oldAttrs: rec {
version = "4.2.2";
src = pkgs.fetchurl {
url = "mirror://gnu/sed/sed-${version}.tar.xz";
sha256 = "";
};
});
Note again we're setting sha256
to ""
so that the error will tell us the
correct SHA.
And, it should just work...
> error: cannot download sed-4.2.2.tar.xz from any mirror
Cue shocked Pikachu face.
Okay, this one is easy. Looking at the mirror,
we see that they only started publishing tar.xz
packages with sed 4.3. Prior to
that it was always tar.gz
. So we just need to update the URL:
gnused-4_2_2 = pkgs.gnused.overrideAttrs (oldAttrs: rec {
version = "4.2.2";
src = pkgs.fetchurl {
url = "mirror://gnu/sed/sed-${version}.tar.gz";
sha256 = "";
};
Et voila
$ sed --version
sed (GNU sed) 4.2.2
Override Package Attributes: Go Package
Lots of command line utilities are written in Go, and nix has good abstractions
around building Go packages. Let's look at how to install a specific version of
terragrunt
, a staple in the DevOps world. Its
derivation
looks pretty simple! The only thing we see that looks unfamiliar is a
vendorSha256
, which I don't know offhand what it is, but let's do our normal
override process and see what happens, trying to install terragrunt 0.36.2.
We'll make an override like so, using it in the appropriate place in your flake
or your shell.nix
file as described above:
terragrunt-0_36_2 = pkgs.terragrunt.overrideAttrs (oldAttrs: rec {
pname = "terragrunt";
version = "0.36.2";
src = pkgs.fetchFromGitHub {
owner = "gruntwork-io";
repo = pname;
rev = "v${version}";
sha256 = "";
};
});
Note as usual we set our sha256
to the empty string, so we will get an error
with the correct SHA.
Let's see what we see when we try to build our dev env:
error: hash mismatch in fixed-output derivation '/nix/store/1ca0y9ws97csxwg0x2z48mf7prrd1n9f-source.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-Iv9ZQoU/mMYdxBuPfoYc/zQXQ14FmDBfoFwxnESC6Ns=
Nice! We can replace the SHA in our override with that and then try again.
This time we succeed, but we have some weirdness:
$ terragrunt --version
terragrunt version v0.35.20
Weird! Even older than we were trying to get.
Hm, if we look around the derivation again, we see that the vesion
variable is
actually used in a couple places:
ldflags = [ "-s" "-w" "-X main.VERSION=v${version}" ];
doInstallCheck = true;
installCheckPhase = ''
runHook preInstallCheck
$out/bin/terragrunt --help
$out/bin/terragrunt --version | grep "v${version}"
runHook postInstallCheck
'';
Let's try sticking those into our override also:
terragrunt-0_36_2 = pkgs.terragrunt.overrideAttrs (oldAttrs: rec {
pname = "terragrunt";
version = "0.36.2";
src = pkgs.fetchFromGitHub {
owner = "gruntwork-io";
repo = pname;
rev = "v${version}";
sha256 = "sha256-Iv9ZQoU/mMYdxBuPfoYc/zQXQ14FmDBfoFwxnESC6Ns=";
};
ldflags = [ "-s" "-w" "-X main.VERSION=v${version}" ];
installCheckPhase = ''
runHook preInstallCheck
$out/bin/terragrunt --help
$out/bin/terragrunt --version | grep "v${version}"
runHook postInstallCheck
'';
});
Now, when we build, we get the version we expect!
$ terragrunt --version
terragrunt version v0.36.2
(relatively) easy peasy!
Override Package Attributes: Rust Package
Similarly, lots of of command line utilities are now written in Rust. Let's take
a look at fd
, which we were also looking at above. Here
is the derivation. Looks pretty straightforward, but unfortunately overriding
Rust package attributes is not as easy as it should be.
The buildRustPackage
function generates a derivation, and while you can
override those attributes that are passed to mkDerivation
like you normally
would, other attributes that are passed to rust-specific tooling cannot be
directly overridden. The most important one is generally cargoSha256
, which is
the SHA of the cargo lockfile for the package. See this
poist for more details.
The gist is that we want to start with an overlay that looks like this: Note
that we use ""
for both the fetchFromGitHub
call and for the nested override
for cargoDeps
:
fd-8_3_2 = pkgs.fd.overrideAttrs (oldAttrs: rec {
pname = "fd";
version = "8.3.2";
src = pkgs.fetchFromGitHub {
owner = "sharkdp";
repo = "fd";
rev = "v${version}";
sha256 = "";
};
cargoDeps = oldAttrs.cargoDeps.overrideAttrs (_: {
inherit src;
# This is where `cargoSha256` winds up being passed. We need to override
# it directly rather than overriding `cargo256` at the parent level.
outputHash = "";
});
});
Running it, we get our first SHA for the package source:
error: hash mismatch in fixed-output derivation '/nix/store/c8515fg97nh69vk9qxlj97k183rm9drl-source.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-aNAV0FVZEqtTdgvnLiS1ixtsPU48rUOZdmj07MiMVKg=
If we replace that and run again, we get another error, this time for the SHA
for the Cargo.lock
file.
error: hash mismatch in fixed-output derivation '/nix/store/inxfs19llvfxkz7bwr483y4wrm0r4dvi-fd-8.3.1-vendor.tar.gz.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-twGv6ABjBH2wkPuthAZRZeM8hXb10uggSkKNJR6L/b0=
Once we replace those and re-build, we should have our desired version of fd
!
$ fd --version
fd 8.3.2
Overlays: Using Your New Pin Everywhere
Once you have a derivation for your package version, you can install it directly
in your buildInputs
for your dev environment. This will make your package
available in that environment, so for example when we've added our custom
fd-8_3_2
to the build inputs, running fd
uses our custom version. This is
often enough, but sometimes you want to make sure that not only your interactive
dev environment but also any other package that depends on the package in
question uses your custom version. To accomplish this, we use an overlay.
An overlay is a function that takes the old package set and returns an attribute
set that will be be merged with the old package set to produce a new one. It
takes two arguments, often called self
and super
: the package set being
generated and the package set being replaced. So, let's build an overlay that
replaces the canonical version of each of our example packages that we built
above.
let my-overlay = self: super: {
fd = fd-8_3_2;
jq = jq-1_5rc2;
terragrunt = terragrunt-0_36_2;
}
We can then specify this overlay when instantiating a nixpkgs package set. For flakes:
outputs = { self, nixpkgs, flake-utils, flake-compat }:
# Calls the provided function for each "default system", which
# is the standard set.
flake-utils.lib.eachDefaultSystem
(system:
# instantiate the package set for the supported system, with our
# rust overlay
let
my-overlay = self: super: {...}; # definition from above
pkgs = import nixpkgs {
inherit system;
# Specify your overlay as one of the package sets overlays
overlays = [ my-overlay ];
};
in
# "unpack" the pkgs attrset into the parent namespace
with pkgs;
{
devShell = mkShell {
# Packages required for development.
buildInputs = [
# Any reference to the overridden attributes, whether here or in
# other packages, will use our versions
fd
jq
terragrunt
];
};
});
And for niv, in shell.nix
:
let
sources = import ./nix/sources.nix;
my-overlay = self: super: {...}; # definition from above
pkgs = import sources.nixpkgs {
overlays = [ my-overlay ]
};
in
with pkgs;
mkShell {
buildInputs = [ fd jq terragrunt ];
}
Summary
There are some things we haven't covered here: installing closed source binaries, more complicated overrides, and so on. I may update this post later with more content, so stay tuned!