resholve: init at 0.4.0 (#85827)
resholve: init at 0.4.0 resholve attempts to resolve executables in shell scripts. Includes Nix builder for resolving dependencies in Nix-built shell projects.
This commit is contained in:
parent
645f39f33e
commit
6fd9283bba
138
pkgs/development/misc/resholve/README.md
Normal file
138
pkgs/development/misc/resholve/README.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ 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:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
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:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
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.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# --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;
|
||||||
|
};
|
||||||
|
```
|
9
pkgs/development/misc/resholve/default.nix
Normal file
9
pkgs/development/misc/resholve/default.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{ callPackage
|
||||||
|
, doCheck ? true
|
||||||
|
}:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
resholve = callPackage ./resholve.nix { inherit doCheck; };
|
||||||
|
resholvePackage =
|
||||||
|
callPackage ./resholve-package.nix { inherit resholve; };
|
||||||
|
}
|
120
pkgs/development/misc/resholve/deps.nix
generated
Normal file
120
pkgs/development/misc/resholve/deps.nix
generated
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
{ stdenv
|
||||||
|
, python27Packages
|
||||||
|
, fetchFromGitHub
|
||||||
|
, makeWrapper
|
||||||
|
, # re2c deps
|
||||||
|
autoreconfHook
|
||||||
|
, # py-yajl deps
|
||||||
|
git
|
||||||
|
, # oil deps
|
||||||
|
readline
|
||||||
|
, cmark
|
||||||
|
, file
|
||||||
|
, glibcLocales
|
||||||
|
, oilPatches ? [ ]
|
||||||
|
}:
|
||||||
|
|
||||||
|
/*
|
||||||
|
Notes on specific dependencies:
|
||||||
|
- if/when python2.7 is removed from nixpkgs, this may need to figure
|
||||||
|
out how to build oil's vendored python2
|
||||||
|
- I'm not sure if glibcLocales is worth the addition here. It's to fix
|
||||||
|
a libc test oil runs. My oil fork just disabled the libc tests, but
|
||||||
|
I haven't quite decided if that's the right long-term call, so I
|
||||||
|
didn't add a patch for it here yet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
rec {
|
||||||
|
# had to add this as well; 1.3 causes a break here; sticking
|
||||||
|
# to oil's official 1.0.3 dep for now.
|
||||||
|
re2c = stdenv.mkDerivation rec {
|
||||||
|
pname = "re2c";
|
||||||
|
version = "1.0.3";
|
||||||
|
sourceRoot = "${src.name}/re2c";
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "skvadrik";
|
||||||
|
repo = "re2c";
|
||||||
|
rev = version;
|
||||||
|
sha256 = "0grx7nl9fwcn880v5ssjljhcb9c5p2a6xpwil7zxpmv0rwnr3yqi";
|
||||||
|
};
|
||||||
|
nativeBuildInputs = [ autoreconfHook ];
|
||||||
|
preCheck = ''
|
||||||
|
patchShebangs run_tests.sh
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
py-yajl = python27Packages.buildPythonPackage rec {
|
||||||
|
pname = "oil-pyyajl-unstable";
|
||||||
|
version = "2019-12-05";
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "oilshell";
|
||||||
|
repo = "py-yajl";
|
||||||
|
rev = "eb561e9aea6e88095d66abcc3990f2ee1f5339df";
|
||||||
|
sha256 = "17hcgb7r7cy8r1pwbdh8di0nvykdswlqj73c85k6z8m0filj3hbh";
|
||||||
|
fetchSubmodules = true;
|
||||||
|
};
|
||||||
|
# just for submodule IIRC
|
||||||
|
nativeBuildInputs = [ git ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# resholve's primary dependency is this developer build of the oil shell.
|
||||||
|
oildev = python27Packages.buildPythonPackage rec {
|
||||||
|
pname = "oildev-unstable";
|
||||||
|
version = "2020-03-31";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "oilshell";
|
||||||
|
repo = "oil";
|
||||||
|
rev = "ea80cdad7ae1152a25bd2a30b87fe3c2ad32394a";
|
||||||
|
sha256 = "0pxn0f8qbdman4gppx93zwml7s5byqfw560n079v68qjgzh2brq2";
|
||||||
|
|
||||||
|
/*
|
||||||
|
It's not critical to drop most of these; the primary target is
|
||||||
|
the vendored fork of Python-2.7.13, which is ~ 55M and over 3200
|
||||||
|
files, dozens of which get interpreter script patches in fixup.
|
||||||
|
*/
|
||||||
|
extraPostFetch = ''
|
||||||
|
rm -rf Python-2.7.13 benchmarks metrics py-yajl rfc gold web testdata services demo devtools cpp
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO: not sure why I'm having to set this for nix-build...
|
||||||
|
# can anyone tell if I'm doing something wrong?
|
||||||
|
SOURCE_DATE_EPOCH = 315532800;
|
||||||
|
|
||||||
|
# These aren't, strictly speaking, nix/nixpkgs specific, but I've
|
||||||
|
# had hell upstreaming them. Pulling from resholve source and
|
||||||
|
# passing in from resholve.nix
|
||||||
|
patches = oilPatches;
|
||||||
|
|
||||||
|
buildInputs = [ readline cmark py-yajl ];
|
||||||
|
|
||||||
|
nativeBuildInputs = [ re2c file makeWrapper ];
|
||||||
|
|
||||||
|
propagatedBuildInputs = with python27Packages; [ six typing ];
|
||||||
|
|
||||||
|
doCheck = true;
|
||||||
|
|
||||||
|
preBuild = ''
|
||||||
|
build/dev.sh all
|
||||||
|
'';
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
patchShebangs asdl build core doctools frontend native oil_lang
|
||||||
|
'';
|
||||||
|
|
||||||
|
_NIX_SHELL_LIBCMARK = "${cmark}/lib/libcmark${stdenv.hostPlatform.extensions.sharedLibrary}";
|
||||||
|
|
||||||
|
# See earlier note on glibcLocales
|
||||||
|
LOCALE_ARCHIVE = stdenv.lib.optionalString (stdenv.buildPlatform.libc == "glibc") "${glibcLocales}/lib/locale/locale-archive";
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "A new unix shell";
|
||||||
|
homepage = "https://www.oilshell.org/";
|
||||||
|
license = with stdenv.lib.licenses; [
|
||||||
|
psfl # Includes a portion of the python interpreter and standard library
|
||||||
|
asl20 # Licence for Oil itself
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
97
pkgs/development/misc/resholve/resholve-package.nix
Normal file
97
pkgs/development/misc/resholve/resholve-package.nix
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{ stdenv, lib, resholve }:
|
||||||
|
|
||||||
|
{ pname
|
||||||
|
, src
|
||||||
|
, version
|
||||||
|
, passthru ? { }
|
||||||
|
, solutions
|
||||||
|
, ...
|
||||||
|
}@attrs:
|
||||||
|
let
|
||||||
|
inherit stdenv;
|
||||||
|
/* These functions break up the work of partially validating the
|
||||||
|
* 'solutions' attrset and massaging it into env/cli args.
|
||||||
|
*
|
||||||
|
* Note: some of the left-most args do not *have* to be passed as
|
||||||
|
* deep as they are, but I've done so to provide more error context
|
||||||
|
*/
|
||||||
|
|
||||||
|
# for brevity / line length
|
||||||
|
spaces = l: builtins.concatStringsSep " " l;
|
||||||
|
semicolons = l: builtins.concatStringsSep ";" l;
|
||||||
|
|
||||||
|
/* Throw a fit with dotted attr path context */
|
||||||
|
nope = path: msg:
|
||||||
|
throw "${builtins.concatStringsSep "." path}: ${msg}";
|
||||||
|
|
||||||
|
/* Special-case directive value representations by type */
|
||||||
|
makeDirective = solution: env: name: val:
|
||||||
|
if builtins.isInt val then builtins.toString val
|
||||||
|
else if builtins.isString val then name
|
||||||
|
else if true == val then name
|
||||||
|
else if false == val then "" # omit!
|
||||||
|
else if null == val then "" # omit!
|
||||||
|
else if builtins.isList val then "${name}:${semicolons val}"
|
||||||
|
else nope [ solution env name ] "unexpected type: ${builtins.typeOf val}";
|
||||||
|
|
||||||
|
/* Build fake/fix/keep directives from Nix types */
|
||||||
|
makeDirectives = solution: env: val:
|
||||||
|
lib.mapAttrsToList (makeDirective solution env) val;
|
||||||
|
|
||||||
|
/* Special-case value representation by type/name */
|
||||||
|
makeEnvVal = solution: env: val:
|
||||||
|
if env == "inputs" then lib.makeBinPath val
|
||||||
|
else if builtins.isString val then val
|
||||||
|
else if builtins.isList val then spaces val
|
||||||
|
else if builtins.isAttrs val then spaces (makeDirectives solution env val)
|
||||||
|
else nope [ solution env ] "unexpected type: ${builtins.typeOf val}";
|
||||||
|
|
||||||
|
/* Shell-format each env value */
|
||||||
|
shellEnv = solution: env: value:
|
||||||
|
lib.escapeShellArg (makeEnvVal solution env value);
|
||||||
|
|
||||||
|
/* Build a single ENV=val pair */
|
||||||
|
makeEnv = solution: env: value:
|
||||||
|
"RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}";
|
||||||
|
|
||||||
|
/* Discard attrs claimed by makeArgs */
|
||||||
|
removeCliArgs = value:
|
||||||
|
removeAttrs value [ "scripts" "flags" ];
|
||||||
|
|
||||||
|
/* Verify required arguments are present */
|
||||||
|
validateSolution = { scripts, inputs, interpreter, ... }: true;
|
||||||
|
|
||||||
|
/* Pull out specific solution keys to build ENV=val pairs */
|
||||||
|
makeEnvs = solution: value:
|
||||||
|
spaces (lib.mapAttrsToList (makeEnv solution) (removeCliArgs value));
|
||||||
|
|
||||||
|
/* Pull out specific solution keys to build CLI argstring */
|
||||||
|
makeArgs = { flags ? [ ], scripts, ... }:
|
||||||
|
spaces (flags ++ scripts);
|
||||||
|
|
||||||
|
/* Build a single resholve invocation */
|
||||||
|
makeInvocation = solution: value:
|
||||||
|
if validateSolution value then
|
||||||
|
"${makeEnvs solution value} resholve --overwrite ${makeArgs value}"
|
||||||
|
else throw "invalid solution"; # shouldn't trigger for now
|
||||||
|
|
||||||
|
/* Build resholve invocation for each solution. */
|
||||||
|
makeCommands = solutions:
|
||||||
|
lib.mapAttrsToList makeInvocation solutions;
|
||||||
|
|
||||||
|
self = (stdenv.mkDerivation ((removeAttrs attrs [ "solutions" ])
|
||||||
|
// {
|
||||||
|
inherit pname version src;
|
||||||
|
buildInputs = [ resholve ];
|
||||||
|
|
||||||
|
# enable below for verbose debug info if needed
|
||||||
|
# supports default python.logging levels
|
||||||
|
# LOGLEVEL="INFO";
|
||||||
|
preFixup = ''
|
||||||
|
pushd "$out"
|
||||||
|
${builtins.concatStringsSep "\n" (makeCommands solutions)}
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
}));
|
||||||
|
in
|
||||||
|
lib.extendDerivation true passthru self
|
74
pkgs/development/misc/resholve/resholve.nix
Normal file
74
pkgs/development/misc/resholve/resholve.nix
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{ stdenv
|
||||||
|
, callPackage
|
||||||
|
, python27Packages
|
||||||
|
, installShellFiles
|
||||||
|
, fetchFromGitHub
|
||||||
|
, file
|
||||||
|
, findutils
|
||||||
|
, gettext
|
||||||
|
, bats
|
||||||
|
, bash
|
||||||
|
, doCheck ? true
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
version = "0.4.0";
|
||||||
|
rSrc = fetchFromGitHub {
|
||||||
|
owner = "abathur";
|
||||||
|
repo = "resholve";
|
||||||
|
rev = "v${version}";
|
||||||
|
hash = "sha256-wfxcX3wMZqoi5bWjXYRa21UDDJmTDfE+21p4mL2IJog=";
|
||||||
|
};
|
||||||
|
deps = callPackage ./deps.nix {
|
||||||
|
/*
|
||||||
|
resholve needs to patch Oil, but trying to avoid adding
|
||||||
|
them all *to* nixpkgs, since they aren't specific to
|
||||||
|
nix/nixpkgs.
|
||||||
|
*/
|
||||||
|
oilPatches = [
|
||||||
|
"${rSrc}/0001-add_setup_py.patch"
|
||||||
|
"${rSrc}/0002-add_MANIFEST_in.patch"
|
||||||
|
"${rSrc}/0003-fix_codegen_shebang.patch"
|
||||||
|
"${rSrc}/0004-disable-internal-py-yajl-for-nix-built.patch"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
python27Packages.buildPythonApplication {
|
||||||
|
pname = "resholve";
|
||||||
|
inherit version;
|
||||||
|
src = rSrc;
|
||||||
|
format = "other";
|
||||||
|
|
||||||
|
nativeBuildInputs = [ installShellFiles ];
|
||||||
|
|
||||||
|
propagatedBuildInputs = [ deps.oildev python27Packages.ConfigArgParse ];
|
||||||
|
|
||||||
|
patchPhase = ''
|
||||||
|
for file in resholve; do
|
||||||
|
substituteInPlace $file --subst-var-by version ${version}
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
install -Dm755 resholve $out/bin/resholve
|
||||||
|
installManPage resholve.1
|
||||||
|
'';
|
||||||
|
|
||||||
|
inherit doCheck;
|
||||||
|
checkInputs = [ bats ];
|
||||||
|
RESHOLVE_PATH = "${stdenv.lib.makeBinPath [ file findutils gettext ]}";
|
||||||
|
|
||||||
|
checkPhase = ''
|
||||||
|
# explicit interpreter for test suite
|
||||||
|
export INTERP="${bash}/bin/bash" PATH="$out/bin:$PATH"
|
||||||
|
patchShebangs .
|
||||||
|
./test.sh
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with stdenv.lib; {
|
||||||
|
description = "Resolve external shell-script dependencies";
|
||||||
|
homepage = "https://github.com/abathur/resholve";
|
||||||
|
license = with licenses; [ mit ];
|
||||||
|
maintainers = with maintainers; [ abathur ];
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
@ -7099,6 +7099,9 @@ in
|
|||||||
|
|
||||||
rescuetime = libsForQt5.callPackage ../applications/misc/rescuetime { };
|
rescuetime = libsForQt5.callPackage ../applications/misc/rescuetime { };
|
||||||
|
|
||||||
|
inherit (callPackage ../development/misc/resholve { })
|
||||||
|
resholve resholvePackage;
|
||||||
|
|
||||||
reuse = callPackage ../tools/package-management/reuse { };
|
reuse = callPackage ../tools/package-management/reuse { };
|
||||||
|
|
||||||
rewritefs = callPackage ../os-specific/linux/rewritefs { };
|
rewritefs = callPackage ../os-specific/linux/rewritefs { };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user