Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrappers.<name>.programs attrset #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/content/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<name>.target`

- 2023-11-13
- Added `prependFlags`, which maps to `--add-flags`
- Added `appendFlags`, which maps to `--append-flags`
Expand Down
240 changes: 150 additions & 90 deletions modules/base.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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"])} = <value>;
''
else (actualType.merge loc defs);
};
in
types.attrsOf forgedType;
type = with types; attrsOf (submodule ./env-type.nix);
description = lib.mdDoc ''
Structured environment variables.
'';
Expand Down Expand Up @@ -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;
Expand All @@ -135,6 +151,14 @@
};

config = {
programs = let
renamesOpt = lib.showOption ["wrappers" name "renames"];
suggestedOpt = lib.showOption ["wrappers" name "programs" "<name>" "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];
Expand All @@ -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"
Expand All @@ -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
Expand Down
39 changes: 39 additions & 0 deletions modules/program-type.nix
Original file line number Diff line number Diff line change
@@ -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
;
};
}
8 changes: 4 additions & 4 deletions tests/test-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
env.FOO.value = "foo";
env.BAR.value = "bar";
basePackage = pkgs.hello;
flags = [
appendFlags = [
"-g"
some-special-arg
];
Expand Down Expand Up @@ -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"
];
};
Expand Down
Loading