diff --git a/default.nix b/default.nix
index 2f9e050..33eb3f9 100644
--- a/default.nix
+++ b/default.nix
@@ -1,21 +1,27 @@
let
+ overlays.default =
+ final: prev:
+ prev.lib.packagesFromDirectoryRecursive {
+ inherit (final) callPackage;
+ directory = ./pkgs;
+ };
+
eval =
{
pkgs,
- lib ? pkgs.lib,
modules ? [ ],
specialArgs ? { },
}:
- lib.evalModules {
- modules = [
- ./modules/many-wrappers.nix
- ] ++ modules;
+ pkgs.lib.evalModules {
+ modules = [ ./modules ] ++ modules;
specialArgs = {
- inherit pkgs;
- } // specialArgs;
+ pkgs = pkgs.extend overlays.default;
+ }
+ // specialArgs;
};
in
{
+ inherit overlays;
lib = {
inherit eval;
__functor = _: eval;
@@ -24,10 +30,11 @@ in
(pkgs.lib.evalModules {
modules = [
./modules/wrapper.nix
+ pkgs.mkWrapper.modules.wrapped
module
];
specialArgs = {
- inherit pkgs;
+ pkgs = pkgs.extend overlays.default;
};
}).config.wrapped;
};
diff --git a/docs/api.md b/docs/api.md
index b1f982b..f526567 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -14,12 +14,16 @@ import { RenderDocs } from "easy-nix-documentation";
## Main API
-
+
## Program configuration
+## Modular modules
+
+
+
## Outputs
diff --git a/docs/modular-modules.nix b/docs/modular-modules.nix
new file mode 100644
index 0000000..63da4b3
--- /dev/null
+++ b/docs/modular-modules.nix
@@ -0,0 +1,40 @@
+# https://github.com/NixOS/nixpkgs/blob/52c032768a8bd0c7a23fdad4232e50a2962079c2/nixos/modules/misc/documentation/modular-services.nix
+{ lib, pkgs, ... }:
+let
+ fakeSubmodule =
+ module:
+ lib.mkOption {
+ type = lib.types.submodule [
+ module
+ {
+ _file = ../modules;
+ options.wrappers =
+ lib.mapAttrs
+ (
+ name: _:
+ lib.mkOption {
+ type = lib.types.submodule ../modules/wrapper.nix;
+ description = "Wrapper created by option `${name}`.";
+ }
+ )
+ (lib.evalModules {
+ modules = [
+ module
+ {
+ options.wrappers = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.submodule ../modules/wrapper.nix);
+ };
+ }
+ ];
+ }).config.wrappers;
+ }
+ ];
+ description = "This is a modular module (inspired by [modular service](https://nixos.org/manual/nixos/unstable/#modular-services)), which can be imported into a wrapper-manager configuration using the `wrappers` option.";
+ };
+
+ modularModulesModule = {
+ _file = "${__curPos.file}#L${toString __curPos.line}";
+ options = { };
+ };
+in
+modularModulesModule
diff --git a/docs/package.nix b/docs/package.nix
index 1a1eef2..64c48f0 100644
--- a/docs/package.nix
+++ b/docs/package.nix
@@ -9,10 +9,19 @@ let
options_json =
(nixosOptionsDoc {
options =
- ((import ../.).lib {
- inherit pkgs;
- modules = [ ];
- }).options;
+ lib.recursiveUpdate
+ ((import ../.).lib {
+ inherit pkgs;
+ modules = [ ./modular-modules.nix ];
+ }).options
+ {
+ wrappers.type = lib.types.attrsOf (
+ lib.types.submodule [
+ ../modules/wrapper.nix
+ (pkgs.extend (import ../.).overlays.default).mkWrapper.modules.wrapped
+ ]
+ );
+ };
}).optionsJSON;
in
buildNpmPackage {
@@ -25,7 +34,7 @@ buildNpmPackage {
inherit (importNpmLock) npmConfigHook;
env.WRAPPER_MANAGER_OPTIONS_JSON = options_json;
- passthru = {inherit options_json;};
+ passthru = { inherit options_json; };
buildPhase = ''
runHook preBuild
diff --git a/docs/readme.md b/docs/readme.md
index 59e8ea7..b2abbc9 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -172,6 +172,11 @@ https://github.com/viperML/wrapper-manager/issues
## Changelog
+- 2025-09-20
+ - Full rewrite
+ - `env` has been renamed to `envVars`
+ - `flags` and `wrapFlags` has been removed
+
- 2025-06-19
- Full rewrite
- `flags` has been removed in favor of `prependFlags`
diff --git a/modules/common-wrapper.nix b/modules/common-wrapper.nix
deleted file mode 100644
index 529b243..0000000
--- a/modules/common-wrapper.nix
+++ /dev/null
@@ -1,120 +0,0 @@
-{
- lib,
- config,
- options,
- ...
-}:
-let
- inherit (lib) mkOption types flatten;
- inherit (builtins) attrValues;
- flagsType = with types; listOf (coercedTo anything (x: "${x}") str);
-in
-{
- options = {
- wrapFlags = mkOption {
- type = flagsType;
- default = [ ];
- description = "Structured flags passed to makeWrapper.";
- example = [
- "--argv0"
- "myprog"
- ];
- };
- appendFlags = mkOption {
- type = flagsType;
- default = [ ];
- description = "Flags passed after any arguments to the wrapped program. Usually you want to use prependFlags instead.";
- example = lib.literalExpression ''
- ["--config-file" ./config.toml]
- '';
- };
- # Poor's man mkRemovedOptionModule
- # As we don't have assertions
- flags = mkOption {
- type = flagsType;
- default = [ ];
- description = "(Deprecated) Flags passed before any arguments to the wrapped program. Use prependFlags instead";
- apply =
- flags:
- if flags == [ ] then
- [ ]
- else
- throw "The option `${lib.showOption [ "flags" ]}' used in ${lib.showFiles options.flags.files} is deprecated. Use `${
- lib.showOption [ "prependFlags" ]
- }' instead.";
- };
- prependFlags = mkOption {
- type = flagsType;
- default = [ ];
- description = "Flags passed before any arguments to the wrapped program.";
- example = lib.literalExpression ''
- ["--config-file" ./config.toml]
- '';
- };
- env = mkOption {
- type = with types; attrsOf (submodule ./env-type.nix);
- default = { };
- description = "Structured configuration for environment variables.";
- example = lib.literalExpression ''
- {
- GIT_CONFIG.value = ./gitconfig;
- }
- '';
- };
- extraWrapperFlags = mkOption {
- type = with types; separatedString " ";
- description = ''
- Raw flags passed to makeWrapper. You may want to use wrapFlags instead.
- '';
- default = "";
- example = "--argv0 foo --set BAR value";
- };
- pathAdd = mkOption {
- type = with types; listOf package;
- description = ''
- Packages to append to PATH.
- '';
- default = [ ];
- example = lib.literalExpression "[ pkgs.starship ]";
- };
- wrapperType = mkOption {
- description = "Whether to use a binary or a shell wrapper.";
- type = types.enum [
- "shell"
- "binary"
- ];
- default = "binary";
- example = "shell";
- };
- };
-
- config = {
- wrapFlags =
- (flatten (
- map (f: [
- "--add-flag"
- f
- ]) config.prependFlags
- ))
- # Force the eval of config.flags to trigger throw
- ++ (flatten (
- map (f: [
- "--add-flag"
- f
- ]) config.flags
- ))
- ++ (flatten (
- map (f: [
- "--append-flag"
- f
- ]) config.appendFlags
- ))
- ++ (lib.optionals (config.pathAdd != [ ]) [
- "--prefix"
- "PATH"
- ":"
- (lib.makeBinPath config.pathAdd)
- ])
- ++ (flatten (map (e: e.asFlags) (attrValues config.env)));
- };
-}
diff --git a/modules/default.nix b/modules/default.nix
new file mode 100644
index 0000000..ea75830
--- /dev/null
+++ b/modules/default.nix
@@ -0,0 +1,127 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+let
+ inherit (lib) mkOption types;
+ # https://github.com/nix-community/disko/blob/146f45bee02b8bd88812cfce6ffc0f933788875a/lib/default.nix#L11-L61
+ subType =
+ subTypes:
+ lib.mkOptionType {
+ name = "subType";
+ description = "one of ${lib.concatStringsSep "," (lib.attrNames subTypes)}";
+ check = x: lib.isAttrs x;
+ merge =
+ loc: defs:
+ let
+ evaled = lib.evalModules {
+ modules = [
+ {
+ freeformType = types.lazyAttrsOf types.raw;
+ options.type = mkOption {
+ type = types.str;
+ default = "default";
+ };
+ }
+ ]
+ ++ map (
+ { value, file }:
+ {
+ _file = file;
+ config = value;
+ }
+ ) defs;
+ };
+ inherit (evaled.config) type;
+ in
+ subTypes.${type}.merge loc defs;
+ nestedTypes = subTypes;
+ };
+in
+{
+ options = {
+ wrapperType = mkOption {
+ description = "Which wrapper type to use by default for all wrappers.";
+ type = types.enum [
+ "shell"
+ "binary"
+ ];
+ default = "binary";
+ example = "shell";
+ };
+
+ wrappers = mkOption {
+ type = types.attrsOf (subType {
+ default = types.submodule [
+ {
+ options.type = mkOption {
+ type = types.enum [ "default" ];
+ readOnly = true;
+ internal = true;
+ };
+ }
+ ./wrapper.nix
+ pkgs.mkWrapper.modules.wrapped
+ { wrapperType = lib.mkDefault config.wrapperType; }
+ ];
+ custom = types.submodule [
+ {
+ options.type = mkOption {
+ type = types.enum [ "custom" ];
+ readOnly = true;
+ internal = true;
+ };
+ }
+ ./wrapper.nix
+ ];
+ });
+ description = ''
+ Wrappers to create.
+
+ Also a collection of [modular modules](#modular-modules) that are configured as wrappers.'';
+ example = lib.literalExpression ''
+ {
+ hello = {
+ basePackage = pkgs.hello;
+ prependFlags = [
+ "-g"
+ "Hi"
+ ];
+ };
+ }
+ '';
+ };
+
+ build = {
+ toplevel = mkOption {
+ type = types.package;
+ readOnly = true;
+ description = ''
+ (Read-only) Package that merges all the wrappers into a single derivation.
+ You may want to use build.packages instead.
+ '';
+ };
+
+ packages = mkOption {
+ type = with types; attrsOf package;
+ readOnly = true;
+ description = ''
+ (Read-only) Attribute set of name=pkg, for every wrapper.
+ '';
+ };
+ };
+ };
+
+ config = {
+ build = {
+ toplevel = pkgs.buildEnv {
+ name = "wrapper-manager-bundle";
+ paths = lib.attrValues config.build.packages;
+ };
+
+ packages = lib.mapAttrs (_: value: value.wrapped) config.wrappers;
+ };
+ };
+}
diff --git a/modules/env-type.nix b/modules/env-type.nix
deleted file mode 100644
index 85954ab..0000000
--- a/modules/env-type.nix
+++ /dev/null
@@ -1,98 +0,0 @@
-{
- config,
- lib,
- name,
- ...
-}:
-let
- inherit (lib) mkOption types;
-in
-{
- options = {
- name = mkOption {
- type = types.str;
- description = ''
- Name of the variable.
- '';
- default = name;
- example = "GIT_CONFIG";
- };
-
- value = mkOption {
- type =
- let
- inherit (types)
- coercedTo
- anything
- str
- nullOr
- ;
- strLike = coercedTo anything (x: "${x}") str;
- in
- nullOr strLike;
- description = ''
- Value of the variable to be set.
- Set to `null` to unset the variable.
-
- Note that any environment variable will be escaped. For example, `value = "$HOME"`
- will be converted to the literal `$HOME`, with its dollar sign.
- '';
- example = lib.literalExpression "./gitconfig";
- };
-
- force = mkOption {
- type = types.bool;
- description = ''
- Whether the value should be always set to the specified value.
- If set to `true`, the program will not inherit the value of the variable
- if it's already present in the environment.
-
- Setting it to false when unsetting a variable (value = null)
- will make the option have no effect.
- '';
- default = config.value == null;
- defaultText = lib.literalMD "true if `value` is null, otherwise false";
- example = true;
- };
-
- asFlags = mkOption {
- type = with types; listOf str;
- internal = true;
- readOnly = true;
- };
- };
-
- config = {
- asFlags =
- let
- unsetArgs =
- if !config.force then
- (lib.warn ''
- ${
- lib.showOption [
- "env"
- config.name
- "value"
- ]
- } is null (indicating unsetting the variable), but ${
- lib.showOption [
- "env"
- config.name
- "force"
- ]
- } is false. This option will have no effect
- '' [ ])
- else
- [
- "--unset"
- config.name
- ];
- setArgs = [
- (if config.force then "--set" else "--set-default")
- config.name
- config.value
- ];
- in
- (if config.value == null then unsetArgs else setArgs);
- };
-}
diff --git a/modules/many-wrappers.nix b/modules/many-wrappers.nix
deleted file mode 100644
index 1d04beb..0000000
--- a/modules/many-wrappers.nix
+++ /dev/null
@@ -1,80 +0,0 @@
-{
- config,
- lib,
- pkgs,
- ...
-}:
-let
- inherit (lib) mkOption types;
-in
-{
- options = {
- wrapperType = mkOption {
- description = "Which wrapper type to use by default for all wrappers.";
- type = types.enum [
- "shell"
- "binary"
- ];
- default = "binary";
- example = "shell";
- };
-
- wrappers = mkOption {
- type = types.attrsOf (
- types.submoduleWith {
- modules = [
- ./wrapper.nix
- {
- wrapperType = lib.mkDefault config.wrapperType;
- }
- ];
- specialArgs = {
- inherit pkgs;
- };
- }
- );
- description = "Wrappers to create.";
- example = lib.literalExpression ''
- {
- hello = {
- basePackage = pkgs.hello;
- prependFlags = [
- "-g"
- "Hi"
- ];
- };
- }
- '';
- };
-
- build = {
- toplevel = mkOption {
- type = types.package;
- readOnly = true;
- description = ''
- (Read-only) Package that merges all the wrappers into a single derivation.
- You may want to use build.packages instead.
- '';
- };
-
- packages = mkOption {
- type = with types; attrsOf package;
- readOnly = true;
- description = ''
- (Read-only) Attribute set of name=pkg, for every wrapper.
- '';
- };
- };
- };
-
- config = {
- build = {
- toplevel = pkgs.buildEnv {
- name = "wrapper-manager-bundle";
- paths = builtins.attrValues config.build.packages;
- };
-
- packages = builtins.mapAttrs (_: value: value.wrapped) config.wrappers;
- };
- };
-}
diff --git a/modules/wrapper-impl.nix b/modules/wrapper-impl.nix
deleted file mode 100644
index 162ad5a..0000000
--- a/modules/wrapper-impl.nix
+++ /dev/null
@@ -1,188 +0,0 @@
-{
- lib,
- pkgs,
- config,
- ...
-}:
-let
- inherit (lib) mkOption types;
- inherit (builtins) attrValues;
-
- printAndRun = cmd: ''
- echo ":: ${cmd}"
- eval "${cmd}"
- '';
-
- hasMan = builtins.any (builtins.hasAttr "man") ([ config.basePackage ] ++ config.extraPackages);
-in
-{
- options = {
- wrapped = mkOption {
- type = types.package;
- readOnly = true;
- description = "(Read-only) The final wrapped package";
- };
-
- overrideAttrs = mkOption {
- type = with types; functionTo attrs;
- description = ''
- Function to override attributes from the final package.
- '';
- default = lib.id;
- defaultText = lib.literalExpression "lib.id";
- example = lib.literalExpression ''
- old: {
- pname = "''${pname}-with-settings";
- }
- '';
- };
-
- postBuild = mkOption {
- type = types.str;
- default = "";
- description = "Raw commands to execute after the wrapping process has finished";
- example = ''
- echo "Running sanity check"
- $out/bin/nvim '+q'
- '';
- };
- };
-
- config = {
- wrapped =
- (
- (pkgs.symlinkJoin {
- # inherit (config.basePackage) name;
- pname = lib.getName config.basePackage;
- version = lib.getVersion config.basePackage;
- __intentionallyOverridingVersion = true;
- paths = [ config.basePackage ] ++ config.extraPackages;
- nativeBuildInputs = [
- pkgs.makeBinaryWrapper
- pkgs.makeWrapper
- ];
- passthru = (config.basePackage.passthru or { }) // {
- unwrapped = config.basePackage;
- };
- outputs = [
- "out"
- ] ++ (lib.optional hasMan "man");
- meta = (config.basePackage.meta or { }) // {
- outputsToInstall = [
- "out"
- ] ++ (lib.optional hasMan "man");
- };
- postBuild = ''
- pushd "$out/bin" > /dev/null
-
- echo "::: Wrapping explicit .programs ..."
- already_wrapped=()
- ${lib.concatMapStringsSep "\n" (
- program:
- let
- name = program.name;
- target = if program.target == null then "" else program.target;
- wrapProgram = if program.wrapperType == "shell" then "wrapProgramShell" else "wrapProgramBinary";
- makeWrapper = if program.wrapperType == "shell" then "makeShellWrapper" else "makeBinaryWrapper";
- in
- # bash
- ''
- already_wrapped+="${program.name}"
-
- # If target is empty, use makeWrapper
- # If target is not empty, but the same as name, use makeWrapper
- # If target is not empty, is different from name, and doesn't exist, use wrapProgram
- # If target is not empty, is different from name, and exists, error out
-
- cmd=()
- if [[ -z "${target}" ]]; then
- cmd=(${wrapProgram} "$out/bin/${name}")
- elif [[ -e "$out/bin/${name}" ]]; then
- echo ":: Error: Target '${name}' already exists"
- exit 1
- else
- cmd=(${makeWrapper} "$out/bin/${target}" '${name}')
- fi
-
- ${
- if program.wrapFlags == [ ] && program.extraWrapperFlags == "" then
- "echo ':: (${name} skipped: no wrapper configuration)'"
- else
- printAndRun "\${cmd[@]} ${lib.escapeShellArgs program.wrapFlags} ${program.extraWrapperFlags}"
- }
- ''
- ) (attrValues config.programs)}
-
- echo "::: Wrapping packages in out/bin ..."
-
- for file in "$out/bin/"*; do
- # check if $file is in $already_wrapped
- prog="$(basename "$file")"
- if [[ " ''${already_wrapped[@]} " =~ " $prog " ]]; then
- continue
- fi
-
- ${
- if config.wrapFlags == [ ] && config.extraWrapperFlags == "" then
- "echo \":: ($prog skipped: no wrapper configuration)\""
- else
- printAndRun (
- let
- wrapProgram = if config.wrapperType == "shell" then "wrapProgramShell" else "wrapProgramBinary";
- in
- ''${wrapProgram} "$file" ${lib.escapeShellArgs config.wrapFlags} ${config.extraWrapperFlags}''
- )
- }
- done
- popd > /dev/null
-
- ## Fix desktop files
-
- # Some derivations have nested symlinks here
- if [[ -d $out/share/applications && ! -w $out/share/applications ]]; then
- echo "Detected nested symlink, fixing"
- temp=$(mktemp -d)
- cp -v $out/share/applications/* $temp
- rm -vf $out/share/applications
- mkdir -pv $out/share/applications
- cp -v $temp/* $out/share/applications
- fi
-
- pushd "$out/bin" > /dev/null
- for exe in *; do
- # Fix .desktop files
- # This list of fixes might not be exhaustive
- for file in $out/share/applications/*; do
- trap "set +x" ERR
- set -x
- sed -i "s#/nix/store/.*/bin/$exe #$out/bin/$exe #" "$file"
- sed -i -E "s#Exec=$exe([[:space:]]*)#Exec=$out/bin/$exe\1#g" "$file"
- sed -i -E "s#TryExec=$exe([[:space:]]*)#TryExec=$out/bin/$exe\1#g" "$file"
- set +x
- done
- done
- popd > /dev/null
-
- ${lib.optionalString hasMan ''
- mkdir -p ''${!outputMan}
- ${lib.concatMapStringsSep "\n" (
- p:
- if p ? "man" then
- "${lib.getExe pkgs.xorg.lndir} -silent ${p.man} \${!outputMan}"
- else
- "echo \"No man output for ${lib.getName p}\""
- ) ([ config.basePackage ] ++ config.extraPackages)}
- ''}
-
- ${config.postBuild}
- '';
- }).overrideAttrs
- (
- final: prev: {
- name = "${final.pname}-${final.version}";
- }
- )
- ).overrideAttrs
- config.overrideAttrs;
- };
-}
diff --git a/modules/wrapper.nix b/modules/wrapper.nix
index a5d5852..bf0ec4c 100644
--- a/modules/wrapper.nix
+++ b/modules/wrapper.nix
@@ -1,72 +1,26 @@
-{ pkgs, lib, config, ... }:
+{ config, lib, ... }:
let
inherit (lib) mkOption types;
-
in
{
- imports = [
- ./common-wrapper.nix
- ./wrapper-impl.nix
- ];
-
options = {
- basePackage = mkOption {
- type = with types; package;
- description = "Program to be wrapped";
- };
-
- extraPackages = mkOption {
- type = with types; listOf package;
- default = [ ];
- description = "Optional extra packages to also wrap";
- };
-
- programs = mkOption {
- default = { };
- description = "Wrap specific binaries with specific options. You may use it to skip wrapping some program.";
- example = lib.literalExpression ''
- {
- supervim = {
- target = "neovim";
- };
-
- git = {
- env.GIT_CONFIG.value = ./gitconfig;
- };
-
- # Don't wrap scalar
- scalar = {};
+ overrideAttrs = mkOption {
+ type = with types; functionTo attrs;
+ default = lib.id;
+ defaultText = lib.literalExpression "lib.id";
+ description = "Function to override attributes from the final package.";
+ example = lib.literalExpression ''
+ oldAttrs: {
+ pname = "''${oldAttrs.pname}-with-settings";
}
'';
- type = types.attrsOf (
- types.submoduleWith {
- modules = [
- ./common-wrapper.nix
- (
- { name, ... }:
- {
- options = {
- name = mkOption {
- type = types.str;
- default = name;
- description = "Name of the program";
- };
-
- target = mkOption {
- type = with types; nullOr str;
- default = null;
- description = "Target of the program";
- };
- };
+ };
- config = {
- wrapperType = lib.mkDefault config.wrapperType;
- };
- }
- )
- ];
- }
- );
+ wrapped = mkOption {
+ type = types.package;
+ readOnly = true;
+ apply = x: x.overrideAttrs config.overrideAttrs;
+ description = "(Read-only) The final wrapped package.";
};
};
}
diff --git a/pkgs/mkWrapper/modules/args.nix b/pkgs/mkWrapper/modules/args.nix
new file mode 100644
index 0000000..000348e
--- /dev/null
+++ b/pkgs/mkWrapper/modules/args.nix
@@ -0,0 +1,82 @@
+{
+ config,
+ lib,
+ ...
+}:
+let
+ inherit (lib) mkOption types;
+in
+{
+ imports = [ ./common-args.nix ];
+
+ options = {
+ basePackage = mkOption {
+ type = types.package;
+ description = "Program to be wrapped.";
+ };
+
+ extraPackages = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ description = "Optional extra packages to also wrap.";
+ };
+
+ programs = mkOption {
+ type = types.attrsOf (
+ types.submoduleWith {
+ modules = [
+ ./common-args.nix
+ (
+ { name, ... }:
+ {
+ options = {
+ name = mkOption {
+ type = types.str;
+ default = name;
+ description = "Name of the program.";
+ };
+
+ target = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Target of the program.";
+ };
+ };
+
+ config = {
+ wrapperType = lib.mkDefault config.wrapperType;
+ };
+ }
+ )
+ ];
+ }
+ );
+ default = { };
+ description = "Wrap specific binaries with specific options. You may use it to skip wrapping some program.";
+ example = lib.literalExpression ''
+ {
+ supervim = {
+ target = "neovim";
+ };
+
+ git = {
+ envVars.GIT_CONFIG.value = ./gitconfig;
+ };
+
+ # Don't wrap scalar
+ scalar = { };
+ }
+ '';
+ };
+
+ postBuild = mkOption {
+ type = types.str;
+ default = "";
+ description = "Raw commands to execute after the wrapping process has finished.";
+ example = ''
+ echo "Running sanity check"
+ $out/bin/nvim '+q'
+ '';
+ };
+ };
+}
diff --git a/pkgs/mkWrapper/modules/common-args.nix b/pkgs/mkWrapper/modules/common-args.nix
new file mode 100644
index 0000000..4278a99
--- /dev/null
+++ b/pkgs/mkWrapper/modules/common-args.nix
@@ -0,0 +1,103 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+ strLike = with types; coercedTo anything (x: "${x}") str;
+in
+{
+ imports = [ (lib.mkRenamedOptionModule [ "env" ] [ "envVars" ]) ];
+
+ options = {
+ prependFlags = mkOption {
+ type = types.listOf strLike;
+ default = [ ];
+ description = "Flags passed before any arguments to the wrapped program.";
+ example = lib.literalExpression ''
+ [
+ "--config-file"
+ ./config.toml
+ ]
+ '';
+ };
+
+ appendFlags = mkOption {
+ type = types.listOf strLike;
+ default = [ ];
+ description = "Flags passed after any arguments to the wrapped program. Usually you want to use prependFlags instead.";
+ example = lib.literalExpression ''
+ [
+ "--config-file"
+ ./config.toml
+ ]
+ '';
+ };
+
+ pathAdd = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ description = "Packages to append to PATH.";
+ example = lib.literalExpression "[ pkgs.starship ]";
+ };
+
+ envVars = mkOption {
+ type = types.attrsOf (
+ types.submodule (
+ { name, ... }:
+ {
+ options = {
+ name = mkOption {
+ type = types.str;
+ default = name;
+ description = "Name of the variable.";
+ example = "GIT_CONFIG";
+ };
+
+ value = mkOption {
+ type = types.nullOr strLike;
+ default = null;
+ description = ''
+ Value of the variable to be set.
+ Set to `null` to unset the variable.
+
+ Note that any environment variable will be escaped. For example, `value = "$HOME"`
+ will be converted to the literal `$HOME`, with its dollar sign.
+ '';
+ example = lib.literalExpression "./gitconfig";
+ };
+
+ force = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the value should be always set to the specified value.
+ If set to `true`, the program will not inherit the value of the variable
+ if it's already present in the environment.
+ '';
+ example = true;
+ };
+ };
+ }
+ )
+ );
+ default = { };
+ description = "Structured configuration for environment variables.";
+ example = lib.literalExpression "{ GIT_CONFIG.value = ./gitconfig; }";
+ };
+
+ extraWrapperFlags = mkOption {
+ type = types.separatedString " ";
+ default = "";
+ description = "Raw flags passed to makeWrapper.";
+ example = "--argv0 foo --set BAR value";
+ };
+
+ wrapperType = mkOption {
+ type = types.enum [
+ "shell"
+ "binary"
+ ];
+ default = "binary";
+ description = "Whether to use a binary or a shell wrapper.";
+ example = "shell";
+ };
+ };
+}
diff --git a/pkgs/mkWrapper/modules/wrapped.nix b/pkgs/mkWrapper/modules/wrapped.nix
new file mode 100644
index 0000000..f2c6a27
--- /dev/null
+++ b/pkgs/mkWrapper/modules/wrapped.nix
@@ -0,0 +1,21 @@
+{ mkWrapper }:
+
+{ config, ... }:
+{
+ imports = [ ./args.nix ];
+
+ wrapped = mkWrapper {
+ inherit (config)
+ basePackage
+ extraPackages
+ programs
+ prependFlags
+ appendFlags
+ pathAdd
+ envVars
+ extraWrapperFlags
+ wrapperType
+ postBuild
+ ;
+ };
+}
diff --git a/pkgs/mkWrapper/package.nix b/pkgs/mkWrapper/package.nix
new file mode 100644
index 0000000..923ceab
--- /dev/null
+++ b/pkgs/mkWrapper/package.nix
@@ -0,0 +1,252 @@
+{
+ lib,
+ stdenvNoCC,
+ lndir,
+ makeWrapper,
+ makeBinaryWrapper,
+ mkWrapper,
+}:
+{
+ modules.wrapped = lib.modules.importApply ./modules/wrapped.nix { inherit mkWrapper; };
+ __functor =
+ _:
+ lib.extendMkDerivation {
+ constructDrv = stdenvNoCC.mkDerivation;
+ inheritFunctionArgs = false;
+ extendDrvArgs =
+ finalAttrs:
+ {
+ basePackage,
+ extraPackages ? [ ],
+ programs ? { },
+ prependFlags ? [ ],
+ appendFlags ? [ ],
+ pathAdd ? [ ],
+ envVars ? { },
+ extraWrapperFlags ? "",
+ wrapperType ? "binary",
+ postBuild ? "",
+ ...
+ }:
+ let
+ printAndRun = cmd: ''
+ echo ":: ${cmd}"
+ eval "${cmd}"
+ '';
+ genFlags =
+ {
+ prependFlags ? [ ],
+ appendFlags ? [ ],
+ pathAdd ? [ ],
+ envVars ? { },
+ }:
+ lib.concatLists [
+ (lib.flatten (
+ map (flag: [
+ "--add-flag"
+ flag
+ ]) prependFlags
+ ))
+ (lib.flatten (
+ map (flag: [
+ "--append-flag"
+ flag
+ ]) appendFlags
+ ))
+ (lib.optionals (pathAdd != [ ]) [
+ "--prefix"
+ "PATH"
+ ":"
+ (lib.makeBinPath pathAdd)
+ ])
+ (lib.flatten (
+ map (env: [
+ (
+ if env.value == null then
+ "--unset"
+ else if env.force then
+ "--set"
+ else
+ "--set-default"
+ )
+ env.name
+ env.value
+ ]) (lib.attrValues envVars)
+ ))
+ ];
+ finalArgs =
+ (lib.evalModules {
+ modules = [
+ ./modules/args.nix
+ (lib.filterAttrs (
+ name: _:
+ lib.elem name [
+ "basePackage"
+ "extraPackages"
+ "programs"
+ "prependFlags"
+ "appendFlags"
+ "pathAdd"
+ "envVars"
+ "extraWrapperFlags"
+ "wrapperType"
+ "postBuild"
+ ]
+ ) finalAttrs)
+ ];
+ }).config;
+ hasMan = lib.any (lib.hasAttr "man") ([ finalArgs.basePackage ] ++ finalArgs.extraPackages);
+ in
+ {
+ __structuredAttrs = true;
+ name = "${finalAttrs.pname}-${finalAttrs.version}";
+ pname = lib.getName finalArgs.basePackage;
+ version = lib.getVersion finalArgs.basePackage;
+ __intentionallyOverridingVersion = true;
+ nativeBuildInputs = [
+ lndir
+ makeWrapper
+ makeBinaryWrapper
+ ];
+ passthru = (finalArgs.basePackage.passthru or { }) // {
+ unwrapped = finalArgs.basePackage;
+ };
+ outputs = [
+ "out"
+ ]
+ ++ (lib.optional hasMan "man");
+ meta = (finalArgs.basePackage.meta or { }) // {
+ outputsToInstall = [
+ "out"
+ ]
+ ++ (lib.optional hasMan "man");
+ };
+ buildCommand = ''
+ mkdir -p $out
+ for i in ${lib.escapeShellArgs ([ finalArgs.basePackage ] ++ finalArgs.extraPackages)}; do
+ if test -d $i; then lndir -silent $i $out; fi
+ done
+
+ pushd "$out/bin" > /dev/null
+
+ echo "::: Wrapping explicit .programs ..."
+ already_wrapped=()
+ ${lib.concatMapStringsSep "\n" (
+ program:
+ let
+ name = program.name;
+ target = if program.target == null then "" else program.target;
+ wrapProgram = if program.wrapperType == "shell" then "wrapProgramShell" else "wrapProgramBinary";
+ makeWrapper = if program.wrapperType == "shell" then "makeShellWrapper" else "makeBinaryWrapper";
+ flags = genFlags {
+ inherit (program)
+ prependFlags
+ appendFlags
+ pathAdd
+ envVars
+ ;
+ };
+ in
+ ''
+ already_wrapped+="${program.name}"
+
+ # If target is empty, use makeWrapper
+ # If target is not empty, but the same as name, use makeWrapper
+ # If target is not empty, is different from name, and doesn't exist, use wrapProgram
+ # If target is not empty, is different from name, and exists, error out
+
+ cmd=()
+ if [[ -z "${target}" ]]; then
+ cmd=(${wrapProgram} "$out/bin/${name}")
+ elif [[ -e "$out/bin/${name}" ]]; then
+ echo ":: Error: Target '${name}' already exists"
+ exit 1
+ else
+ cmd=(${makeWrapper} "$out/bin/${target}" '${name}')
+ fi
+
+ ${
+ if flags == [ ] && program.extraWrapperFlags == "" then
+ "echo ':: (${name} skipped: no wrapper configuration)'"
+ else
+ printAndRun "\${cmd[@]} ${lib.escapeShellArgs flags} ${program.extraWrapperFlags}"
+ }
+ ''
+ ) (lib.attrValues finalArgs.programs)}
+
+ echo "::: Wrapping packages in out/bin ..."
+
+ for file in "$out/bin/"*; do
+ # check if $file is in $already_wrapped
+ prog="$(basename "$file")"
+ if [[ " ''${already_wrapped[@]} " =~ " $prog " ]]; then
+ continue
+ fi
+
+ ${
+ let
+ flags = genFlags {
+ inherit (finalArgs)
+ prependFlags
+ appendFlags
+ pathAdd
+ envVars
+ ;
+ };
+ in
+ if flags == [ ] && finalArgs.extraWrapperFlags == "" then
+ "echo \":: ($prog skipped: no wrapper configuration)\""
+ else
+ printAndRun (
+ let
+ wrapProgram = if finalArgs.wrapperType == "shell" then "wrapProgramShell" else "wrapProgramBinary";
+ in
+ ''${wrapProgram} "$file" ${lib.escapeShellArgs flags} ${finalArgs.extraWrapperFlags}''
+ )
+ }
+ done
+ popd > /dev/null
+
+ ## Fix desktop files
+
+ # Some derivations have nested symlinks here
+ if [[ -d $out/share/applications && ! -w $out/share/applications ]]; then
+ echo "Detected nested symlink, fixing"
+ temp=$(mktemp -d)
+ cp -v $out/share/applications/* $temp
+ rm -vf $out/share/applications
+ mkdir -pv $out/share/applications
+ cp -v $temp/* $out/share/applications
+ fi
+
+ pushd "$out/bin" > /dev/null
+ for exe in *; do
+ # Fix .desktop files
+ # This list of fixes might not be exhaustive
+ for file in $out/share/applications/*; do
+ trap "set +x" ERR
+ set -x
+ sed -i "s#/nix/store/.*/bin/$exe #$out/bin/$exe #" "$file"
+ sed -i -E "s#Exec=$exe([[:space:]]*)#Exec=$out/bin/$exe\1#g" "$file"
+ sed -i -E "s#TryExec=$exe([[:space:]]*)#TryExec=$out/bin/$exe\1#g" "$file"
+ set +x
+ done
+ done
+ popd > /dev/null
+
+ ${lib.optionalString hasMan ''
+ mkdir -p ''${!outputMan}
+ ${lib.concatMapStringsSep "\n" (
+ package:
+ if package ? "man" then
+ "lndir -silent ${package.man} \${!outputMan}"
+ else
+ "echo \"No man output for ${lib.getName package}\""
+ ) ([ finalArgs.basePackage ] ++ finalArgs.extraPackages)}
+ ''}
+
+ ${finalArgs.postBuild}
+ '';
+ };
+ };
+}