nobe4 / Posts / Nix bin alias _

  |   Nix Tech

In one of my previous post, I explored how to use ln to link files across a NixOS system. The two initial needs were to link configuration files, and create a binary alias.

I tend to install gojq and use it as jq, it makes more sense to me to have a consistent experience between the CLI and library. I have not yet suffered from this choice, despite my intense jq usage.

In this post, we’ll use it to explore how to create a direct link to the gojq binary. The logic can be applied to any other binary of your choice.

ln-root 🔗

In my previous post, I showed how using activationScripts allowed to create root-owned symlink. I had used it to create a global link:

ln-root = [
    [ "${pkgs.gojq}/bin/gojq" "/usr/bin/jq" ]
];

And this worked well:

$ readlink /usr/bin/jq
/nix/store/<hash>-gojq-0.12.17/bin/gojq

/usr/bin/ is however not in the $PATH, so it wouldn’t be useable right away.

Updating $PATH 🔗

This would be the obvious solution, which could be achieved in a couple of different ways:

This technically works. However I know that in a couple of months, I’ll look back and think “Why did I wanted to add this path?” I would rather not create any ambiguity.

mkDerivation 🔗

A better solution is to create a whole new derivation in nix, which creates the link for us.

mkDerivation is “the fundamental building block” of nixpkgs and empowers users and contributors to create new derivations.

In our case, we want:

  1. get the path to the installed gojq
  2. create a link to it, called jq.

Path to gojq 🔗

The path to any Nix package is available directly from the package itself:

stdenv.mkDerivation {
  buildCommand = ''echo ${pkgs.gojq}'';
}

Result:

/nix/store/<hash>-gojq-0.12.17

Note that this doesn’t point to the gojq binary, but to the package’s path. To get to the binary, we need:

stdenv.mkDerivation {
  buildCommand = ''echo ${pkgs.gojq}/bin/gojq'';
}

mkDerivation 🔗

mkDerivation expects installed content in specific locations:

$out is a special variable that stores the result of a derivation. It’s given by the stdenv builder and can be used directly.

The $out are symlinked and added into the user’s $PATH.

Additional information:

Both are used in the final path to the package:

/nix/store/<hash>-<name>-<version>

Linking gojq 🔗

The final version of our custom link is:

stdenv.mkDerivation {
  name = "jq";
  version = gojq.version;
  buildCommand = ''
    mkdir -p $out/bin
    ln -s ${gojq}/bin/gojq $out/bin/jq
  '';
}

And can even be added directly in users.users.<user>.package:

{ pkgs, ... }: {
  users.users.nobe4.packages = with pkgs; [
    gojq

    (stdenv.mkDerivation {
      name = "jq";
      version = gojq.version;
      buildCommand = ''
        mkdir -p $out/bin
        ln -s ${gojq}/bin/gojq $out/bin/jq
      '';
    })
  ];
}

This, IMHO, is the best way to explicitly declare that jq is a link to gojq. The Nix configuration shows what it is, and if any additional comments are needed, they can be added here and not lack context.

Let’s now look at the full chain of links:

$ which jq
/etc/profiles/per-user/nobe4/bin/jq
$ readlink /etc/profiles/per-user/nobe4/bin/jq
/nix/store/<hash>-jq/bin/jq
$ readlink /nix/store/<hash>-jq/bin/jq
/nix/store/<hash>-gojq-0.12.17/bin/gojq

The /etc/profiles/per-user/<user>/ folder is a managed user profile, where all the binaries, libraries, headers, etc, are linked for a user. It is similar in spirit to ~/.nix-profile.

References: