Skip to content

BirdeeHub/nix-wrapper-modules

Repository files navigation

A Nix library to create wrapped executables via the module system.

Are you annoyed by rewriting modules for every platform? nixos, home-manager, nix-darwin, devenv?

Then this library is for you!

What is this for?

When configuring programs using nix, one of the highlights for most is the module system.

The main "configuration.nix" file of NixOS and "home.nix" for home-manager contain all sorts of shortlist options. For a while, it's great!

But then you need to use your configuration somewhere else. Pulling in your home-manager configuration on some other machine is usually overkill, takes too long, and is often a destructive action, as it will link files into the home directory and move the old files.

You don't want to pull in your entire home environment, you just needed to do some pair programming and wanted to use some of your tools, not destroy your co-workers dotfiles. Can't you make like, a shell, or a derivation or something and use that directly?

In addition, you often have some modules that might be duplicated because NixOS or home-manager options can be different. And you can't use any of that in a shell. It is starting to wear on you a bit.

So you hear about this thing called "wrapping" a package. This means, writing a script that launches the program with specific arguments or variables set, and installing that instead.

Then, you could have your configured tools as derivations you can just install via any means nix has of installing something.

Nix makes this concept very powerful, as you can create files and pull in other programs without installing them globally.

Your first attempt, you might write something that looks like this:

pkgs.writeShellScriptBin "alacritty" (let
  tomlcfg = pkgs.writeText "alacritty.toml" ''
    [terminal.shell]
    program = "${pkgs.zsh}/bin/zsh"
    args = [ "-l" ]
  '';
in ''
  exec ${pkgs.alacritty}/bin/alacritty --config-file ${tomlcfg} "$@"
'')

This is good! Kinda. If you install it, it will install the wrapper script instead of the program, and the script tells it where the config is! And it doesn't need home-manager or NixOS!

But on closer inspection, its missing a lot. What if this were a package with a few more things you could launch? Where is the desktop file? Man pages?

So, your next attempt might look more like this:

pkgs.symlinkJoin (let
  tomlcfg = pkgs.writeText "alacritty.toml" ''
    [terminal.shell]
    program = "${pkgs.zsh}/bin/zsh"
    args = [ "-l" ]
  '';
in {
  name = "alacritty";
  paths = [ pkgs.alacritty ];
  nativeBuildInputs = [ pkgs.makeWrapper ];
  postBuild = ''
    wrapProgram $out/bin/alacritty --add-flag --config-file --add-flag ${tomlcfg}
  '';
})

Ok. So maybe that isn't your second try. But you get there eventually.

This is a little closer to how stuff like nixvim works, if you have heard of it. It just has a lot more on top of that.

But even this has problems. If you want to have any sensible ability to override this later, for example, you will need to add that ability yourself.

You also now have a desktop file that might point to the wrong place. And if all you wanted to do was set a setting or 2 and move on, all of that will still be necessary to deal with.

You eventually are reduced to going to the source code of a bunch of modules in nixpkgs or home-manager and copy pasting what they did into your wrapper.

What if I told you, you can solve all those problems, and gain a really nice, consistent, and flexible way to do this, and make sure it can always be overridden later?

And it uses something you already know! The module system!

inputs.nix-wrapper-modules.wrapperModules.alacritty.wrap {
  inherit pkgs;
  settings.terminal.shell.program = "${pkgs.zsh}/bin/zsh";
  settings.terminal.shell.args = [ "-l" ];
}

The above snippet does everything the prior 2 examples did, and then some!

That's a full module (defined like this and with docs here) but just for that package, and the result is a fully portable derivation, just like the wrapper scripts above!

And you can call .wrap on it as many times as you want! You can define your own options to easily toggle things for your different use cases and re-export it in a flake and change them on import, etc.

And you do not lose your ability to use .override or .overrideAttrs on the original package!

The arguments will be passed through to the value of config.package, and the result will persist within the module system for future evaluations!

As a result it is safe to replace the vast majority of packages with their wrapped counterpart in an overlay directly.

There are included modules for several programs already, but there are rich and easy to use options defined for creating your own modules as well!

If you make one, you are encouraged to submit it here for others to use if you wish!

For more information on how to do this, check out the getting started documentation, and the descriptions of the module options you have at your disposal!

Long-term Goals

It is the ideal of this project to become a hub for everyone to contribute, so that we can all enjoy our portable configurations with as little individual strife as possible.

In service of that ideal, the immediate goal would be to transfer this repo to nix-community the moment that becomes an option.

Eventually I hope to have wrapper modules in nixpkgs, but again, nix-community would be the first step.

Short-term Goals

Help us add more modules! Contributors are what makes projects like these which contain modules for so many programs amazing!


Why rewrite lassulus/wrappers?

Yes, I know about this comic (xkcd 927), but it was necessary that I not heed the warning it gives.

For those paying attention to the recent nix news, you may have heard of a similar project which was released recently.

This excellent video by Vimjoyer was made, which mentions the project this one is inspired by at the end.

Homeless Dotfiles with Nix Wrappers

The video got that repository a good amount of attention. And the idea of the .apply interface was quite good, although I did implement it in my own way.

Most of the video is still applicable though! It is short and most of its runtime is devoted to explaining the problem being solved. So, if you still find yourself confused as to what problem this repository is solving, please watch it!

But the mentioned project gives you very little control from within the module system over what is being built as your wrapper derivation. (the thing you are actually trying to create)

It was designed around a module system which can supply some of the arguments of some separate builder function designed to be called separately, which itself does not give full control over the derivation.

This repository was designed around giving you absolute control over the derivation your wrapper is creating from within the module system, and defining modules for making the experience making wrapper modules great.

In short, this repo is more what it claims to be. A generalized and effective module system for creating wrapper derivations, and offers far more abilities to that effect to the module system itself.

This allows you to easily modify your module with extra files and scripts or whatever else you may need!

Maybe you want your tmux wrapper to also output a launcher script that rejoins a session, or creates one? You can do that using this project with, for example, a drv.postBuild hook! Just like in a derivation, and you can even use "${placeholder "out"}" in it!

But you can supply it from within the module system! You could then define an option to customize its behavior later!

In addition, the way it is implemented allows for the creation of helper modules that wrap derivations in all sorts of ways, which you could import instead of wlib.modules.default if you wanted. We could have similar modules for wrapping projects via bubblewrap or into docker containers with the same ease with which this library orchestrates regular wrapper scripts.

It makes a lot of improvements, both to the basic wrapping options, and to the module system as a whole.

Things like:

  • A wlib.types.subWrapperModuleWith type which works like lib.types.submoduleWith (and can be used in other module systems which use the nixpkgs module system)
  • Fine-grained control over the actual wrapper derivation you are making with options like config.drv and config.passthru (and others...)
  • You can call .extendModules from the evaluated result without problems.
  • A customizable type which normalizes "specs" for you, and an associated sorting function (wlib.types .dagOf and .dalOf for attribute set and list forms)
  • And for the wrapper options implementation:
    • The full suite of options you are used to from pkgs.makeWrapper, but in module form, and with full control of the order even across options.
    • Choose between multiple backend implementations with a single line of code without changing any other options:
      • nix which is the default, like shell but allows runtime variable expansion rather than build time
      • shell which uses pkgs.makeWrapper
      • binary which uses pkgs.makeBinaryWrapper
    • ${placeholder "out"} works correctly in this module, pointing to the final wrapper derivation
    • Ordering of flags on a fine-grained basis (via the DAG and DAL types mentioned above)
    • Customizing of flag separator per item (via those same types)
    • Customizing of escaping function per item (same thing here...)
    • and more...
  • and more...

While both projects have surface level similarities, this repository is in fact a full rewrite, with a quite significant increase in functionality!

About

Library for using modules to wrap packages with configuration directly, and a collection of pre-built wrapper modules!

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages