4

From what I understand, I can use a given version of Nix packages using the -I flag. But I would like to know if it is possible within a single shell.nix conf to have something like:

  • git v2.1.2
  • htop v1.2.3
  • ...

to be able to ensure given tools versions.

2 Answers 2

4

Yes, there are several ways to do this, but none of them are as direct and simple as git v2.1.2; htop v1.2.3 and come with a lot of caveats.

aside
Specifying versions for programming language packages are possible too, but that topic seems to be even messier. The most promising standardization effort to date is dream2nix.

0. Methods

That is, available at the time of this writing:

0.1 "versioned" attribute paths (if available)

Single-version policy

We keep multiple versions in nixpkgs only when there's a good reason to. Nix is able to handle any number of versions/configurations, but on the other hand it's much more convenient when all (or most) use just a single one. It leads to better sharing of the effort in many respects: simplified maintenance, testing, sharing of the binaries, etc. It's what most distros do. (Only gentoo diverges from the big ones I know, and they pay a price for it.)

When we do create more variants, we just name them (attribute paths), e.g. gcc48 and gcc49 or ffmpeg and ffmpeg-full.

-- vcunat commented on Sep 6, 2015 on Nixpkgs issue #9682

Citing jtojnar's discourse answer for a more specific example:

$ nix-env -qP --available nodejs
nixos.nodejs       nodejs-10.18.1
nixos.nodejs-10_x  nodejs-10.18.1
nixos.nodejs-12_x  nodejs-12.14.1
nixos.nodejs-13_x  nodejs-13.6.0
$ nix-env -iA nixos.nodejs-13_x

0.2 Pinning

Either explicitly or implicitly with

  • Nix derivation expressions (see Example 2.2)
  • Nix command line tools (see Example 2.1)
  • overlays
  • overrides (see Example 2.3)
  • flakes (pinning is the default)
  • third party, "unofficial", and/or proprietary methods (e.g., niv, niv + Home Manager, flox)

1. Terms

1.1 Pinning

The best description I could find is in this comment by CMCDragonkai; to paraphrase it:

Pinning in a Nix expression means to use a Nix package set (usually Nixpkgs or a fork of it) at a specific point (i.e., in a certain state) of its history.

Using a Git (content-addressed) commit hash in the Nixpkgs repo is similar to being in "detached HEAD state": from a Nix expression's perspective, every package definition is the latest version at the time the commit was issued.

For example, when referring to the Nixpkgs package set pinned to 39cd40f (Sep 29, 2017), the "latest" versions will be:

WARNING
The Nixpkgs repo tracks both the official Nix package set and the convenience tools used in Nix expressions! See Example 2.1 below on how this can break assumptions.

As far as I can tell, the term "pinning" is still not defined in any of the official documentation. (The Nixpkgs manual mentions it once with an example but without explanation.)

1.2 Nix expression

A piece of code written using the Nix expression language.

1.3 Nix derivation expression

A Nix expression that will evaluate to a Nix derivation (usually by eventually calling the derivation (source) primop, most commonly via mkShell (source) or mkDerivation (source)).

note: Nix derivation vs Nix derivation expression

It is easier for me to reason about Nix when making a clear distinction between a Nix derivation and code that evaluates to a Nix derivation (i.e., Nix derivation expression). Did an attempt to make it clearer in this answer, but it needs to updated so take it with a grain of salt.

1.4 Primop

Primitive operations, or primops, are functions that are built into the language. They need not be defined or imported; they are in scope by default. But since they are normal identifiers, they can be overridden by local definitions.

-- Eelco Dolstra, The Purely Functional Software Deployment Model (PhD thesis) (January 18, 2006)

These are implemented in the NixOS/nix repo.

1.5 Nix derivation

(placeholder)

1.6 Package set

See What are package sets? on the NixOS Discourse, but to paraphrase ryantm's answer:

Package set is a collection of Nix expressions that evaluate to an attribute set (also called "attrset") where the names are the names of the packages (or names of recursive package sets) and the values are Nix derivations.

Examples:

2. Examples

2.1 Pinning Nixpkgs on nix-shell invocation

Compressing a usual Nix-shell expression,

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {

  buildInputs = [
    pkgs.git
    pkgs.htop
  ];

  shellHook = ''
    echo "git:  $(git  --version)"
    echo "htop: $(htop --version)"
  '';
}

into a one-liner, then

nix-shell \
-I nixpkgs=https://github.com/NixOS/nixpkgs/archive/39cd40f7bea40116ecb756d46a687bfd0d2e550e.tar.gz \
-E '{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell { buildInputs = [ pkgs.git pkgs.htop ]; shellHook = "echo \"git: $(git --version)\"; echo \"htop: $(htop --version)\";"; }'

won't work because mkShell has not been implemented yet, thus:

nix-shell \
-I nixpkgs=https://github.com/NixOS/nixpkgs/archive/39cd40f7bea40116ecb756d46a687bfd0d2e550e.tar.gz \
-E '{ pkgs ? import <nixpkgs> {} }: pkgs.stdenv.mkDerivation { name = "shell"; buildInputs = [ pkgs.git pkgs.htop ]; shellHook = "echo \"git: $(git --version)\"; echo \"htop: $(htop --version)\";"; }'

Notes:

  • The only difference between the 2 snippet is that the part pkgs.mkShell { buildInputs has been replaced with pkgs.stdenv.mkDerivation { name = "shell"; buildInputs.

  • Ran both commands on Ubuntu 22.04 (aarch64); also, the 2nd won't run on aarch64-darwin.

2.2 Pin Nixpkgs in a Nix expression

Using jeff-hykin's comment as template:

Step 0. Find the Version you Want

Find (almost) all versions of a package using lazamar's absolutely amazing online tool.

Step 1: Install that version using its Hash

Click on the hash in the row for the version you need (e.g., git 2.23.0 and htop 2.2.0), and either use the nix-env/nix-shell commands listed there one-by-one, or copy the Nix expression snippets into a file (and rename to variables to avoid clashes):


# git_htop.nixshell

let

  pkgs_for_git = import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/bca9437d1eae9519b61a58f2593f25f65494f8e9.tar.gz";
  }) {};

  pkgs_for_htop = import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/b5e903cedb331f9ee268ceebffb58069f1dae9fb.tar.gz";
  }) {};

  # Copied lines above from lazamar's tool,
  # but the shortened form works too:
  #
  #   pkgs = import (builtins.fetchTarball "<url>") {};

in
  
  # Chose `pkgs_for_htop` arbitrarily; what matters
  # is that the  derivation function  exists in the
  # pinned Nixpkgs.

  pkgs_for_htop.mkShell {

    buildInputs = [
      pkgs_for_htop.htop
      pkgs_for_git.git
    ];

    shellHook = ''
      echo "git:  $(git  --version)"
      echo "htop: $(htop --version)"
    '';
  }

and then call it:

nix-shell -v git_htop.nixshell

2.3 Specify packages with overlays (stub)

Simply linking a file from jwiegley's nix-config repo; I don't understand it yet, but wanted to include it anyway.

3. Further reading

Online discussions to follow this still evolving topic:


(Hoping to expand this with more examples.)

1
1

You can import nixpkgs archives with hashes that contain specific package versions. You can find hashes for example here

{ pkgs ? import <nixpkgs> { } }:

let
  gitPkgsHash = "0c159930e7534aa803d5cf03b27d5c86ad6050b7"; #git 2.16.2
  htopPkgsHash = "5ed9176c52e4ceed2755a19b3e8357a0772de8ff"; #htop 2.0.0
  gitPkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/${gitPkgsHash}.tar.gz") { };
  htopPkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/${htopPkgsHash}.tar.gz") { };
in

pkgs.mkShell {
  buildInputs = [
    gitPkgs.gitAndTools.gitFull
    htopPkgs.htop
  ];
}

PS. In this config i used newer versions, because your version are very old and caused some errors

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .