simple and robust nix package wrapper function
wrap.nix
183 lines 5.6 kB view raw
1/** 2 Wraps a package with flags and env vars. 3 4 The wrapper derivation has each specified binary in the original package replaced by a wrapper with the specified flags and environment variables. 5 Every other file from the original derivation is symlinked to by the new derivation, except any file referencing the original $pkg will be cloned and have that reference replaced by the wrapper derivation's $out. 6 7 # Example 8 9 ```nix 10 mkWrapper = import ./wrap.nix; 11 editor = mkWrapper pkgs { 12 pkg = pkgs.helix; 13 hx = { 14 env.idk = "hi"; 15 prependFlags = [ 16 "--config" 17 "./helix.toml" 18 ]; 19 }; 20 }; 21 ``` 22 23 # Type 24 25 ``` 26 mkWrapper :: Pkgs -> { 27 pkg : Derivation, 28 pname? : String, 29 additionalDependencies? : [Derivation], 30 <bin> : { 31 env? : AttrSet String, 32 prependFlags? : [String] 33 appendFlags? : [String] 34 } 35 } -> Derivation 36 ``` 37 38 # Arguments 39 40 pkgs 41 : An instance of nixpkgs 42 43 pkg 44 : The derivation to wrap 45 46 pname 47 : An optional name for the wrapped package, defaults to `pkg.name` 48 49 additionalDependencies 50 : An optional list of additional derivations to include in the wrapped environment 51 52 <bin> 53 : Variadic number of attrsets in the form bin = { env?, flags? }, where `bin` is the name of the binary that `pkg` outputs that you want to wrap. `env` and `flags` are detailed below. 54 55 env 56 : An optional attrset specifying environment variables to set inside the wrapped environment, defaults to `{}` 57 58 prependFlags 59 : An optional list specifying flags to append to the wrapped binary, defaults to `[]`. These flags go before your arguments, in the form `bin --prepended $@`. 60 61 appendFlags 62 : An optional list specifying flags to append to the wrapped binary, defaults to `[]` These flags go after your arguments, in the form `bin $@ --appended`. 63*/ 64pkgs: 65let 66 wrap = 67 { 68 pkg, 69 pname ? pkg.pname, 70 version ? pkg.version, 71 additionalDependencies ? [ ], 72 ... 73 }@args: 74 75 let 76 lib = pkgs.lib; 77 in 78 79 assert lib.isDerivation pkg || throw "'pkg' must be a derivation (e.g. pkgs.hello)"; 80 assert lib.isString pname || throw "'name' must be a string"; 81 82 let 83 # all keyword kwargs are bins 84 binaries = builtins.removeAttrs args [ 85 "pkg" 86 "pname" 87 "version" 88 "additionalDependencies" 89 ]; 90 91 wrapCommands = lib.mapAttrsToList ( 92 bin: 93 { 94 prependFlags ? [ ], 95 appendFlags ? [ ], 96 env ? { }, 97 }: 98 assert 99 (lib.isList prependFlags && lib.all lib.isString prependFlags) 100 || throw "'${bin}.addFlags' must be a list of strings"; 101 assert 102 (lib.isList appendFlags && lib.all lib.isString appendFlags) 103 || throw "'${bin}.addFlags' must be a list of strings"; 104 assert 105 (lib.isAttrs env && lib.all lib.isString (lib.attrValues env)) 106 || throw "'${bin}.env' must be an attribute set of strings"; 107 let 108 envArgs = lib.concatStringsSep " " (lib.mapAttrsToList (k: v: "--set '${k}' '${v}'") env); 109 in 110 '' 111 wrapProgram $out/bin/${bin} \ 112 --add-flags "${builtins.toString prependFlags}" \ 113 --append-flags "${builtins.toString appendFlags}" \ 114 ${envArgs} 115 '' 116 ) binaries; 117 118 # nasty script to replace all text references to ${pkg} with ${out} 119 # this is necessary because some files that get symlinked into the new 120 # package directory ($out) will still reference ${pkg}, leading to 121 # unexpected behavior 122 fixReferences = '' 123 # iterate through every file/symlink in $out 124 find "$out" \( -type l -o -type f \) | while read -r item; do 125 # if item is a symlink, we want to (recursively) find the file it 126 # points to, read it, and if it contains $pkg, overwrite the symlink 127 # with a regular file to replace $pkg with $out 128 if [ -L "$item" ]; then 129 # redirection is to ignore broken symlinks 130 target=$(readlink -f "$item" 2>/dev/null || true) 131 132 # if our target is a nonempty file containing pkg, replace item with target 133 if [ -n "$target" ] && [ -f "$target" ] && grep -F -q "${pkg}" "$target"; then 134 rm "$item" 135 cp "$target" "$item" 136 # otherwise, skip the file replacement code 137 else 138 continue 139 fi 140 fi 141 142 # $item is guaranteed to be a regular file 143 if grep -F -q "${pkg}" "$item"; then 144 # ensure $item is writeable 145 [ -w "$item" ] || chmod +w "$item" 146 # replace all occurances of $pkg with $out 147 sed -i "s|${pkg}|$out|g" "$item" || true 148 fi 149 done 150 ''; 151 in 152 pkgs.symlinkJoin { 153 inherit pname; 154 inherit version; 155 passthru = (pkg.passthru or { }) // { 156 unwrapped = pkg; 157 158 override = 159 overrideArgs: 160 let 161 newPkg = pkg.override overrideArgs; 162 newArgs = args // { 163 pkg = newPkg; 164 }; 165 in 166 wrap newArgs; 167 168 overrideAttrs = 169 f: 170 let 171 newPkg = pkg.overrideAttrs f; 172 newArgs = args // { 173 pkg = newPkg; 174 }; 175 in 176 wrap newArgs; 177 }; 178 paths = [ pkg ]; 179 buildInputs = [ pkgs.makeWrapper ] ++ additionalDependencies; 180 postBuild = lib.concatStringsSep "\n" (wrapCommands ++ [ fixReferences ]); 181 }; 182in 183wrap