foodogsquared's reimagining of wrapper-manager.
Go to file
2024-07-19 13:50:41 +08:00
docs wrapper-manager-fds/docs: update README 2024-07-19 13:50:15 +08:00
lib wrapper-manager-fds/lib: remove build-support subset 2024-07-10 15:44:40 +08:00
modules wrapper-manager-fds: update comments 2024-07-16 18:50:29 +08:00
npins wrapper-manager-fds: update sources 2024-07-16 18:39:02 +08:00
tests wrapper-manager-fds/tests: init derivation for tests 2024-07-13 17:00:06 +08:00
.gitignore wrapper-manager-fds: init gitignore 2024-07-14 11:16:58 +08:00
default.nix wrapper-manager-fds: update comments 2024-07-16 18:50:29 +08:00
flake.nix wrapper-manager-fds: update comments 2024-07-16 18:50:29 +08:00
LICENSE wrapper-manager-fds: put more things in flake 2024-07-14 11:13:08 +08:00
Makefile wrapper-manager-fds: update devshell 2024-07-19 13:50:41 +08:00
README.adoc wrapper-manager-fds/docs: update README 2024-07-19 13:50:15 +08:00
shell.nix wrapper-manager-fds: update devshell 2024-07-19 13:50:41 +08:00
treefmt.toml wrapper-manager-fds: update devshell 2024-07-19 13:50:41 +08:00


title: Project overview --- = nix-wrapper-manager-fds :toc:

wrapper-manager-fds is foodogsquareds reimagining of wrapper-manager.

Caution

wrapper-manager-fds is considered unstable and the author is still freely experimenting with various aspects of the project including the module options, library set API, and ease of use for third-partyuse. Expect constant breakages here and put on a hard hat before exploring this part of the town.

As a recap, the original aim of wrapper-manager is to pave a way of configuring Nix environments through wrappers. There has been interest on this feature [1] and I sometimes configure programs through wrappers instead of the typical module system. It has its use cases such as quickly creating variants of the same program for specific reasons (for example, creating specialized versions of yt-dlp for downloading audio, video, and more). But why not create something more than that? And thats how wrapper-manager-fds came to be.

Installation

Channels

You can install wrapper-manager-fds with Nix channels.

nix-channel --add https://github.com/foo-dogsquared/wrapper-manager-fds/archive/master.tar.gz wrapper-manager-fds
nix-channel --update

Then in your environment configuration, youll have to import it. For more details, see User entrypoint.

Pinning tool

A recommended (non-flakes) way to install Nix dependencies is to use a pinning tool. There are a handful of pinning tool out there but in this case, well use npins as our tool of choice. Assuming you have already initialized npins, you can simply add wrapper-manager-fds to your Nix project with the following command.

npins add github foo-dogsquared wrapper-manager-fds

Similar to channels installation, youll have to import the User entrypoint object.

Manual pinning

Though not recommended, you could manually pin the Nix library yourself like in the following code.

{ pkgs, ... }:

let
  wrapper-manager-fds = builtins.fetchTarball "https://github.com/foo-dogsquared/wrapper-manager-fds/archive/master.tar.gz";
  wrapperManager = import wrapper-manager-fds { };
  wrapperManagerLib = import wrapperManagerLib.lib { inherit pkgs; }
in
wrapperManagerLib.env.build { }

Flakes

This project also has a flake object. In your flake definition, just import it as one of the inputs. Unlike the other methods, the flake output is the user entrypoint so no need to import it or anything.

{
  inputs.wrapper-manager-fds.url = "github:foo-dogsquared/wrapper-manager-fds";

  outputs = { nixpkgs, ... }@inputs: {
    nixosConfigurations.desktop = nixpkgs.lib.nixosSystem {
      modules = [
        ./hosts/desktop.nix
        inputs.wrapper-manager-fds.nixosModules.default
      ];
    };
  };
}

User entrypoint

Most of the things you need from the project are all retrieveable from the entrypoint of this project (default.nix) which is an attribute set closely structured from a typical Nix flake that you see. For example, if you want the NixOS module for wrapper-manager, you could refer to nixosModules.default. Heres the breakdown of the attrset entries in the entrypoint.

  • lib is an attribute set containing the main functions: eval and build. (More details to use them are in As a library.)

  • nixosModules contains a set of NixOS modules for wrapper-manager-fds integration. You could get the main module with the default attribute.

  • homeModules are for home-manager modules and similarly structured to nixosModules attribute.

  • wrapperManagerLib contains a path intended to be imported within your Nix code. It simply contains the library set that can be used outside of the wrapper-manager-fds module environment.

Getting started

Now that you have wrapper-manager-fds on the go, lets have a breakdown of what it is, exactly. The project itself is made of different parts which you can use for different purposes.

The module environment

One part of the project is the module environment. Just like home-manager and disko and the original source, wrapper-manager-fds comes with its own module environment. Instead of a home environment from home-manager or an installation script from disko, wrapper-manager-fds module environment evaluates to a derivation containing a wrapper script. [2] This can be thought of as a declarative layer over makeWrapper build hook from nixpkgs.

Heres a very simple example of a wrapper for Neofetch.

{ lib, pkgs, ... }:

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

Or if you want fastfetch…

{ lib, pkgs, ... }:

{
  wrappers.fastfetch = {
    arg0 = lib.getExe' pkgs.fastfetch "fastfetch";
    appendArgs = [ "--logo" "Guix" ];
    env.NO_COLOR = "1";
  };
}

Or even both in the same configuration (which you can do).

{
  imports = [
    ./fastfetch.nix
    ./neofetch.nix
  ];
}

You could even create XDG desktop entry files useful for the application to be launched through an application launcher/menu. For example, you could create an executable and a desktop entry to launch a custom Firefox profile in your home-manager configuration.

Creating a custom Firefox desktop entry launching a custom profile
{ config, lib, pkgs, ... }:

{
  programs.firefox.profiles.custom-profile = {
    # Put some profile-specific settings here.
  };

  wrapper-manager.packages.browsers = {
    wrappers.firefox-custom-profile = {
      arg0 = lib.getExe' config.programs.firefox.package "firefox";
      prependArgs = [
        "-P" "custom-profile"
      ];
      xdg.desktopEntry = {
        enable = true;
        settings = {
          desktopName = "Firefox (custom-profile)";
          startupNotify = true;
          startupWMClass = "firefox";
          icon = "firefox";
          mimeTypes = [
            "text/html"
            "application/xhtml+xml"
            "application/vnd.mozilla.xul+xml"
            "x-scheme-handler/http"
            "x-scheme-handler/https"
          ];
        };
      };
    };
  };
}

As a library

wrapper-manager also comes with a library set which you can use to evaluate and build wrapper-manager packages yourself. This is found in the wrapperManagerLib attribute from the user entrypoint where it needs an attribute set containing a nixpkgs instance in pkgs.

An example of importing wrapper-manager library
{ pkgs }:

let
  wrapper-manager = import (builtins.fetchgit { }) { };

  wmLib = import wrapper-manager.wrapperManagerLib { inherit pkgs; };
in
wmLib.env.build {
  inherit pkgs;
  modules = [ ./fastfetch.nix ];
  specialArgs.yourMomName = "Joe Mama";
}

Heres a quick rundown of what you can do with the library.

  • Evaluate a wrapper-manager module with env.eval where it accepts an attrset similar to the previous code listing containing a list of additional modules, the nixpkgs instance to be used, and specialArgs to be passed on to the lib.evalModules from nixpkgs.

  • Build a wrapper through env.build returning a derivation of the wrapper. It accepts the same arguments as env.eval.

There is also lib attribute if all you want to do is to build and/or evaluate a wrapper-manager configuration. It only contains the function from env subset which contains build and eval.

As a composable module

The most user-friendly way of using wrapper-manager would be as a composable nixpkgs module of an existing environment. wrapper-manager provides a Nix module specifically for NixOS and home-manager environment. [3] You can import them through the {nixos,home}Modules.default from the user entrypoint of the project.

Most of the things set up here are implemented to make declaring wrappers ergonomic with the environment. For a start, wrapper-manager-fds sets up a module namespace in wrapper-manager. Heres a quick breakdown of the features that the module has.

  • Passes the wrapper-manager library through wrapperManagerLib module argument. This is nice if you want to only use wrapper-manager to quickly create wrappers inside of the configuration without using the wrapper-manager NixOS/home-manager integration module.

  • You could declare wrappers through wrapper-manager.packages.<name> where each of the attribute value is expected to be a wrapper-manager configuration to be added in its respective wider-scope environment.

  • You could include other modules through wrapper-manager.sharedModules. This is useful for extending wrapper-manager inside of the configuration environment.

Heres an example of adding wrappers through wrapper-manager inside of a home-manager configuration. The following configuration will create a wrapped package for yt-dlp with an additional wrapper script named yt-dlp-audio and yt-dlp-video.

Installing yt-dlp with custom variants of it inside of a home-manager configuration
{ config, lib, pkgs, ... }:

{
  home.packages = with pkgs; [
    flowtime
    blanket
  ];

  wrapper-manager.packages = {
    music-setup = {
      basePackages = [ pkgs.yt-dlp ];
      wrappers.yt-dlp-audio = {
        arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
        prependArgs = [
          "--config-location" ./config/yt-dlp/audio.conf
        ];
      };
      wrappers.yt-dlp-video = {
        arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
        prependArgs = [
          "--config-location" ./config/yt-dlp/video.conf
        ];
      };
    };
  };
}

Development

If you want to hack this hack, youll need either Nix with flakes enabled (experimental-features = nix-command flakes in nix.conf) or not. Either way, this should be enough to cater both flake- and non-flake users.

This project supports the current stable and unstable version of NixOS. Specifically, were looking out for the nixpkgs instance both of these versions has. As an implementation detail, we pin these branches through npins which both flakes- and non-flake-based setups uses. Just be familiar with it and youll be fine for the most part. [4]

Setting up the development environment should be easy enough.

  • For flake users, you can just reproduce the development environment with nix develop.

  • For non-flake users, you can do the same with nix-develop.

As an additional note, it is recommended to use something like direnv with use flake or use nix depending on your personal preferences to use flake or not.

Take note there is a Makefile full of commands intended for easily interacting with the project but it is heavily assumed youre in the development environment of the project.

Library set and modules

This Nix project has a test infrastructure set up at ./tests covering the library set nad the wrapper-manager module environment. For its library set, it makes use of the nixpkgs library and a JSON schema to validate if it passes the whole tests. To make use of it, you can run it with the following commands.

  • For flake users, you can run nix flake check.

  • For non-flake users, you can do the same with nix-build tests/ -A libTestPkg or nix build -f tests/ libTestPkg.

The derivation output should be successfully built if all of the tests in the suite passes. Otherwise, it should fail and youll have to see the build log containing all of the tests that failed.

On another note, there is a quicker way of checking the test suite with nix eval -f tests lib (or nix-instantiate --eval --strict tests/ -A lib) where it contains the raw test data which is useful if you dont want to essentially build a new derivation each time. It is also quicker to eyeball results in this way especially if youre always working with the tests anyways.

Website

This project also has a website set up with Hugo. The files that you need to see are in ./docs directory.

  • For flake users, you can build the website with nix build .#website.

  • For non-flake users, you can do the same with nix-build docs/.

There is also a dedicated development environment placed in docs/shell.nix but this should be a part of the primary development environment already. You can enter it with nix develop .#website or nix-shell docs/.

Just take note that the website also requires the NixOS options which comes in a JSON file. This should be already taken care of in the package definition of the website but otherwise it is something that youll have to be aware of.

The more important task to developing this part of the project is continuously getting feedback from it. You can do so simply with the following commands:

  • For flake users, nix develop --command hugo -s ./docs serve.

  • For non-flake users, nix-shell docs --command hugo -s ./docs serve.

  • If youre using Makefile of this project, make docs-serve.

Nix environment

As for developing the environment with Nix itself, it is very much preferred to make wrapper-manager-fds work with non-flake setups. This also includes the workflow of the development itself for the purpose of easier time bootstrapping wrapper-manager-fds.

Due to the unfortunate situation with flakes as an experimental feature, it is more like a second-class citizen in terms of support. This is because it is pretty easy to make a flake with non-flake tools compared to vice versa. [5]

Heres an exhaustive guidelines that you have to keep in mind when developing related files within the project:

  • This project uses calendar versioning following software versioning of the upstream. The unstable branches are basically deployed with development versions of this project.

  • Only the current stable branch and the unstable branch of NixOS is supported.

  • The preferred default nixpkgs branch at development is nixos-unstable.

  • There shouldnt be any user consumables that requires anything from the npins sources.

Goals and non-goals

As a Nix project, wrapper-manager-fds aims for the following goals.

  • Create an ecosystem of creating them wrappers, mainly through its library set and the module environment.

  • Make creating wrappers ergonomic for its users. Not necessarily user-friendly but it should easy enough to get started while allowing some flexibility, yeah?

  • Make a nice environment for creating custom wrappers which is already quite possible thanks to the heavy lifting of the nixpkgs module system.

Frequently asked questions (FAQ)

  1. Is this compatible with the original wrapper-manager?

    Nope. It is a reimagining with a completely different way of using it so it wont be fully compatible with it from the start.

  2. Why reimplement this anyways?

    For funsies and also because there are some things I find not so great with using the project. As of this writing, using wrapper-manager to simply create wrappers anywhere is a pain.

  3. Why not just incorporate the wanted changes into the original implementation?

    While it could be done, there will be some unwanted major changes into the project which would cause inconvenience to its users anyways so it isnt a good idea. Plus it also justifies me implementing a bunch of features that would otherwise be deemed inconsistent with the project.

  4. Cant you just create a wrapper with pkgs.makeWrapper and such from nixpkgs?

    Yeah, you can. Theres nobody stopping you from doing so and surely theres no hitman preparing to assissinate right behind you as you about to deny wrapper-manager-fds and smugly type make in makeWrapper. In fact, wrapper-manager uses makeWrapper as the main ingredient. Just think of wrapper-manager as a declarative version of that among the bajillion ways of making wrappers in the Nix ecosystem.

    As an additional point, there are still use cases for it even with a simple pkgs.writeShellScriptBin. In fact, if you have a situation like say having to create a one-off wrapper script to be added in a NixOS system, you can simply do the following:

    let
      ytdlpAudio = pkgs.writeScriptBin "yt-dlp-audio" ''
        ${pkgs.yt-dlp}/bin/yt-dlp --config-location "${../../config/yt-dlp/audio.conf}" $@
      '';
    in
    {
      environment.systemPackages = [ ytdlpAudio ];
    }

    BAM! No need for wrapper-manager!

  5. Why use the module system?

    Because screw you, thats why!!! Am I stupid and lazy for basically using a battle-hardened configuration system library such as nixpkgs module system? [6]

    Seriously though, the main reason is pretty simple: it is quite established and a battle-hardened part in the Nix ecosystem. It has gone through the test of time and the numerous 339 users of the entire Nix ecosystem are quite adamant in the declarative aspect of the Nix thingy. So… why not use it.

  6. Any problems (and impending explosions) when using this project?

    As far as I can tell, not much (especially explosions) but there are a few caveats you need to know. Just know this is something the author is trying to resolve.

    • wrapper-manager-fds is not great at handling double wrappers. It just naively wraps a package and goes on its merry way.

    • wrapper-manager-fds doesnt handle any replacement for the related files very well. This is especially noticeable in large desktop-adjacent packages such as Inkscape, Firefox, and Blender with a bunch of plugins and whatnot where they have their own wrappers. This means you cannot set programs.NAME.package or something similar with it.

Acknowledgements

I found a bunch of things for inspiration (READ: to steal ideas from). Heres a list of resources Ive found.

This project is licensed under MIT License (SPDX identifier: MIT). Just see ./LICENSE for full text and details and whatnot.

The documentation (except for the code examples), on the other hand, is licensed under GNU Free Documentation License v1.3 only with no "Invariants" section (SPDX identifier: GFDL-1.3-no-invariants-only) You can see either the link or ./docs/LICENSE for more info. The code examples, similar to the project codebase, are licensed under MIT with the same conditions apply and all that jazz.


1. I mean, a part of the nixpkgs package set has dedicated wrappers for some packages such as GIMP, Inkscape, and Blender.
2. While the original source also evaluates similar to that, it typically involves a set of wrappers inside of the same configuration environment rather than a single wrapper.
3. Any other environments are basically unsupported and if you like to use it outside of NixOS and home-manager, youre on your own.
4. Most likely, you dont even need to interact with it for the most part since handling update cadence is handled automatically through the remote CI.
5. flake-compat is great and all but it holds back wrapper-manager-fds in making it easy to bootstrap if we rely on it.
6. The answer is yes to both!