Dependency for multiple shell scripts in mkDerivation

Hi everybody,

I’m trying to package a few shell scripts that I wrote before I started using Nix and I want them in a single package (so not using writeScript function). The shell scripts execute a few Linux commands like mountpoint which on Debian is stored in /usr/bin/mountpoint. After build when I run it, the script fails with mountpoint: command not found. I realized I need to add pkgs.util-linux as dependency and I did that like this:

pkgs.stdenv.mkDerivation {
  # ...

  nativeBuildInputs = [ pkgs.e2fsprogs pkgs.util-linux ];

  installPhase = ''
    mkdir $out
    mkdir $out/bin

    cp bash-script-a  $out/bin/bash-script-a
    cp bash-script-b  $out/bin/bash-script-b

    chmod +x $out/bin/*
  '';
}

But I still get line ###: mountpoint: command not found when I run the script. Could you please tell me how to fix that?

Thank you.

Hans

You can use patsh or resholve to patch your shell scripts. You can find some examples in nixpkgs

1 Like

Thank you @figsoda

I also considered:

  1. Using /run/current-system/sw/bin/mountpoint instead of mountpoint inside of the script (patching the script by myself)
  2. Adding /run/current-system/sw/bin into PATH at the beginning of the script
  3. Adding parameters to the script (something like mountpoint_path) where I can then pass ${pkgs.util-linux}/bin/mountpoint. But that won’t be easy to do

Is any of that a good idea?

Also should I add the list of packages into nativeBuildInputs = [...] or should I use something else?

Thank you.

These are all of course possible, but I wouldn’t recommend any of them.

There are a lot of ways to do what you want, but I think the ~best ones all boil down to two fundamental approaches (ignoring the possibility of pulling the scripts into Nix and directly interpolating the dependencies–it doesn’t square as readily with your question):

  1. Inject a PATH (either directly in the script, or by wrapping the script). You suggest doing this manually in #2, but you can do it from the Nix side without having to bake Nix assumptions (and a dependency on util-linux being in your systemPackages) into the script. writeShellApplication is an example of doing this directly (but it’s focused on single-script use cases) and makeWrapper is an example of wrapping.

  2. Rewrite the actual invocations in the scripts. This is what patsh and resholve do, but there are also approaches that use sed/awk, the stdenv’s substitute* shell functions, and so on. You’re proposing a manual version of this in #1, but again it’ll bake Nix assumptions (and a dependency on util-linux being in your systemPackages) into the script.

If your scripts are small/simple and you’re confident you already know all of the packages they depend on, I’d just use makeWrapper to set or prefix the PATH. There are gobs of real-world examples, but here’s one where a package is iterating over a set of scripts to wrap them all with the same dependencies:

This specific example prefixes the PATH, but the next line shows that you can also just --set it to an explicit value if you want to keep it from using random system executables.

It is more focused on single scripts, but if you’re curious about the pros/cons I blogged last year about the main approaches to supplying shell script dependencies with Nix: no-look, no-leap Shell script dependencies

1 Like

Thank you, great explanation.

I’ve just tested patsh like this:

let
  fixpath = patsh.packages.x86_64-linux.default;
in
pkgs.stdenv.mkDerivation {
  ...

  nativeBuildInputs = [ pkgs.e2fsprogs pkgs.util-linux ];

  installPhase = ''
    mkdir $out
    mkdir $out/bin

    ${fixpath}/bin/patsh script  $out/bin/script
    ...
  '';
}

and it works and it was much simpler than I thought.

The only thing I’m still not sure, should I add those packages into nativeBuildInputs or should I use a different build phase ?

I think they more correctly go in buildInputs (but I’ve always found these distinctions a bit slippery–someone might correct me).

IIRC, patsh will try to pick up whatever is in PATH, which I think will include at least stdenv tools, buildInputs, and nativeBuildInputs. (resholve uses a separate set of explicitly-supplied dependencies.)

1 Like

IIUC buildInputs would indeed be more correct

patsh by default looks in $PATH, but also has a --path flag to override this behavior

on a side note, patsh is in nixpkgs so you don’t have to use the flake

1 Like

Thank you. If it is in nixpkgs - great. And will be cached too :+1: