From 9e685d604a7a5d2586e4e9a9212b456fa25bbdaf Mon Sep 17 00:00:00 2001 From: command_block Date: Sun, 27 Jul 2025 14:20:13 +0800 Subject: [PATCH 1/2] Full rewrite --- default.nix | 23 ++- docs/package.nix | 2 +- docs/readme.md | 5 + modules/common-wrapper.nix | 120 ------------ modules/default.nix | 124 ++++++++++++ modules/env-type.nix | 98 ---------- modules/many-wrappers.nix | 80 -------- modules/wrapper-impl.nix | 188 ------------------ modules/wrapper.nix | 76 ++------ pkgs/mkWrapper/modules/args.nix | 82 ++++++++ pkgs/mkWrapper/modules/common-args.nix | 103 ++++++++++ pkgs/mkWrapper/modules/wrapped.nix | 21 +++ pkgs/mkWrapper/package.nix | 252 +++++++++++++++++++++++++ 13 files changed, 618 insertions(+), 556 deletions(-) delete mode 100644 modules/common-wrapper.nix create mode 100644 modules/default.nix delete mode 100644 modules/env-type.nix delete mode 100644 modules/many-wrappers.nix delete mode 100644 modules/wrapper-impl.nix create mode 100644 pkgs/mkWrapper/modules/args.nix create mode 100644 pkgs/mkWrapper/modules/common-args.nix create mode 100644 pkgs/mkWrapper/modules/wrapped.nix create mode 100644 pkgs/mkWrapper/package.nix 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/package.nix b/docs/package.nix index 1a1eef2..017d9af 100644 --- a/docs/package.nix +++ b/docs/package.nix @@ -25,7 +25,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..0e8d729 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,124 @@ +{ + 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."; + 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} + ''; + }; + }; +} From df3c03fcaa078c76f417099007f2ea6057d801fa Mon Sep 17 00:00:00 2001 From: command_block Date: Sat, 20 Sep 2025 20:46:18 +0800 Subject: [PATCH 2/2] Docs for modular modules --- docs/api.md | 6 +++++- docs/modular-modules.nix | 40 ++++++++++++++++++++++++++++++++++++++++ docs/package.nix | 17 +++++++++++++---- modules/default.nix | 5 ++++- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 docs/modular-modules.nix 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 017d9af..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 { diff --git a/modules/default.nix b/modules/default.nix index 0e8d729..ea75830 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -77,7 +77,10 @@ in ./wrapper.nix ]; }); - description = "Wrappers to create."; + description = '' + Wrappers to create. + + Also a collection of [modular modules](#modular-modules) that are configured as wrappers.''; example = lib.literalExpression '' { hello = {