{ self, lib, ... }: { imports = [ ./mpv/default.nix ./notmuch/default.nix ]; flake.options.wrapperModules = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); default = { }; description = "Wrapper modules"; }; /** A function to create a wrapper module. returns an attribute set with options and apply function. Example usage: helloWrapper = mkWrapper (wlib: { config, ... }: { options.greeting = lib.mkOption { type = lib.types.str; default = "hello"; }; config.package = config.pkgs.hello; config.flags = { "--greeting" = config.greeting; }; }; helloWrapper.apply { pkgs = pkgs; greeting = "hi"; }; # This will return a derivation that wraps the hello package with the --greeting flag set to "hi". */ flake.config.wrapLib.mkWrapper = declarationArgs: let declaration = declarationArgs wrapperLib; wrapperLib = { types = { inherit file; }; }; file = # we need to pass pkgs here, because writeText is in pkgs pkgs: lib.types.submodule ( { name, config, ... }: { options = { content = lib.mkOption { type = lib.types.lines; description = '' content of file ''; }; path = lib.mkOption { type = lib.types.path; description = '' the path to the file ''; default = pkgs.writeText name config.content; defaultText = "pkgs.writeText name "; }; }; } ); evalArgs = cfg: { modules = [ declaration cfg { options = { pkgs = lib.mkOption { description = '' The nixpkgs pkgs instance to use. We want to have this, so wrapper modules can be system agnostic. ''; }; package = lib.mkOption { type = lib.types.package; description = '' The base package to wrap. This means we inherit all other files from this package (like man page, /share, ...) ''; }; extraPackages = lib.mkOption { type = lib.types.listOf lib.types.package; default = [ ]; description = '' Additional packages to add to the wrapper's runtime dependencies. This is useful if the wrapped program needs additional libraries or tools to function correctly. These packages will be added to the wrapper's runtime dependencies, ensuring they are available when the wrapped program is executed. ''; }; flags = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; # TODO add list handling default = { }; description = '' Flags to pass to the wrapper. The key is the flag name, the value is the flag value. If the value is true, the flag will be passed without a value. If the value is false or null, the flag will not be passed. If the value is a list, the flag will be passed multiple times with each value. ''; }; env = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { }; description = '' Environment variables to set in the wrapper. ''; }; passthru = lib.mkOption { type = lib.types.attrs; default = { }; description = '' Additional attributes to add to the resulting derivation's passthru. This can be used to add additional metadata or functionality to the wrapped package. This will always contain options, config and settings, so these are reserved names and cannot be used here. ''; }; }; } ]; }; in { # expose options to generate documentation of available modules options = (lib.evalModules (evalArgs { })).options; apply = settings: let eval = lib.evalModules (evalArgs { config = settings; }); options = eval.options; config = eval.config; in self.wrapLib.makeWrapper { pkgs = config.pkgs; package = config.package; runtimeInputs = config.extraPackages; flagSeparator = "="; flags = config.flags; env = config.env; passthru = { inherit options config settings; } // config.passthru; }; }; /** Create a wrapped application that preserves all original outputs (man pages, completions, etc.) # Arguments - `pkgs`: The nixpkgs pkgs instance to use - `package`: The package to wrap - `runtimeInputs`: List of packages to add to PATH (optional) - `env`: Attribute set of environment variables to export (optional) - `flags`: Attribute set of command-line flags to add (optional) - `flagSeparator`: Separator between flag names and values (optional, defaults to " ") - `preHook`: Shell script to run before executing the command (optional) - `passthru`: Attribute set to pass through to the wrapped derivation (optional) - `aliases`: List of additional names to symlink to the wrapped executable (optional) - `wrapper`: Custom wrapper function (optional, defaults to exec'ing the original binary with flags) - Called with { env, flags, envString, flagsString, exePath, preHook } # Example ```nix makeWrapper { pkgs = pkgs; package = pkgs.curl; runtimeInputs = [ pkgs.jq ]; env = { CURL_CA_BUNDLE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; }; flags = { "--silent" = { }; # becomes --silent "--connect-timeout" = "30"; # becomes --connect-timeout 30 }; preHook = '' echo "Making request..." >&2 ''; } # Or with custom wrapper: makeWrapper pkgs.someProgram { wrapper = { exePath, flagsString, envString, preHook, ... }: '' ${envString} ${preHook} echo "Custom logic here" exec ${exePath} ${flagsString} "$@" ''; } ``` */ flake.config.wrapLib.makeWrapper = { pkgs, package, runtimeInputs ? [ ], env ? { }, flags ? { }, flagSeparator ? " ", # " " for "--flag value" or "=" for "--flag=value" preHook ? "", passthru ? { }, aliases ? [ ], wrapper ? ( { exePath, flagsString, envString, preHook, ... }: '' ${envString} ${preHook} exec ${exePath}${flagsString} "$@" '' ), }: let # Extract binary name from the exe path exePath = lib.getExe package; binName = baseNameOf exePath; # Generate environment variable exports envString = if env == { } then "" else lib.concatStringsSep "\n" ( lib.mapAttrsToList (name: value: ''export ${name}="${toString value}"'') env ) + "\n"; # Generate flag arguments with proper line breaks and indentation flagsString = if flags == { } then "" else " \\\n " + lib.concatStringsSep " \\\n " ( lib.mapAttrsToList ( name: value: if value == { } then "${name}" else "${name}${flagSeparator}${lib.escapeShellArg (toString value)}" ) flags ); finalWrapper = wrapper { inherit env flags envString flagsString exePath preHook ; }; # Multi-output aware symlink join function multiOutputSymlinkJoin = { name, paths, outputs ? [ "out" ], originalOutputs ? { }, passthru ? { }, meta ? { }, aliases ? [ ], binName ? null, ... }@args: pkgs.stdenv.mkDerivation ( { inherit name outputs; buildCommand = '' # Symlink all paths to the main output mkdir -p $out for path in ${lib.concatStringsSep " " (map toString paths)}; do ${pkgs.lndir}/bin/lndir -silent "$path" $out done # Create symlinks for aliases ${lib.optionalString (aliases != [ ] && binName != null) '' mkdir -p $out/bin for alias in ${lib.concatStringsSep " " (map lib.escapeShellArg aliases)}; do ln -sf ${lib.escapeShellArg binName} $out/bin/$alias done ''} # Handle additional outputs by symlinking from the original package's outputs ${lib.concatMapStringsSep "\n" ( output: if output != "out" && originalOutputs ? ${output} && originalOutputs.${output} != null then '' if [[ -n "''${${output}:-}" ]]; then mkdir -p ${"$" + output} # Only symlink from the original package's corresponding output ${pkgs.lndir}/bin/lndir -silent "${originalOutputs.${output}}" ${"$" + output} fi '' else "" ) outputs} ''; inherit passthru meta; } // (removeAttrs args [ "name" "paths" "outputs" "originalOutputs" "passthru" "meta" "aliases" "binName" ]) ); # Get original package outputs for symlinking originalOutputs = if package ? outputs then lib.listToAttrs ( map (output: { name = output; value = if package ? ${output} then package.${output} else null; }) package.outputs ) else { }; # Create the wrapper derivation using our multi-output aware symlink join wrappedPackage = multiOutputSymlinkJoin ( { name = package.pname or package.name; paths = [ (pkgs.writeShellApplication { name = binName; runtimeInputs = runtimeInputs; text = finalWrapper; }) package ]; outputs = if package ? outputs then package.outputs else [ "out" ]; inherit originalOutputs aliases binName; passthru = (package.passthru or { }) // passthru // { inherit env flags preHook aliases ; }; # Pass through original attributes meta = package.meta or { }; } // lib.optionalAttrs (package ? version) { inherit (package) version; } // lib.optionalAttrs (package ? pname) { inherit (package) pname; } ); in wrappedPackage; }