diff --git a/doc/content/_index.md b/doc/content/_index.md index 6c7c625..546f0be 100644 --- a/doc/content/_index.md +++ b/doc/content/_index.md @@ -203,6 +203,11 @@ https://github.com/viperML/wrapper-manager/issues ## Changelog +- TBA + - Added `programs` attrset which allows to wrap specific programs + - Added `wrapByDefault` which can be set to `false` to disable the previous behavior + - Deprecated `renames` instead of setting `programs..target` + - 2023-11-13 - Added `prependFlags`, which maps to `--add-flags` - Added `appendFlags`, which maps to `--append-flags` diff --git a/modules/base.nix b/modules/base.nix index c9d501f..08be3ab 100644 --- a/modules/base.nix +++ b/modules/base.nix @@ -9,53 +9,10 @@ types ; - wrapperOpts = {config, ...}: { - imports = [ - (lib.mkAliasOptionModuleMD ["flags"] ["prependFlags"]) - ]; - + commonOpts = { options = { - basePackage = mkOption { - type = with types; package; - description = lib.mdDoc '' - Program to be wrapped. - ''; - example = lib.literalExpression "pkgs.nix"; - }; - - extraPackages = mkOption { - type = with types; listOf package; - description = lib.mdDoc '' - Extra packages to also wrap. - ''; - example = lib.literalExpression "[ pkgs.git-extras pkgs.delta ]"; - default = []; - }; - env = mkOption { - # This is a hack to display a helpful error message to the user about the changed api. - # Should be changed to just `attrsOf submodule` at some point. - type = let - inherit (lib) any isStringLike showOption; - actualType = types.submodule ./env-type.nix; - forgedType = - actualType - // { - # There's special handling if this value is present which makes merging treat this type as any other submodule type, - # so we lie about there being no sub-modules so that our `check` and `merge` get called. - getSubModules = null; - check = v: isStringLike v || actualType.check v; - merge = loc: defs: - if any (def: isStringLike def.value) defs - then - throw '' - ${showOption loc} has been changed to an attribute set. - Instead of assigning value directly, use ${showOption (loc ++ ["value"])} = ; - '' - else (actualType.merge loc defs); - }; - in - types.attrsOf forgedType; + type = with types; attrsOf (submodule ./env-type.nix); description = lib.mdDoc '' Structured environment variables. ''; @@ -112,6 +69,65 @@ default = ""; example = "--argv0 foo --set BAR value"; }; + }; + }; + + wrapperOpts = { + config, + name, + ... + }: { + imports = [ + commonOpts + (lib.mkAliasOptionModuleMD ["flags"] ["prependFlags"]) + ]; + + options = { + basePackage = mkOption { + type = with types; package; + description = lib.mdDoc '' + Program to be wrapped. + ''; + example = lib.literalExpression "pkgs.nix"; + }; + + extraPackages = mkOption { + type = with types; listOf package; + description = lib.mdDoc '' + Extra packages to also wrap. + ''; + example = lib.literalExpression "[ pkgs.git-extras pkgs.delta ]"; + default = []; + }; + + wrapByDefault = mkOption { + type = with types; bool; + description = lib.mdDoc '' + Whether to wrap all programs under bin/ by default. + ''; + example = false; + default = true; + }; + + programs = mkOption { + type = with types; + attrsOf (submoduleWith { + shorthandOnlyDefinesConfig = true; + modules = [commonOpts ./program-type.nix]; + specialArgs = { + defaults = config; + }; + }); + description = lib.mdDoc '' + Programs to wrap. + ''; + example = { + fish = { + prependFlags = ["-C" "echo Hello, fish"]; + }; + }; + default = {}; + }; wrapped = mkOption { type = with types; package; @@ -135,6 +151,14 @@ }; config = { + programs = let + renamesOpt = lib.showOption ["wrappers" name "renames"]; + suggestedOpt = lib.showOption ["wrappers" name "programs" "" "target"]; + in + lib.warnIf + (config.renames != {}) + "${renamesOpt} is deprecated. Set ${suggestedOpt} instead" + (lib.mapAttrs (_: target: {inherit target;}) config.renames); wrapped = let envToWrapperArg = name: config: let optionStr = attr: lib.showOption ["env" name attr]; @@ -155,27 +179,87 @@ if config.value == null then unsetArg else setArg; + wrapProgramStr = { + name, + target, + env, + prependFlags, + appendFlags, + pathAdd, + extraWrapperFlags, + ... + }: let + envArgs = lib.mapAttrsToList envToWrapperArg env; + # Yes, the arguments are escaped later, yes, this is intended to "double escape", + # so that they are escaped for wrapProgram and for the final binary too. + prependFlagArgs = map (args: ["--add-flags" (lib.escapeShellArg args)]) prependFlags; + appendFlagArgs = map (args: ["--append-flags" (lib.escapeShellArg args)]) appendFlags; + pathArgs = map (p: ["--prefix" "PATH" ":" "${p}/bin"]) pathAdd; + allArgs = lib.flatten (envArgs ++ prependFlagArgs ++ appendFlagArgs ++ pathArgs); + renameStr = lib.optionalString (name != target) '' + mv -vf ${name} ${lib.escapeShellArg target} + ''; + in '' + echo "Wrapping ${name}" + wrapProgram \ + "$out/bin/${name}" \ + ${lib.escapeShellArgs allArgs} \ + ${extraWrapperFlags} + + ${renameStr} + exe="${name}" + newexe="${target}" + + # Fix .desktop files + # This list of fixes might not be exhaustive + for file in $out/share/applications/*; do + echo "Fixing file=$file for exe=$exe" + set -x + trap "set +x" ERR + sed -i "s#/nix/store/.*/bin/$exe #$out/bin/$newexe #" "$file" + sed -i -E "s#Exec=$exe([[:space:]]*)#Exec=$out/bin/$newexe\1#g" "$file" + sed -i -E "s#TryExec=$exe([[:space:]]*)#TryExec=$out/bin/$newexe\1#g" "$file" + set +x + done + ''; + defaultWrappers = let + moveOutPrograms = + lib.optionalString + (config.programs != {}) + '' + echo Moving explicitly wrapped programs + mv -vf ${lib.escapeShellArgs (lib.mapAttrsToList (_: p: p.name) config.programs)} $wrapped_temp_dir + ''; + moveBackPrograms = + lib.optionalString + (config.programs != {}) + '' + echo Restoring explicitly wrapped programs + mv -vf $wrapped_temp_dir/* ./ + ''; + in + lib.optionalString config.wrapByDefault '' + wrapped_temp_dir=$(mktemp -d) + ${moveOutPrograms} + for file in *; do + ${wrapProgramStr (config + // { + name = "$file"; + target = "$file"; + })} + done + ${moveBackPrograms} + ''; + explicitWrappers = + lib.concatMapStringsSep + "\n" + wrapProgramStr + (lib.attrValues config.programs); result = pkgs.symlinkJoin ({ paths = [config.basePackage] ++ config.extraPackages; nativeBuildInputs = [pkgs.makeWrapper]; - postBuild = let - envArgs = lib.mapAttrsToList envToWrapperArg config.env; - # Yes, the arguments are escaped later, yes, this is intended to "double escape", - # so that they are escaped for wrapProgram and for the final binary too. - prependFlagArgs = map (args: ["--add-flags" (lib.escapeShellArg args)]) config.prependFlags; - appendFlagArgs = map (args: ["--append-flags" (lib.escapeShellArg args)]) config.appendFlags; - pathArgs = map (p: ["--prefix" "PATH" ":" "${p}/bin"]) config.pathAdd; - allArgs = lib.flatten (envArgs ++ prependFlagArgs ++ appendFlagArgs ++ pathArgs); - in '' - for file in $out/bin/*; do - echo "Wrapping $file" - wrapProgram \ - $file \ - ${lib.escapeShellArgs allArgs} \ - ${config.extraWrapperFlags} - done - + postBuild = '' # Some derivations have nested symlinks here if [[ -d $out/share/applications && ! -w $out/share/applications ]]; then echo "Detected nested symlink, fixing" @@ -186,34 +270,10 @@ cp -v $temp/* $out/share/applications fi - cd $out/bin - for exe in *; do - - if false; then - exit 2 - ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: '' - elif [[ $exe == ${lib.escapeShellArg name} ]]; then - newexe=${lib.escapeShellArg value} - mv -vf $exe $newexe - '') - config.renames)} - else - newexe=$exe - fi - - # Fix .desktop files - # This list of fixes might not be exhaustive - for file in $out/share/applications/*; do - echo "Fixing file=$file for exe=$exe" - set -x - trap "set +x" ERR - sed -i "s#/nix/store/.*/bin/$exe #$out/bin/$newexe #" "$file" - sed -i -E "s#Exec=$exe([[:space:]]*)#Exec=$out/bin/$newexe\1#g" "$file" - sed -i -E "s#TryExec=$exe([[:space:]]*)#TryExec=$out/bin/$newexe\1#g" "$file" - set +x - done - done - + pushd $out/bin + ${defaultWrappers} + ${explicitWrappers} + popd # I don't know of a better way to create a multe-output derivation for symlinkJoin # So if the packages have man, just link them into $out diff --git a/modules/program-type.nix b/modules/program-type.nix new file mode 100644 index 0000000..25963f5 --- /dev/null +++ b/modules/program-type.nix @@ -0,0 +1,39 @@ +{ + config, + lib, + name, + defaults, + ... +}: let + inherit (lib) mkOption types mdDoc literalMD; +in { + options = { + name = mkOption { + type = types.str; + description = mdDoc '' + Name of the program. + ''; + default = name; + }; + + target = mkOption { + type = types.str; + description = mdDoc '' + The final name of the program after wrapping. + ''; + default = config.name; + defaultText = literalMD "value of name"; + }; + }; + + config = { + inherit + (defaults) + env + prependFlags + appendFlags + pathAdd + extraWrapperFlags + ; + }; +} diff --git a/tests/test-module.nix b/tests/test-module.nix index a379f0e..0f9a403 100644 --- a/tests/test-module.nix +++ b/tests/test-module.nix @@ -8,7 +8,7 @@ env.FOO.value = "foo"; env.BAR.value = "bar"; basePackage = pkgs.hello; - flags = [ + appendFlags = [ "-g" some-special-arg ]; @@ -45,14 +45,14 @@ wrappers.neovim = { basePackage = pkgs.neovim; - renames = { - "nvim" = "nvim2"; + programs.nvim = { + target = "nvim2"; }; }; wrappers.discord = { basePackage = pkgs.discord; - flags = [ + programs.discord.prependFlags = [ "--disable-gpu" ]; };