User guide

While the Project overview should be enough to get you started, this document contain all of the information you may need to make full use of wrapper-manager.

Prerequisites

While skimming through this guide, this document works with the following assumptions:

  • Familiarity with the module system and its conventions.

  • Knowledgeable in some parts of nixpkgs including its standard library and builders as makeWrapper and several trivial builders.

  • Familiarity with parts of Nix ecosystem outside of nixpkgs and NixOS. In particular, home-manager.

  • Love for spelunking source code just to understand a concept within wrapper-manager-fds. ;p [1]

Anyways, this document won’t introduce any tutorial for any of the above. I recommend to take a gander at the given links to get a sense of them.

What is wrapper-manager?

Simply put, this is a declarative interface built on top of makeWrapper and company plus some other integrations as we’ll explore in this document. Under the hood, the declarative interface is implemented with the same module system as NixOS' configuration system which is used by other Nix environments such as home-manager. Once you’re familiar with NixOS, you’re halfway there in knowing wrapper-manager, the difference being the different options and all.

It is comparable to NixOS and home-manager in a way that it compiles into an operating system and a home environment respectively, wrapper-manager compiles the module environment into a package. Speaking of which, wrapper-manager is meant to be composed in larger-scoped environments such as NixOS and home-manager, mainly by including wrapper-manager packages in environment.systemPackages and home.packages but you could also make them as a standalone package.

Why use this anyways?

  • Possibility to create multiple variants of the same program. Most of the time, the program may have ways to manage their configuration in limiting ways (e.g., only allowing one program configuration at a time stored in an arbitrary location). wrapper-manager allows you to work around those limitations.

  • Packaging those configurations and variants in a standalone manner. wrapper-manager having an output of a package made it possible to create standalone versions of program variants and various files to be composed in an environment such as a devshell, in a NixOS or home-manager system, and the like.

  • Integration with the rest of the NixOS (and home-manager) ecosystem. This project was created with integrating with those environments in mind with ergonomic ways to create various things such as XDG desktop entries and systemd units.

Getting started

But first, we’ll have to have wrapper-manager in our configuration. There are multiple ways to do so as already shown from the Installation so we’ll go ahead with what should you do after it. There are multiple ways to build a wrapper-manager package:

  • You can manually build one with wrapperManagerLib.env.build. It needs a nixpkgs instance (pkgs) and a list of additional modules (modules) with an optional attrset of static of module arguments (specialArgs).

    Here’s an example of using the function.

    { pkgs ? import <nixpkgs> { } }:
    
    let
      wrapper-manager = import <wrapper-manager-fds> { };
      wrapperManagerLib = import wrapper-manager.wrapperManagerLib { inherit pkgs; };
    in
      wrapperManagerLib.env.build {
        inherit pkgs;
        modules = [ ./config/wrapper.nix ];
        specialArgs = { hello = "WORLD"; };
      }
  • There is also wrapperManagerLib.env.eval which is just evaluating a wrapper-manager package and nothing else. [2] Useful for creating your own integrations in other module environments. It should accept the same arguments as the build function.

  • You can take advantage of the integration modules for home-manager and NixOS and it automatically adds them into their respective package list (i.e., home.packages and environment.systemPackages). They are mainly configured through wrapper-manager.packages which is an attribute set representing a set of package. For more details, you can see With NixOS and home-manager.

Using wrapper-manager

The module environment of wrapper-manager is the main interface of the project. In the following code, we’ll define two wrappers around github:yt-dlp/yt-dlp[opts=repo].

A package containing two wrapper scripts for yt-dlp
{ config, lib, pkgs, ... }:

{
  wrappers.yt-dlp-audio = {
    arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
    prependArgs = [
      "--no-overwrite"
      "--extract-audio"
      "--format" "bestaudio"
      "--audio-format" "opus"
      "--output" "'%(album_artists.0,artists.0)s/%(album,playlist)s/%(track_number,playlist_index)d-%(track,title)s.%(ext)s'"
      "--download-archive" "archive"
      "--embed-thumbnail"
      "--add-metadata"
    ];
  };

  # You could also lessen the code above by passing `--config-location` to
  # yt-dlp and move them into a separate file. This is what wrapper-manager is
  # made for, after all.
  wrappers.yt-dlp-video = {
    arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
    prependArgs = [
      "--config-location" (builtins.toString pkgs.emptyFile)
    ];
  };

}

If we build the configuration, it should result in a derivation containing two executables.

$ ls ./result/bin
yt-dlp-audio  yt-dlp-video

By default, these wrappers are compiled with makeBinaryWrapper. You could make into a shell-based wrapper by changing build.variant value into shell.

If you want to include the original yt-dlp package as part of the standalone package, just pass the package as part of basePackages.

By evaluating the following code, you’ll be losing metadata for the yt-dlp package (e.g., version, meta, src) since it is made with buildEnv builder.

For this case, wrapper-manager has a way to make overridden packages by passing basePackages with a bare package instead of a list of packages (e.g., basePackages = pkgs.yt-dlp; instead of basePackages = [ pkgs.yt-dlp ];). This method makes it suitable to pass programs.<name>.package module options typically found from NixOS and home-manager but at the cost of a rebuild and may interfere with the build steps already defined from its package authors.

{ lib, pkgs, ... }:

{
  # ...

  basePackages = [ pkgs.yt-dlp ];
}

Another thing to keep in mind is wrapper-manager packages have the library set available as wrapperManagerLib module argument. This is mainly useful for setting values within the configuration.

Some uses of the wrapper-manager library set
{ config, lib, pkgs, wrapperManagerLib, ... }:

{
  # It is used for setting values in certain modules options.
  wrappers.yt-dlp-video = {
    xdg.dataDirs = wrapperManagerLib.getXdgDataDirs [
      pkgs.emacs
      pkgs.neovim
    ];

    pathAdd = wrapperManagerLib.getBin (with pkgs; [
      yt-dlp
      gallery-dl
    ]);
  };

  # Another nicety is to create a wraparound wrapper like in the following code
  # where we wrap tmux to be used with boxxy.
  wrappers.tmux = wrapperManagerLib.makeWraparound {
    arg0 = lib.getExe' pkgs.tmux "tmux";
    under = lib.getExe' pkgs.boxxy "boxxy";
    underFlags = [ "--rule" "~/.tmux.conf:~/.config/tmux/tmux.conf" ];
    underSeparator = "--";
  };
}

One of the typical thing to set in a wrapper script is the environment variables. You could set them from environment.variables to set it for all of the wrappers. For wrapper-specific values, just go for wrappers.<name>.env.

{ config, lib, pkgs, wrapperManagerLib, ... }: {
  # Set a envvar and its value.
  environment.variables.LOG_STYLE.value = "systemd";

  # By default, the values are forcibly set. You could set as the default value
  # if unset by setting the action to `set-default`.
  environment.variables.LOG_STYLE.action = "set-default";

  # Unset an environment variable. Its value will be ignored.
  environment.variables.MODS_DIR.action = "unset";

  # Set a list of separator-delimited values, typically for PATH,
  # XDG_CONFIG_DIRS, XDG_DATA_DIRS, and the like.
  environment.variables.PATH = {
    action = "prefix";
    separator = ":";
    value = wrapperManagerLib.getBin (with pkgs; [
      yt-dlp
      neofetch
    ]);
  };

  # For wrapper-specific values, it has the same interface, just different attribute.
  wrappers.name.env.LOG_STYLE.value = "systemd";
}

XDG integration

This environment comes with various features for XDG desktop integrations. These does not necessarily implements the feature itself but rather creates the files typically recognized with the wider-scoped list of packages (e.g., home.packages for home-manager, environment.systemPackages for NixOS).

As one of those features, you can create XDG desktop entries to be exported to $out/share/applications/$NAME.desktop in the output path. This uses the makeDesktopItem builder from nixpkgs so the settings should be the same with those. Here’s an example of creating a wrapper-manager package with a sole desktop entry for Firefox with the additional configuration to be opened within GNOME Shell.

{ config, lib, pkgs, ... }: {
  xdg.desktopEntries.firefox = {
    desktopName = "Firefox";
    genericName = "Web browser";
    exec = "firefox %u";
    terminal = false;
    categories = [ "Application" "Network" "WebBrowser" ];
    mimeTypes = [ "text/html" "text/xml" ];
    extraConfig."X-GNOME-Autostart-Phase" = "WindowManager";
    keywords = [ "Web" "Browser" ];
    startupNotify = false;
    startupWMClass = "MyOwnClass";
  };
}

You could also automatically create a desktop entry for one of your wrappers by setting wrappers.<name>.xdg.desktopEntry.enable to true and configuring the entry with wrappers.<name>.xdg.desktopEntry.settings. It simply sets some of those settings automatically for you such as the Name=, DesktopName=, and Exec= but you’ll have to set the rest of it yourself for full control what’s in there.

{ lib, pkgs, ... }: {
  wrappers.nvim = {
    arg0 = lib.getExe' pkgs.neovim "nvim";
    xdg.desktopEntry = {
      enable = true;
      settings = {
        terminal = true;
        extraConfig."X-GNOME-Autostart-Phase" = "WindowManager";
        keywords = [ "Text editor" ];
        startupNotify = false;
        startupWMClass = "MyOwnClass";
      };
    };
  };
}

Another XDG-related feature for wrapper-manager is adding paths to a couple of XDG search paths including for XDG_CONFIG_DIRS and XDG_DATA_DIRS. You can either add them for all wrappers or set them per-wrapper.

{ config, lib, pkgs, wrapperManagerLib, ... }: let
  inherit (wrapperManagerLib) getXdgDataDirs getXdgConfigDirs;
  searchPaths = with pkgs; [ yt-dlp neofetch ];
in {
  xdg.configDirs = getXdgConfigDirs searchPaths;
  xdg.dataDirs = getXdgDataDirs searchPaths;

  wrappers.nvim.xdg.configDirs = getXdgConfigDirs searchPaths;
  wrappers.emacs.xdg.dataDirs = getXdgDataDirs searchPaths;
}

Generating files

You can generate files with any content in any location within the derivation through files namespace. If you’re familiar with NixOS and home-manager, you have similar option to set those files through environment.etc.<name> and home.file.<name>, respectively.

Setting files in a wrapper-manager configuraiton alongside a Neofetch wrapper
{
  config,
  lib,
  pkgs,
  ...
}:

{
  wrappers.neofetch = {
    arg0 = lib.getExe' pkgs.neofetch "neofetch";
    appendArgs = [
      "--ascii_distro"
      "guix"
      "--title_fqdn"
      "off"
      "--os_arch"
      "off"
    ];
  };

  # Testing out a simple file.
  files."share/nix/hello".text = ''
    WHOA THERE!
  '';

  # A file target with an "absolute" path.
  files."/absolute/path".text = ''
    WHAAAAAAAT!
  '';

  # Testing out source.
  files."share/nix/aloha".source = config.files."share/nix/hello".source;

  # Testing out an executable file.
  files."share/nix/example" = {
    text = "WHOA";
    mode = "0755";
  };

  # Testing out a directory.
  files."share/whoa".source = pkgs.writeTextDir "/what/is/this.txt" ''
    WHAT
  '';

}

One of the more common use case is generating files with a common data format such as JSON, YAML, or TOML. wrapper-manager-fds facilitates this through dataFormats namespace taking advantage of Nix-representable data formats already existing within Nix ecosystem. This would also allow you to set them modularly, taking full advantage of nixpkgs' module system with its merging.

Setting various configurations with dataFormats
{ config, lib, pkgs, ... }:

{
  dataFormats.files."/etc/app/config.json" = {
    variant = "json";
    content = {
      words.allowlist = [
        "Why,"
        "Hello"
        "There"
      ];

      words.denylist = [
        "What"
        "WUUUUUUUUUUUUT"
        "WHHHUUUUUUUUUUUUUUUUUUUUUUUT"
      ];
    };
  };

  dataFormats.files."/etc/com.example.SampleApp/config" = {
    variant = "toml";

    # This is just to test if it's possible to merge them (AKA define them in
    # two different modules).
    content = lib.mkMerge [
      {
        health = 1.0000;
        battle.skills = [
          "Fireball"
          "Pocket sand"
        ];
      }

      {
        battle.skills = lib.mkBefore [
          "Mach 13 Elephant Explosion"
          "Galaxy Impact"
        ];
      }
    ];
  };

  dataFormats.files."share/org.example.SampleApp/config.ini" = {
    variant = "ini";
    content = lib.mkMerge [
      {
        "Desktop Thingies" = {
          MouseSupport = true;
          ControllerSupport = true;
        };

        "Temper Issues" = {
          DontHaveManagementLessons = true;
          MaximumHeatTolerance = 90;
          MinimumHeatTolerance = 35;
        };
      }

      {
        "Desktop Thingies" = {
          MouseSupport = lib.mkForce false;
          ControllerSupport = lib.mkDefault false;
        };
      }
    ];
  };

}

Several of the common data formats are included set with dataFormats.enableCommonFormats. There are also additional data formats within the baseline wrapper-manager configuration such as systemd INI format to generate your custom systemd units and configurations but as shown in Generating systemd units, there is a more ergonomic way to generate them. You can unlock them by setting dataFormats.enableExtraFormats to true. Of course, you could also set your own data format if you wish by creating a Nix-representable data and setting dataFormats.formats.<name> to it.

{ pkgs, lib, ... }:

{
  dataFormats.formats.lua = pkgs.formats.lua { };
  dataFormats.formats.djangoVars = pkgs.formats.pythonVars { };
  dataFormats.formats.xml = pkgs.formats.xml { };
}

Generating systemd units

wrapper-manager-fds has a large module set for generating systemd units at programs.systemd namespace. This is useful for integrating this into module environments such as NixOS.

A lightly comprehensive example of systemd units
{ config, lib, pkgs, ... }:

{
  programs.systemd.system.services = {
    hello = {
      description = "wrapper-manager systemd service example";
      path = with pkgs; [ hello ];
      script = ''
        hello --help
      '';
      startAt = "weekly";
    };

    there = {
      enableStatelessInstallation = true;
      aliases = [ "whut-whut.service" ];
      wantedBy = [ "graphical.target" ];
      requiredBy = [ "multi-user.target" ];
      upheldBy = [ "default.target" "basic.target" ];
      description = "EEEEEEEEeeeeeeehhh.....";
    };

    # A duplicate of `there.service` with the same installation configuration
    # and everything.
    whomp = {
      enableStatelessInstallation = true;
      aliases = [ "whut-whut.service" ];
      wantedBy = [ "graphical.target" ];
      requiredBy = [ "multi-user.target" ];
      upheldBy = [ "default.target" "basic.target" ];
      description = "EEEEEEEEeeeeeeehhh.....";
    };

    "there/10-hello" = {
      description = "wrapper-manager systemd service override example";
      wantedBy = [ "graphical.target" "gnome-session@sample.target" ];
    };

    "there/20-hello-again" = {
      wantedBy = [ "womp-womp.target" ];
    };

    service-with-custom-sections = {
      description = "wrapper-manager systemd unit with custom sections";
      wantedBy = [ "graphical.target" "gnome-session@sample.target" ];
      extraSectionsConfig = {
        "Custom Section" = {
          Hello = "there";
          AnotherCustomConfigSettings = builtins.toString [
            "SPACE"
            "DELIMITED"
            "VALUES"
            "IN"
            "SPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACE"
          ];
        };

        "org.example.App" = {
          additional_search_path = lib.makeBinPath [
            "/root"
            "/usr"
            "/usr/local"
          ];
        };
      };
    };
  };

  programs.systemd.system.sockets.hello = {
    description = "Hello socket";
    before = [ "multi-user.target" ];
    wantedBy = [ "sockets.target" ];
    listenStreams = [ "%t/hello" ];
  };

  programs.systemd.user.targets.activitywatch = {
    wantedBy = [ "default.target" ];
    after = [ "default.target" ];
    description = "ActivityWatch server";
    requires = [ "default.target" ];
  };

  # An override drop-in target unit.
  programs.systemd.user.targets."gnome-session@one.foodogsquared.HorizontalHunger/session" = {
    wants = let
      gsdComponents =
        lib.map
        (gsdc: "org.gnome.SettingsDaemon.${gsdc}")
        [
          "A11ySettings"
          "Color"
          "Housekeeping"
          "Power"
          "Keyboard"
          "Sound"
          "Wacom"
          "XSettings"
        ];
    in
      lib.map (n: "${n}.target") (gsdComponents ++ [ "org.gnome.Shell" ]);
    requires = [ "org.gnome.Shell.target" ];
  };

  programs.systemd.user.services = {
    hello = {
      description = "Greeting service";
      path = with pkgs; [ hello hello-go ];
      script = ''
        hello
      '';
    };

    # OK, we're just trying to recreate this unit if it's doable.
    "gnome-session-manager@" = {
      description = "GNOME Session Manager (session: %i)";
      unitConfig = {
        RefuseManualStart = true;
        RefuseManualStop = true;
        OnFailureJobMode = "replace-irreversibly";
        CollectMode = "inactive-or-failed";
      };
      onFailure = [
        "gnome-session-shutdown.target"
      ];
      requisite = [ "gnome-session-pre.target" ];
      after = [ "gnome-session-pre.target" ];

      requires = [ "gnome-session-manager.target" ];
      partOf = [ "gnome-session-manager.target" ];
      before = [ "gnome-session-manager.target" ];
      serviceConfig = {
        Type = "notify";
        ExecStart = "gnome-session-binary --systemd-service --session=%i";
        ExecStopPost = "-gnome-session-ctl --shutdown";
      };
    };

    "gnome-session-manager@one.foodogsquared.HorizontalHunger/10-gnome-session-wrapper-manager-override" = {
      description = "Another override unit";
    };
  };

}

For astute readers, this is actually the same options found from NixOS' systemd module. This is because it uses a forked version from NixOS' systemd library and inherited the same options. If you’re familiar with it, you’re halfway there but this version does have its differences from the original version:

  • Individually enabling units has been removed (i.e., systemd.user.units.<name>.enable).

  • Stateless installation is disabled and has to be enabled through programs.systemd.enableStatelessInstallation and their unit-specific version of the same option.

  • Generating drop-in units is supported out-of-the-box through specifying the unit filename as $UNITNAME/$DROPIN where it will be generated as $UNIT_NAME.$UNIT_TYPE/$DROP_IN_NAME.conf.

    For example, programs.systemd.system.services."nix-daemon/10-basic-overrides" generates $out/etc/systemd/system/nix-daemon.service.d/10-basic-overrides.conf in the final derivation.

    As a consequence, the equivalent of overridesStrategy from NixOS' version has been removed.

  • There is a dedicated settings options in each unit (e.g., programs.systemd.user.<name>.settings). Though, in practice, you may not use it since most of the options handle that for you.

Like from the NixOS' version, there is an option for generating either for system and user-specific units. It will be generated in $out/etc/system/$VARIANT.

Setting locale data

One of the little things to be set for Nix-built applications are setting up locale data. If set improperly, it would use locale data in the host system instead from nixpkgs. [3]

To enable them, you’ll have to set locale.enable to true to set it for all wrappers but you can specifically set them with wrappers.<name>.locale.enable. You could also change the locale archive package with locale.package.

As a standalone package

wrapper-manager packages can be compiled as a standalone package to be included as part of the typical Nix operations (e.g., makeShell, as part of packages flake output, as part of environment.systemPackages in NixOS). That part is easy, just build it with wrapper-manager build function located at its library set.

The following code listing shows an example of it including a wrapper-manager config as part of the devshell. Just remember that wrapper-manager configurations primarily ends as a package.

{ pkgs ? import <nixpkgs> { }, wrapperManager ? import <wrapper-manager-fds> { } }:

let
  inherit (pkgs) lib;
  gems = pkgs.bundlerEnv {
    name = "wrapper-manager-fds-gem-env";
    ruby = pkgs.ruby_3_1;
    gemdir = ./.;
  };
  asciidoctorWrapped = wrapperManager.lib.build {
    inherit pkgs;
    modules = lib.singleton {
      wrappers.asciidoctor = {
        arg0 = lib.getExe' gems "asciidoctor";
        prependArgs = [ "-r" "asciidoctor-diagram" "-T" ./templates ];
      };
    };
  };
in
pkgs.mkShell {
  packages = with pkgs; [
    asciidoctorWrapped
    treefmt
    gems
    gems.wrappedRuby
  ];
}

With NixOS and home-manager

wrapper-manager also comes with integrations for NixOS and home-manager. You’ll have to import the respective environment modules for them somewhere in your configuration. Here’s an example of importing it into a NixOS and home-manager config with flakes.

Importing wrapper-manager integration modules
{
  # ...
  inputs.wrapper-manager.url = "github:bazcatqubed/nix-module-wrapper-manager-fds";

  outputs = inputs:
    let
      inherit (inputs.nixpkgs) lib;
      inherit (lib) nixosSystem;
      inherit (inputs.home-manager.lib) homeManagerConfiguration;
    in
      {
        nixosConfigurations.desktop = nixosSystem {
          modules = [
            inputs.wrapper-manager.nixosModules.wrapper-manager
          ];
        };

        homeConfigurations.user = homeManagerConfiguration {
          modules = [
            inputs.wrapper-manager.homeModules.wrapper-manager
          ];
        };
      };
}

For the most part, the integration modules are mostly the same. As an example, you can create wrappers through wrapper-manager.packages where it is expected to be an attribute set of wrapper-manager configurations.

Any wrapper-manager packages declared through it are automatically added as part of their respective list of packages (e.g., home.packages for home-manager, environment.systemPackages for NixOS).

{ lib, config, ... }:

{
  wrapper-manager.packages.writing.imports = [
    ./other-wrapper-manager-modules
  ];

  wrapper-manager.packages.music-setup = {
    wrappers.beets = {
      arg0 = lib.getExe' pkgs.beets "beet";
      prependArgs = [ "--config" ./config/beets/config.yml ];
    };
  };

  wrapper-manager.packages.archive-setup = { lib, pkgs, ... }: {
    wrappers.gallery-dl = {
      arg0 = lib.getExe' pkgs.gallery-dl "gallery-dl";
      prependArgs = [ ];
    };

    wrappers.yt-dlp-audio = {
      arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
      prependArgs = [
        "--config-location" ./configs/yt-dlp/audio.conf
      ];
    };
  };
}

Aside from an easy way to create wrappers instead of manually invoking the building function from wrapper-manager, there’s also another nicety with the integration module. The wrapper-manager configuration will have an additional module argument depending on the environment: nixosConfig for NixOS and hmConfig for home-manager. This is useful for dynamic and conditional configurations with the wider-scoped environment.

Additionally, with documentation packages alongside the environment similar to NixOS and home-manager.

  • There is a manpage which you can install by setting wrapper-manager.documentation.manpage.enable to true. It is available to be viewed as wrapper-manager-configuration.nix(5) (i.e., man 5 wrapper-manager-configuration.nix).

  • An HTML manual can be brought over by setting wrapper-manager.documentation.html.enable to true. The HTML manual package has a desktop entry file titled wrapper-manager manual in the common application launchers (e.g., rofi, GNOME Shell app launcher).

You can also set additional modules to be included with wrapper-manager.documentation.extraModules in case you have custom wrapper-manager modules.

Differences from original wrapper-manager

Being a reimagining of wrapper-manager, there are some major differences between them.

The recorded differences are noted as of github:viperML/wrapper-manager[this commit, rev=c936f9203217e654a6074d206505c16432edbc70, opts=repo]. It may be revised that renders part of the following list to be outdated. Feel free to correct them in the source code repo.

The main difference is the way how the final output is built. In the original version, each of the specified wrappers under wrappers are individually built. In the reimagined version, these are consolidated into one build step since makeWrapper allows us to do so. As a side effect, there’s no options that could require to be built individually such as wrappers.<name>.basePackage, wrappers.<name>.renames, wrappers.<name>.overrideAttrs, and wrappers.<name>.extraPackages.

Another difference is the original version also handles some cases of fixing XDG desktop entries in the final output. In wrapper-manager-fds, this case is absent since its maintainer at the time (foo-dogsquared) deemed it "a pain in the ass" to handle especially that…​

  • There are more use cases to handle such as multiple desktop entries for multiple reasons.

  • Most desktop metadata is pretty much usable even with the custom wrapper without cleaning them.

  • This need is less emphasized since wrapper-manager-fds also allows you to make XDG desktop entries in the config itself anyways.

A possible consideration is to make a build option toggle to handle this but it would involve "cleaning" the Exec= desktop entry directive to use the executable name instead of the full path.

If you’re interested in migrating to this version, here’s a quicktable of individual differences that might interest you.

How arg0 is set per-wrapper

In the original version…​
{ lib, pkgs, ... }:
{
  wrappers.hello.basePackage = pkgs.hello;
}
And in wrapper-manager-fds.
{ lib, pkgs, ... }:
{
  wrappers.hello.arg0 = lib.getExe' pkgs.hello "hello";
}

Renaming executables per-wrapper

In the original version…​
{ lib, pkgs, ... }:

{
  wrappers.hello.renames.hello = "hello-customized";
}

In wrapper-manager-fds, there’s no renaming step as we already let the user name the executable.

And in wrapper-manager-fds.
{ lib, pkgs, ... }:

{
  wrappers.hello.executableName = "hello-customized";

  # You could also change the attrname.
  wrappers.hello-customized.arg0 = "${pkgs.hello}/bin/hello";
}

Setting (and unsetting) environment variables per-wrapper

In the original version…​
{ lib, pkgs, ... }:

{
  # The default action is to set the value if not yet set.
  wrappers.hello.env.CUSTOM_ENV_VAR.value = "HELLO";

  # You can force it with the following.
  wrappers.hello.env.CUSTOM_ENV_VAR.force = true;

  # You can also unset it by setting the value to null.
  wrappers.hello.env.CUSTOM_ENV_VAR.value = lib.mkForce null;
}
And for wrapper-manager-fds.
{ lib, pkgs, ... }:

{
  # On the other hand, wrapper-manager-fds forces it by default.
  wrappers.hello.env.CUSTOM_ENV_VAR.value = "HELLO";

  # But you can conditionally set it with...
  wrappers.hello.env.CUSTOM_ENV_VAR.action = "set-default";

  # If you want to unset it, set the following code.
  wrappers.hello.env.CUSTOM_ENV_VAR.action = lib.mkForce "unset";
}

Adding PATH env values

In the original version…​
{ config, lib, pkgs, ... }:
{
  wrappers.hello.pathAdd = with pkgs; [
    yt-dlp
    gallery-dl
  ];
}
And for wrapper-manager-fds.
{ config, lib, pkgs, wrapperManagerLib, ... }:
{
  wrappers.hello.pathAdd = wrapperManagerLib.getBin (with pkgs; [
    yt-dlp
    gallery-dl
  ]);
}

1. Naaaah, hopefully you’ll get to do less of that as this document develops into a more comprehensive guide.
2. In fact, wrapperManagerLib.env.build is just a wrapper around this function getting the toplevel package.
3. Not exactly the end of the world as it can still run but it’s just incorrect.