nixpkgs/pkgs/development/misc/resholve
2021-01-11 18:58:56 -06:00
..
default.nix
deps.nix
README.md
resholve-package.nix
resholve.nix resholve: don't propagate, 0.4.0 -> 0.4.1 2021-01-11 18:58:56 -06:00

Using resholve's Nix API

resholve converts bare executable references in shell scripts to absolute paths. This will hopefully make its way into the Nixpkgs manual soon, but until then I'll outline how to use the resholvePackage function.

Fair warning: resholve does not aspire to resolving all valid Shell scripts. It depends on the OSH/Oil parser, which aims to support most (but not all) Bash, and aims to be a ~90% sort of solution.

Let's start with a simple example from one of my own projects:

{ stdenv, lib, resholvePackage, fetchFromGitHub, bashup-events44, bashInteractive_5, doCheck ? true, shellcheck }:

resholvePackage rec {
  pname = "shellswain";
  version = "unreleased";

  src = fetchFromGitHub {
    # ...
  };

  solutions = {
    profile = {
      # the only *required* arguments
      scripts = [ "bin/shellswain.bash" ];
      interpreter = "none";
      inputs = [ bashup-events44 ];
    };
  };

  makeFlags = [ "prefix=${placeholder "out"}" ];

  inherit doCheck;
  checkInputs = [ shellcheck ];

  # ...
}

I'll focus on the solutions attribute, since this is the only part that differs from other derivations.

Each "solution" (k=v pair) describes one resholve invocation. For most shell packages, one invocation will probably be enough. resholve will make you be very explicit about your script's dependencies, and it may also need your help sorting out some references or problems that it can't safely handle on its own.

If you have more than one script, and your scripts need conflicting directives, you can specify more than one solution to resolve the scripts separately, but still produce a single package.

Let's take a closer look:

  solutions = {
    # each solution has a short name; this is what you'd use to
    # override the settings of this solution, and it may also show up
    # in (some) error messages.
    profile = {
      # specify one or more $out-relative script paths (unlike many
      # builders, resholve will modify the output files during fixup
      # to correctly resolve scripts that source within the package)
      scripts = [ "bin/shellswain.bash" ];
      # "none" for no shebang, "${bash}/bin/bash" for bash, etc.
      interpreter = "none";
      # packages resholve should resolve executables from
      inputs = [ bashup-events44 ];
    };
  };

resholve has a (growing) number of options for handling more complex scripts. I won't cover these in excruciating detail here. You can find more information about these in man resholve via nixpkgs.resholve.

Instead, we'll look at the general form of the solutions attrset:

solutions = {
  shortname = {
    # required
    # $out-relative paths to try resolving
    scripts = [ "bin/shunit2" ];
    # packages to resolve executables from
    inputs = [ coreutils gnused gnugrep findutils ];
    # path for shebang, or 'none' to omit shebang
    interpreter = "${bash}/bin/bash";

    # optional
    fake = { fake directives };
    fix = { fix directives };
    keep = { keep directives };
    # file to inject before first code-line of script
    prologue = file;
    # file to inject after last code-line of script
    epilogue = file;
    # extra command-line flags passed to resholve; generally this API
    # should align with what resholve supports, but flags may help if
    # you need to override the version of resholve.
    flags = [ ];
  };
};

The main way you'll adjust how resholve handles your scripts are the fake, fix, and keep directives. The manpage covers their purpose and how to format them on the command-line, so I'll focus on how you'll need to translate them into Nix types.

# --fake 'f:setUp;tearDown builtin:setopt source:/etc/bashrc'
fake = {
    function = [ "setUp" "tearDown" ];
    builtin = [ "setopt" ];
    source = [ "/etc/bashrc" ];
};

# --fix 'aliases xargs:ls $GIT:gix'
fix = {
    # all single-word directives use `true` as value
    aliases = true;
    xargs = [ "ls" ];
    "$GIT" = [ "gix" ];
};

# --keep 'which:git;ls .:$HOME $LS:exa /etc/bashrc ~/.bashrc'
keep = {
    which = [ "git" "ls" ];
    "." = [ "$HOME" ];
    "$LS" = [ "exa" ];
    "/etc/bashrc" = true;
    "~/.bashrc" = true;
};