This repository has no description
12 kB
379 lines
1{ self, lib, ... }:
2{
3 imports = [
4 ./mpv/default.nix
5 ./notmuch/default.nix
6 ];
7 flake.options.wrapperModules = lib.mkOption {
8 type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified);
9 default = { };
10 description = "Wrapper modules";
11 };
12 /**
13 A function to create a wrapper module.
14 returns an attribute set with options and apply function.
15
16 Example usage:
17 helloWrapper = mkWrapper (wlib: { config, ... }: {
18 options.greeting = lib.mkOption {
19 type = lib.types.str;
20 default = "hello";
21 };
22 config.package = config.pkgs.hello;
23 config.flags = {
24 "--greeting" = config.greeting;
25 };
26 };
27
28 helloWrapper.apply {
29 pkgs = pkgs;
30 greeting = "hi";
31 };
32
33 # This will return a derivation that wraps the hello package with the --greeting flag set to "hi".
34 */
35 flake.config.wrapLib.mkWrapper =
36 declarationArgs:
37 let
38 declaration = declarationArgs wrapperLib;
39
40 wrapperLib = {
41 types = {
42 inherit file;
43 };
44 };
45 file =
46 # we need to pass pkgs here, because writeText is in pkgs
47 pkgs:
48 lib.types.submodule (
49 { name, config, ... }:
50 {
51 options = {
52 content = lib.mkOption {
53 type = lib.types.lines;
54 description = ''
55 content of file
56 '';
57 };
58 path = lib.mkOption {
59 type = lib.types.path;
60 description = ''
61 the path to the file
62 '';
63 default = pkgs.writeText name config.content;
64 defaultText = "pkgs.writeText name <content>";
65 };
66 };
67 }
68 );
69 evalArgs = cfg: {
70 modules = [
71 declaration
72 cfg
73 {
74 options = {
75 pkgs = lib.mkOption {
76 description = ''
77 The nixpkgs pkgs instance to use.
78 We want to have this, so wrapper modules can be system agnostic.
79 '';
80 };
81 package = lib.mkOption {
82 type = lib.types.package;
83 description = ''
84 The base package to wrap.
85 This means we inherit all other files from this package
86 (like man page, /share, ...)
87 '';
88 };
89 extraPackages = lib.mkOption {
90 type = lib.types.listOf lib.types.package;
91 default = [ ];
92 description = ''
93 Additional packages to add to the wrapper's runtime dependencies.
94 This is useful if the wrapped program needs additional libraries or tools to function correctly.
95 These packages will be added to the wrapper's runtime dependencies, ensuring they are available when the wrapped program is executed.
96 '';
97 };
98 flags = lib.mkOption {
99 type = lib.types.attrsOf lib.types.unspecified; # TODO add list handling
100 default = { };
101 description = ''
102 Flags to pass to the wrapper.
103 The key is the flag name, the value is the flag value.
104 If the value is true, the flag will be passed without a value.
105 If the value is false or null, the flag will not be passed.
106 If the value is a list, the flag will be passed multiple times with each value.
107 '';
108 };
109 env = lib.mkOption {
110 type = lib.types.attrsOf lib.types.str;
111 default = { };
112 description = ''
113 Environment variables to set in the wrapper.
114 '';
115 };
116 passthru = lib.mkOption {
117 type = lib.types.attrs;
118 default = { };
119 description = ''
120 Additional attributes to add to the resulting derivation's passthru.
121 This can be used to add additional metadata or functionality to the wrapped package.
122 This will always contain options, config and settings, so these are reserved names and cannot be used here.
123 '';
124 };
125 };
126 }
127 ];
128 };
129 in
130 {
131 # expose options to generate documentation of available modules
132 options = (lib.evalModules (evalArgs { })).options;
133 apply =
134 settings:
135 let
136 eval = lib.evalModules (evalArgs {
137 config = settings;
138 });
139 options = eval.options;
140 config = eval.config;
141 in
142 self.wrapLib.makeWrapper {
143 pkgs = config.pkgs;
144 package = config.package;
145 runtimeInputs = config.extraPackages;
146 flagSeparator = "=";
147 flags = config.flags;
148 env = config.env;
149 passthru = {
150 inherit options config settings;
151 }
152 // config.passthru;
153 };
154 };
155
156 /**
157 Create a wrapped application that preserves all original outputs (man pages, completions, etc.)
158
159 # Arguments
160
161 - `pkgs`: The nixpkgs pkgs instance to use
162 - `package`: The package to wrap
163 - `runtimeInputs`: List of packages to add to PATH (optional)
164 - `env`: Attribute set of environment variables to export (optional)
165 - `flags`: Attribute set of command-line flags to add (optional)
166 - `flagSeparator`: Separator between flag names and values (optional, defaults to " ")
167 - `preHook`: Shell script to run before executing the command (optional)
168 - `passthru`: Attribute set to pass through to the wrapped derivation (optional)
169 - `aliases`: List of additional names to symlink to the wrapped executable (optional)
170 - `wrapper`: Custom wrapper function (optional, defaults to exec'ing the original binary with flags)
171 - Called with { env, flags, envString, flagsString, exePath, preHook }
172
173 # Example
174
175 ```nix
176 makeWrapper {
177 pkgs = pkgs;
178 package = pkgs.curl;
179 runtimeInputs = [ pkgs.jq ];
180 env = {
181 CURL_CA_BUNDLE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
182 };
183 flags = {
184 "--silent" = { }; # becomes --silent
185 "--connect-timeout" = "30"; # becomes --connect-timeout 30
186 };
187 preHook = ''
188 echo "Making request..." >&2
189 '';
190 }
191
192 # Or with custom wrapper:
193 makeWrapper pkgs.someProgram {
194 wrapper = { exePath, flagsString, envString, preHook, ... }: ''
195 ${envString}
196 ${preHook}
197 echo "Custom logic here"
198 exec ${exePath} ${flagsString} "$@"
199 '';
200 }
201 ```
202 */
203 flake.config.wrapLib.makeWrapper =
204 {
205 pkgs,
206 package,
207 runtimeInputs ? [ ],
208 env ? { },
209 flags ? { },
210 flagSeparator ? " ", # " " for "--flag value" or "=" for "--flag=value"
211 preHook ? "",
212 passthru ? { },
213 aliases ? [ ],
214 wrapper ? (
215 {
216 exePath,
217 flagsString,
218 envString,
219 preHook,
220 ...
221 }:
222 ''
223 ${envString}
224 ${preHook}
225 exec ${exePath}${flagsString} "$@"
226 ''
227 ),
228 }:
229 let
230 # Extract binary name from the exe path
231 exePath = lib.getExe package;
232 binName = baseNameOf exePath;
233
234 # Generate environment variable exports
235 envString =
236 if env == { } then
237 ""
238 else
239 lib.concatStringsSep "\n" (
240 lib.mapAttrsToList (name: value: ''export ${name}="${toString value}"'') env
241 )
242 + "\n";
243
244 # Generate flag arguments with proper line breaks and indentation
245 flagsString =
246 if flags == { } then
247 ""
248 else
249 " \\\n "
250 + lib.concatStringsSep " \\\n " (
251 lib.mapAttrsToList (
252 name: value:
253 if value == { } then "${name}" else "${name}${flagSeparator}${lib.escapeShellArg (toString value)}"
254 ) flags
255 );
256
257 finalWrapper = wrapper {
258 inherit
259 env
260 flags
261 envString
262 flagsString
263 exePath
264 preHook
265 ;
266 };
267
268 # Multi-output aware symlink join function
269 multiOutputSymlinkJoin =
270 {
271 name,
272 paths,
273 outputs ? [ "out" ],
274 originalOutputs ? { },
275 passthru ? { },
276 meta ? { },
277 aliases ? [ ],
278 binName ? null,
279 ...
280 }@args:
281 pkgs.stdenv.mkDerivation (
282 {
283 inherit name outputs;
284
285 buildCommand = ''
286 # Symlink all paths to the main output
287 mkdir -p $out
288 for path in ${lib.concatStringsSep " " (map toString paths)}; do
289 ${pkgs.lndir}/bin/lndir -silent "$path" $out
290 done
291
292 # Create symlinks for aliases
293 ${lib.optionalString (aliases != [ ] && binName != null) ''
294 mkdir -p $out/bin
295 for alias in ${lib.concatStringsSep " " (map lib.escapeShellArg aliases)}; do
296 ln -sf ${lib.escapeShellArg binName} $out/bin/$alias
297 done
298 ''}
299
300 # Handle additional outputs by symlinking from the original package's outputs
301 ${lib.concatMapStringsSep "\n" (
302 output:
303 if output != "out" && originalOutputs ? ${output} && originalOutputs.${output} != null then
304 ''
305 if [[ -n "''${${output}:-}" ]]; then
306 mkdir -p ${"$" + output}
307 # Only symlink from the original package's corresponding output
308 ${pkgs.lndir}/bin/lndir -silent "${originalOutputs.${output}}" ${"$" + output}
309 fi
310 ''
311 else
312 ""
313 ) outputs}
314 '';
315
316 inherit passthru meta;
317 }
318 // (removeAttrs args [
319 "name"
320 "paths"
321 "outputs"
322 "originalOutputs"
323 "passthru"
324 "meta"
325 "aliases"
326 "binName"
327 ])
328 );
329
330 # Get original package outputs for symlinking
331 originalOutputs =
332 if package ? outputs then
333 lib.listToAttrs (
334 map (output: {
335 name = output;
336 value = if package ? ${output} then package.${output} else null;
337 }) package.outputs
338 )
339 else
340 { };
341
342 # Create the wrapper derivation using our multi-output aware symlink join
343 wrappedPackage = multiOutputSymlinkJoin (
344 {
345 name = package.pname or package.name;
346 paths = [
347 (pkgs.writeShellApplication {
348 name = binName;
349 runtimeInputs = runtimeInputs;
350 text = finalWrapper;
351 })
352 package
353 ];
354 outputs = if package ? outputs then package.outputs else [ "out" ];
355 inherit originalOutputs aliases binName;
356 passthru =
357 (package.passthru or { })
358 // passthru
359 // {
360 inherit
361 env
362 flags
363 preHook
364 aliases
365 ;
366 };
367 # Pass through original attributes
368 meta = package.meta or { };
369 }
370 // lib.optionalAttrs (package ? version) {
371 inherit (package) version;
372 }
373 // lib.optionalAttrs (package ? pname) {
374 inherit (package) pname;
375 }
376 );
377 in
378 wrappedPackage;
379}