nix-wrapper-modules

repository·main·Indexed 18 days ago

https://github.com/birdeehub/nix-wrapper-modules

A Nix library providing a module-based approach to creating wrapped executables. It allows users to define portable derivations of programs with specific configurations, environment variables, and settings without requiring full NixOS or Home Manager environments. The library supports multiple script backends (nix, shell, binary) and includes specialized types for flag control and dependency sorting via DAGs. It also provides a comprehensive template and module for wrapping Neovim, including Lua-side Nix information access and plugin management.

Tokens
7K
Snippets
30
Records
34
Agent score
64%

What's inside nix-wrapper-modules

  1. What is nix-wrapper-modules and when to use it

    main

    nix-wrapper-modules is a Nix library designed to create wrapped executables using the Nix module system.

    Instead of manually writing shell scripts with pkgs.writeShellScriptBin or complex pkgs.symlinkJoin derivations to wrap programs with specific configurations (like custom config files or environment variables), this library allows you to define these wrappers as portable Nix modules.

    Use this library when:

    • You want to create a portable derivation of a program with specific settings (e.g., a specific shell for Alacritty).
    • You want to avoid the overhead of pulling in entire NixOS or Home Manager configurations just to use a few configured tools.
    • You need a consistent, flexible way to wrap packages that remains overrideable via the module system.
    • You want to replace standard packages with wrapped versions in an overlay without losing the ability to use .override or .overrideAttrs.
  2. How to run a spec before or after `init.lua`

    main

    The main init.lua of your configuration directory is automatically added to the specs DAG (Directed Acyclic Graph) under the name INIT_MAIN.

    By default, all other specs run after INIT_MAIN. If you need a plugin to be configured before your main init file runs, use the before field in your spec.

    config.specs.my-early-plugin = {
      data = pkgs.vimPlugins.some-plugin;
      before = [ "INIT_MAIN" ];
    };
  3. Customize wrapper derivations using the module system

    main

    Unlike other wrapper projects that use separate builder functions, nix-wrapper-modules provides absolute control over the resulting derivation directly from within the Nix module system.

    You can use hooks like drv.postBuild to perform actions on the wrapper derivation, such as generating launcher scripts. When using these hooks, you can use the "${placeholder "out"}" syntax to correctly reference the final wrapper derivation's output path.

    Because these are part of the module system, you can expose these customization hooks as module options, allowing users to modify the wrapper's behavior easily.

    # Example concept: using a postBuild hook via the module system
    # (Note: exact syntax depends on the specific module implementation)
    config.drv.postBuild = "cp ${placeholder "out"}/launcher.sh $out/launcher.sh";
  4. Manage Neovim plugins via config.specs

    main

    Plugins are managed through the config.specs option. This option accepts a set of plugins or a set of lists of plugins.

    There are two primary ways to organize plugins:

    1. Startup Plugins: Provided as a simple list of plugins (e.g., specs.general = [ plugin1 plugin2 ];).
    2. Lazy-loaded Plugins: Provided as a spec object where you can set lazy = true and pass the plugin list in the .data field.

    Each item in a spec can be customized using:

    • config.specMods: For customizing the plugin spec.
    • config.specCollect: For processing specs.
    • config.specMaps: For advanced mapping of specs.

    Note: Plugins can be in-store or out-of-store paths, but they cannot be inline Lua values.

    # Basic plugin specification example
    specs.general = with pkgs.vimPlugins; [ 
      # plugins loaded at startup 
    ];
    
    specs.lazy = {
      lazy = true;
      data = with pkgs.vimPlugins; [
        # plugins loaded via vim.cmd.packadd
      ];
    };
  5. Create a custom Neovim host (e.g., Neovide)

    main

    You can define a new host by providing an attribute set to config.hosts. This allows you to define custom wrapper arguments that run in the context of the final Neovim derivation.

    config.hosts.neovide = {
      lib, pkgs, ... :
      {
        imports = [ wlib.modules.default ];
        config.nvim-host.enable = lib.mkDefault false;
        config.package = pkgs.neovide;
        config.nvim-host.flags."--neovim-bin" = config.wrapperPaths.placeholder;
      };
    };
    
    # Enable the host
    config.hosts.neovide.nvim-host.enable = true;
  6. Configure Neovim plugins via `config.specs`

    main

    In the Neovim wrapper module, plugins are managed using the config.specs option. This option accepts a set of plugins or a set of lists of plugins.

    Instead of passing a raw plugin package, you can pass a spec (an attribute set) to provide additional metadata like configuration code, loading behavior, or language types.

    Key behaviors:

    • Direct Plugin: Pass the package directly (e.g., config.specs.name = pkgs.plugin).
    • Spec Object: Pass an attribute set where the plugin is assigned to the data field.
    • Propagation: Many options set in the outer attribute set (the parent) will propagate to all contained lists/specs. For example, setting lazy = true in a parent spec will make all plugins in its data list load lazily by default.
    # Direct plugin path
    config.specs.gitsigns = pkgs.vimPlugins.gitsigns-nvim;
    
    # Spec with configuration
    config.specs.treesj = {
      data = pkgs.vimPlugins.treesj;
      config = "require('treesj').setup({})";
    };
    
    # List of specs with propagation
    config.specs.completion-plugins = {
      lazy = true; # This propagates to all items in the 'data' list
      data = [
        {
          name = "blink-cmp";
          data = pkgs.vimPlugins.blink-cmp;
        }
        pkgs.vimPlugins.fzf-lua-nvim;
      ];
    };
  7. Install the Neovim module without a separate flake

    main

    If you do not want to maintain a separate flake for your Neovim configuration, you can call the module.nix file directly within your NixOS configuration using evalPackage. This example assumes you have access to the nix-wrapper-modules library via inputs.

    inputs: # <-- get the library somehow
    { pkgs, ... }: {
      # call the module and install the package (nixos example)
      environment.systemPackages = [ (inputs.nix-wrapper-modules.lib.evalPackage [ ./module.nix { inherit pkgs; } ]) ];
    }
  8. Access Nix information in Lua via the info plugin

    main

    The Neovim wrapper makes Nix-provided information available in your Lua configuration through a plugin. The plugin is accessible via the global variable vim.g.nix_info_plugin_name.

    To access values, use the fetcher function pattern: nixInfo(default, "path", "to", "value", "in", "plugin").

    Recommended pattern for non-Nix compatibility: To prevent errors when running Neovim outside of a Nix environment, wrap the requirement in a pcall and provide a fallback function that returns the default value.

    -- Accessing values
    local nixInfo = require(vim.g.nix_info_plugin_name)
    local default = nil
    local value = nixInfo(default, "path", "to", "value", "in", "plugin")
    
    -- Non-Nix compatibility shim
    do
      local ok = pcall(require, vim.g.nix_info_plugin_name)
      if not ok then
        package.loaded[vim.g.nix_info_plugin_name] = setmetatable({}, {
          __call = function (_, default) return default end
        })
      end
      require(vim.g.nix_info_plugin_name).isNix = vim.g.nix_info_plugin_name ~= nil
    end