Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at 23.11 310 lines 12 kB view raw view rendered
1# Using resholve's Nix API 2resholve replaces bare references (subject to a PATH search at runtime) to external commands and scripts with absolute paths. 3 4This small super-power helps ensure script dependencies are declared, present, and don't unexpectedly shift when the PATH changes. 5 6resholve is developed to enable the Nix package manager to package and integrate Shell projects, but its features are not Nix-specific and inevitably have other applications. 7 8<!-- generated from resholve's repo; best to suggest edits there (or at least notify me) --> 9 10This will hopefully make its way into the Nixpkgs manual soon, but 11until then I'll outline how to use the functions: 12- `resholve.mkDerivation` (formerly `resholvePackage`) 13- `resholve.writeScript` (formerly `resholveScript`) 14- `resholve.writeScriptBin` (formerly `resholveScriptBin`) 15- `resholve.phraseSolution` (new in resholve 0.8.0) 16 17> Fair warning: resholve does *not* aspire to resolving all valid Shell 18> scripts. It depends on the OSH/Oil parser, which aims to support most (but 19> not all) Bash. resholve aims to be a ~90% sort of solution. 20 21## API Concepts 22 23The main difference between `resholve.mkDerivation` and other builder functions 24is the `solutions` attrset, which describes which scripts to resolve and how. 25Each "solution" (k=v pair) in this attrset describes one resholve invocation. 26 27> NOTE: For most shell packages, one invocation will probably be enough: 28> - Packages with a single script will only need one solution. 29> - Packages with multiple scripts can still use one solution if the scripts 30> don't require conflicting directives. 31> - Packages with scripts that require conflicting directives can use multiple 32> solutions to resolve the scripts separately, but produce a single package. 33 34`resholve.writeScript` and `resholve.writeScriptBin` support a _single_ 35`solution` attrset. This is basically the same as any single solution in `resholve.mkDerivation`, except that it doesn't need a `scripts` attr (it is automatically added). `resholve.phraseSolution` also only accepts a single solution--but it _does_ still require the `scripts` attr. 36 37## Basic `resholve.mkDerivation` Example 38 39Here's a simple example of how `resholve.mkDerivation` is already used in nixpkgs: 40 41<!-- TODO: figure out how to pull this externally? --> 42 43```nix 44{ lib 45, fetchFromGitHub 46, resholve 47, bash 48, coreutils 49, goss 50, which 51}: 52 53resholve.mkDerivation rec { 54 pname = "dgoss"; 55 version = "0.3.18"; 56 57 src = fetchFromGitHub { 58 owner = "aelsabbahy"; 59 repo = "goss"; 60 rev = "v${version}"; 61 sha256 = "01ssc7rnnwpyhjv96qy8drsskghbfpyxpsahk8s62lh8pxygynhv"; 62 }; 63 64 dontConfigure = true; 65 dontBuild = true; 66 67 installPhase = '' 68 sed -i '2i GOSS_PATH=${goss}/bin/goss' extras/dgoss/dgoss 69 install -D extras/dgoss/dgoss $out/bin/dgoss 70 ''; 71 72 solutions = { 73 default = { 74 scripts = [ "bin/dgoss" ]; 75 interpreter = "${bash}/bin/bash"; 76 inputs = [ coreutils which ]; 77 keep = { 78 "$CONTAINER_RUNTIME" = true; 79 }; 80 }; 81 }; 82 83 meta = with lib; { 84 homepage = "https://github.com/aelsabbahy/goss/blob/v${version}/extras/dgoss/README.md"; 85 description = "Convenience wrapper around goss that aims to bring the simplicity of goss to docker containers"; 86 license = licenses.asl20; 87 platforms = platforms.linux; 88 maintainers = with maintainers; [ hyzual ]; 89 }; 90} 91``` 92 93 94## Basic `resholve.writeScript` and `resholve.writeScriptBin` examples 95 96Both of these functions have the same basic API. The examples are a little 97trivial, so I'll also link to some real-world examples: 98- [shell.nix from abathur/tdverpy](https://github.com/abathur/tdverpy/blob/e1f956df3ed1c7097a5164e0c85b178772e277f5/shell.nix#L6-L13) 99 100```nix 101resholvedScript = resholve.writeScript "name" { 102 inputs = [ file ]; 103 interpreter = "${bash}/bin/bash"; 104 } '' 105 echo "Hello" 106 file . 107 ''; 108resholvedScriptBin = resholve.writeScriptBin "name" { 109 inputs = [ file ]; 110 interpreter = "${bash}/bin/bash"; 111 } '' 112 echo "Hello" 113 file . 114 ''; 115``` 116 117 118## Basic `resholve.phraseSolution` example 119 120This function has a similar API to `writeScript` and `writeScriptBin`, except it does require a `scripts` attr. It is intended to make resholve a little easier to mix into more types of build. This example is a little 121trivial for now. If you have a real usage that you find helpful, please PR it. 122 123```nix 124{ stdenv, resholve, module1 }: 125 126stdenv.mkDerivation { 127 # pname = "testmod3"; 128 # version = "unreleased"; 129 # src = ...; 130 131 installPhase = '' 132 mkdir -p $out/bin 133 install conjure.sh $out/bin/conjure.sh 134 ${resholve.phraseSolution "conjure" { 135 scripts = [ "bin/conjure.sh" ]; 136 interpreter = "${bash}/bin/bash"; 137 inputs = [ module1 ]; 138 fake = { 139 external = [ "jq" "openssl" ]; 140 }; 141 }} 142 ''; 143} 144``` 145 146 147## Options 148 149`resholve.mkDerivation` maps Nix types/idioms into the flags and environment variables 150that the `resholve` CLI expects. Here's an overview: 151 152| Option | Type | Containing | 153|--------|------|------------| 154| scripts | `<list>` | scripts to resolve (`$out`-relative paths) | 155| interpreter | `"none"` `<path>` | The absolute interpreter `<path>` for the script's shebang. The special value `none` ensures there is no shebang. | 156| inputs | `<packages>` `<paths>` | A list of packages and string paths to directories/files to resolve external dependencies from. | 157| fake | `<directives>` | pretend some commands exist | 158| fix | `<directives>` | fix things we can't auto-fix/ignore | 159| keep | `<directives>` | keep things we can't auto-fix/ignore | 160| lore | `<directory>` | control nested resolution | 161| execer | `<statements>` | modify nested resolution | 162| wrapper | `<statements>` | modify nested resolution | 163| prologue | `<file>` | insert file before resolved script | 164| epilogue | `<file>` | insert file after resolved script | 165 166<!-- TODO: section below is largely custom for nixpkgs, but I would LIKE to wurst it. --> 167 168## Controlling resolution with directives 169 170In order to resolve a script, resholve will make you disambiguate how it should 171handle any potential problems it encounters with directives. There are currently 1723 types: 1731. `fake` directives tell resholve to pretend it knows about an identifier 174 such as a function, builtin, external command, etc. if there's a good reason 175 it doesn't already know about it. Common examples: 176 - builtins for a non-bash shell 177 - loadable builtins 178 - platform-specific external commands in cross-platform conditionals 1792. `fix` directives give resholve permission to fix something that it can't 180 safely fix automatically. Common examples: 181 - resolving commands in aliases (this is appropriate for standalone scripts 182 that use aliases non-interactively--but it would prevent profile/rc 183 scripts from using the latest current-system symlinks.) 184 - resolve commands in a variable definition 185 - resolve an absolute command path from inputs as if it were a bare reference 186 - force resholve to resolve known security wrappers 1873. `keep` directives tell resholve not to raise an error (i.e., ignore) 188 something it would usually object to. Common examples: 189 - variables used as/within the first word of a command 190 - pre-existing absolute or user-relative (~) command paths 191 - dynamic (variable) arguments to commands known to accept/run other commands 192 193> NOTE: resholve has a (growing) number of directives detailed in `man resholve` 194> via `nixpkgs.resholve` (though protections against run-time use of python2 in nixpkgs mean you'll have to set `NIXPKGS_ALLOW_INSECURE=1` to pull resholve into nix-shell). 195 196Each of these 3 types is represented by its own attrset, where you can think 197of the key as a scope. The value should be: 198- `true` for any directives that the resholve CLI accepts as a single word 199- a list of strings for all other options 200<!-- 201TODO: these should be fully-documented here, but I'm already maintaining 202more copies of their specification/behavior than I like, and continuing to 203add more at this early date will only ensure that I spend more time updating 204docs and less time filling in feature gaps. 205 206Full documentation may be greatly accelerated if someone can help me sort out 207single-sourcing. See: https://github.com/abathur/resholve/issues/19 208--> 209 210This will hopefully make more sense when you see it. Here are CLI examples 211from the manpage, and the Nix equivalents: 212 213```nix 214# --fake 'f:setUp;tearDown builtin:setopt source:/etc/bashrc' 215fake = { 216 # fake accepts the initial of valid identifier types as a CLI convenience. 217 # Use full names in the Nix API. 218 function = [ "setUp" "tearDown" ]; 219 builtin = [ "setopt" ]; 220 source = [ "/etc/bashrc" ]; 221}; 222 223# --fix 'aliases $GIT:gix /bin/bash' 224fix = { 225 # all single-word directives use `true` as value 226 aliases = true; 227 "$GIT" = [ "gix" ]; 228 "/bin/bash"; 229}; 230 231# --keep 'source:$HOME /etc/bashrc ~/.bashrc' 232keep = { 233 source = [ "$HOME" ]; 234 "/etc/bashrc" = true; 235 "~/.bashrc" = true; 236}; 237``` 238 239 240> **Note:** For now, at least, you'll need to reference the manpage to completely understand these examples. 241 242## Controlling nested resolution with lore 243 244Initially, resolution of commands in the arguments to command-executing 245commands was limited to one level for a hard-coded list of builtins and 246external commands. resholve can now resolve these recursively. 247 248This feature combines information (_lore_) that the resholve Nix API 249obtains via binlore ([nixpkgs](../../tools/analysis/binlore), [repo](https://github.com/abathur/resholve)), 250with some rules (internal to resholve) for locating sub-executions in 251some of the more common commands. 252 253- "execer" lore identifies whether an executable can, cannot, 254 or might execute its arguments. Every "can" or "might" verdict requires: 255 - an update to the matching rules in [binlore](https://github.com/abathur/binlore) 256 if there's absolutely no exec in the executable and binlore just lacks 257 rules for understanding this 258 - an override in [binlore](https://github.com/abathur/binlore) if there is 259 exec but it isn't actually under user control 260 - a parser in [resholve](https://github.com/abathur/resholve) capable of 261 isolating the exec'd words if the command does have exec under user 262 control 263 - overriding the execer lore for the executable if manual triage indicates 264 that all of the invocations in the current package don't include any 265 commands that the executable would exec 266 - if manual triage turns up any commands that would be exec'd, use some 267 non-resholve tool to patch/substitute/replace them before or after you 268 run resholve on them (if before, you may need to also add keep directives 269 for these absolute paths) 270 271- "wrapper" lore maps shell exec wrappers to the programs they exec so 272 that resholve can substitute an executable's verdict for its wrapper's. 273 274> **Caution:** At least when it comes to common utilities, it's best to treat 275> overrides as a stopgap until they can be properly handled in resholve and/or 276> binlore. Please report things you have to override and, if possible, help 277> get them sorted. 278 279There will be more mechanisms for controlling this process in the future 280(and your reports/experiences will play a role in shaping them...) For now, 281the main lever is the ability to substitute your own lore. This is how you'd 282do it piecemeal: 283 284```nix 285# --execer 'cannot:${openssl.bin}/bin/openssl can:${openssl.bin}/bin/c_rehash' 286execer = [ 287 /* 288 This is the same verdict binlore will 289 come up with. It's a no-op just to demo 290 how to fiddle lore via the Nix API. 291 */ 292 "cannot:${openssl.bin}/bin/openssl" 293 # different verdict, but not used 294 "can:${openssl.bin}/bin/c_rehash" 295]; 296 297# --wrapper '${gnugrep}/bin/egrep:${gnugrep}/bin/grep' 298wrapper = [ 299 /* 300 This is the same verdict binlore will 301 come up with. It's a no-op just to demo 302 how to fiddle lore via the Nix API. 303 */ 304 "${gnugrep}/bin/egrep:${gnugrep}/bin/grep" 305]; 306``` 307 308 309The format is fairly simple to generate--you can script your own generator if 310you need to modify the lore.