This repository has no description
6.6 kB
222 lines
1{ lib, pkgs, ... }:
2{
3 /**
4 Create a wrapped application that preserves all original outputs (man pages, completions, etc.)
5
6 # Arguments
7
8 - `package`: The package to wrap
9 - `runtimeInputs`: List of packages to add to PATH (optional)
10 - `env`: Attribute set of environment variables to export (optional)
11 - `flags`: Attribute set of command-line flags to add (optional)
12 - `flagSeparator`: Separator between flag names and values (optional, defaults to " ")
13 - `preHook`: Shell script to run before executing the command (optional)
14 - `passthru`: Attribute set to pass through to the wrapped derivation (optional)
15 - `aliases`: List of additional names to symlink to the wrapped executable (optional)
16 - `wrapper`: Custom wrapper function (optional, defaults to exec'ing the original binary with flags)
17 - Called with { env, flags, envString, flagsString, exePath, preHook }
18
19 # Example
20
21 ```nix
22 makeWrapper pkgs.curl {
23 runtimeInputs = [ pkgs.jq ];
24 env = {
25 CURL_CA_BUNDLE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
26 };
27 flags = {
28 "--silent" = { }; # becomes --silent
29 "--connect-timeout" = "30"; # becomes --connect-timeout 30
30 };
31 preHook = ''
32 echo "Making request..." >&2
33 '';
34 }
35
36 # Or with custom wrapper:
37 makeWrapper pkgs.someProgram {
38 wrapper = { exePath, flagsString, envString, preHook, ... }: ''
39 ${envString}
40 ${preHook}
41 echo "Custom logic here"
42 exec ${exePath} ${flagsString} "$@"
43 '';
44 }
45 ```
46 */
47 makeWrapper =
48 package:
49 {
50 runtimeInputs ? [ ],
51 env ? { },
52 flags ? { },
53 flagSeparator ? " ", # " " for "--flag value" or "=" for "--flag=value"
54 preHook ? "",
55 passthru ? { },
56 aliases ? [ ],
57 wrapper ? (
58 {
59 exePath,
60 flagsString,
61 envString,
62 preHook,
63 ...
64 }:
65 ''
66 ${envString}
67 ${preHook}
68 exec ${exePath}${flagsString} "$@"
69 ''
70 ),
71 }:
72 let
73 # Extract binary name from the exe path
74 exePath = lib.getExe package;
75 binName = baseNameOf exePath;
76
77 # Generate environment variable exports
78 envString =
79 if env == { } then
80 ""
81 else
82 lib.concatStringsSep "\n" (
83 lib.mapAttrsToList (name: value: ''export ${name}="${toString value}"'') env
84 )
85 + "\n";
86
87 # Generate flag arguments with proper line breaks and indentation
88 flagsString =
89 if flags == { } then
90 ""
91 else
92 " \\\n "
93 + lib.concatStringsSep " \\\n " (
94 lib.mapAttrsToList (
95 name: value:
96 if value == { } then "${name}" else "${name}${flagSeparator}${lib.escapeShellArg (toString value)}"
97 ) flags
98 );
99
100 finalWrapper = wrapper {
101 inherit
102 env
103 flags
104 envString
105 flagsString
106 exePath
107 preHook
108 ;
109 };
110
111 # Multi-output aware symlink join function
112 multiOutputSymlinkJoin =
113 {
114 name,
115 paths,
116 outputs ? [ "out" ],
117 originalOutputs ? { },
118 passthru ? { },
119 meta ? { },
120 aliases ? [ ],
121 binName ? null,
122 ...
123 }@args:
124 pkgs.stdenv.mkDerivation (
125 {
126 inherit name outputs;
127
128 buildCommand = ''
129 # Symlink all paths to the main output
130 mkdir -p $out
131 for path in ${lib.concatStringsSep " " (map toString paths)}; do
132 ${pkgs.lndir}/bin/lndir -silent "$path" $out
133 done
134
135 # Create symlinks for aliases
136 ${lib.optionalString (aliases != [ ] && binName != null) ''
137 mkdir -p $out/bin
138 for alias in ${lib.concatStringsSep " " (map lib.escapeShellArg aliases)}; do
139 ln -sf ${lib.escapeShellArg binName} $out/bin/$alias
140 done
141 ''}
142
143 # Handle additional outputs by symlinking from the original package's outputs
144 ${lib.concatMapStringsSep "\n" (
145 output:
146 if output != "out" && originalOutputs ? ${output} && originalOutputs.${output} != null then
147 ''
148 if [[ -n "''${${output}:-}" ]]; then
149 mkdir -p ${"$" + output}
150 # Only symlink from the original package's corresponding output
151 ${pkgs.lndir}/bin/lndir -silent "${originalOutputs.${output}}" ${"$" + output}
152 fi
153 ''
154 else
155 ""
156 ) outputs}
157 '';
158
159 inherit passthru meta;
160 }
161 // (removeAttrs args [
162 "name"
163 "paths"
164 "outputs"
165 "originalOutputs"
166 "passthru"
167 "meta"
168 "aliases"
169 "binName"
170 ])
171 );
172
173 # Get original package outputs for symlinking
174 originalOutputs =
175 if package ? outputs then
176 lib.listToAttrs (
177 map (output: {
178 name = output;
179 value = if package ? ${output} then package.${output} else null;
180 }) package.outputs
181 )
182 else
183 { };
184
185 # Create the wrapper derivation using our multi-output aware symlink join
186 wrappedPackage = multiOutputSymlinkJoin (
187 {
188 name = package.pname or package.name;
189 paths = [
190 (pkgs.writeShellApplication {
191 name = binName;
192 runtimeInputs = runtimeInputs;
193 text = finalWrapper;
194 })
195 package
196 ];
197 outputs = if package ? outputs then package.outputs else [ "out" ];
198 inherit originalOutputs aliases binName;
199 passthru =
200 (package.passthru or { })
201 // passthru
202 // {
203 inherit
204 env
205 flags
206 preHook
207 aliases
208 ;
209 };
210 # Pass through original attributes
211 meta = package.meta or { };
212 }
213 // lib.optionalAttrs (package ? version) {
214 inherit (package) version;
215 }
216 // lib.optionalAttrs (package ? pname) {
217 inherit (package) pname;
218 }
219 );
220 in
221 wrappedPackage;
222}