Skip to content

Repository files navigation

Sponsor Vic Image Dendritic Nix License CI Status

with-inputs - A flake-inputs adapter for Nix projects that don't use flake.nix.

with-inputs and vic's dendritic libs made for you with Love++ and AI--. If you like my work, consider sponsoring

with-inputs.nix

Provides exactly the same inputs resolution experience as real Nix flakes — follows, nested follows, per-sub-input overrides, inputs.self, and dependency introspection — using pre-fetched sources from npins, local checkouts, or any other source.

This library is not an inputs lock mechanism nor an inputs fetcher, for those we have plenty of options: npins, niv, lon, unflake, nixlock, nixtamal.

API

with-inputs sources follows outputs

The with-inputs function takes three arguments:

  1. already fetched <name>.outPath attrs.
  2. a specs attrs or function inputs: specs (or a list of such function/attrs) for custom follows, input shims or sources overrides.
  3. a function inputs: outputs like in flakes.

with-inputs does automatic input follows -- having x.inputs.y will automatically lookup for a top-level y input. You only need to specify follows for uncommon input names.

Testimonials

Amazing! I just transitioned my main flake to using your with-inputs and npins. It cut my eval times down from 20s to 6s!
-- @theutz - Den core contributor.

I am very happy to recommend this project. great work @vic!
-- @aanderse - author of trix

Examples with different Nix pinning tools

This repo provides several templates using different Nix pinning tools.

Each template has exactly the same code, except for with-inputs.nix that is used to bootstrap from each particular pinning tool.

  • npins -- Loads from ./npins
  • niv -- Loads from nix/sources.nix
  • lon -- Loads from lon.nix
  • unflake -- Loads from unflake.nix
  • nixtamal -- Loads from nix/tamal
  • flake -- Loads from flake.lock
  • tack -- Loads from .tack/pins.toml + .tack/pins.lock.json

Usage

Download our default.nix into your project ./with-inputs.nix.

curl https://raw.githubusercontent.com/denful/with-inputs/refs/heads/main/default.nix -o with-inputs.nix

Or use npins or builtins.fetchTarball with a fixed revision of it. 1 2

npins add github denful with-inputs
# default.nix
let
   sources = import ./npins; # example with npins. use any other sources.
   with-inputs = import sources.with-inputs sources {
     # keep reading for follows and local inputs
   };

   outputs = inputs: { }; # your flake-like outputs function
in 
with-inputs outputs

or if you want to allow overrides of inputs from a consumer project:

# default.nix
{
  inputsOverrides ? { },
}:
let
   sources = import ./npins; # example with npins. use any other sources.
   with-inputs = import sources.with-inputs sources [ {
     # keep reading for follows and local inputs
   } inputsOverrides ];

   outputs = inputs: { }; # your flake-like outputs function
in
with-inputs outputs

Flake backed by non-flake pins

When with-inputs detect a flake dependency which does not declare any inputs, that flake output function is still called with the all available inputs, so they could be used as overrides.

Follows and local checkout overrides

The second argument to with-inputs is an attribute set that can be used to drive input resolution, for example to use local checkout or to specify flake-like follows. Alternatively that second argument can also be a function taking inputs and returning such an attribute set. It can also be a list of such functions or attribute sets (which is useful to accept overrides from consumer projects).

See tests.nix and vic/vix:follows.nix for usage examples.

{
    # Local checkout — loaded as a flake if a flake.nix is present
    mylib.outPath = ./mylib;

    # Local checkout with sub-input overrides applied when loading its flake.nix
    someLib = { outPath = ./someLib; inputs.nixpkgs.follows = "nixpkgs"; };

    # Direct import — value used as-is (function, module result, attrset, etc)
    systems = import ./systems.nix;

    # Top-level follows: alias one input to another
    nixpkgs-stable.follows = "nixpkgs";

    # Nested follows: traverse sub-inputs
    something.follows = "a/b/c";  # → allInputs.a.inputs.b.inputs.c

    # Empty follows: intentionally disconnect an input
    unwanted.follows = "";

    # Per-sub-input follows (mirrors flake.nix `inputs.foo.inputs.bar.follows`)
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    disko.inputs.nixpkgs.follows = "nixpkgs";

    # Combined: keep the source, override some of its sub-inputs
    someFlake = {
        inputs.nixpkgs.follows = "nixpkgs";
        inputs.utils.follows   = "flake-utils";
    };

    # Takes the original sources.otherFlake and avoids flake call
    otherFlake = source: source // { flake = false; };
}

This second argument can also be a function resolvedInputs -> flakeInputs, this is useful for example to shim dependencies like systems or flake-utils [example].

self shape

All standard inputs.self.* patterns work:

inputs.self                    # the assembled self
inputs.self.inputs             # resolved inputs
inputs.self.inputs.self        # circular, lazy-safe
inputs.self.inputs.nixpkgs     # any resolved input
inputs.self.outputs            # raw outputs attrset
inputs.self.nixosConfigurations  # shorthand for inputs.self.outputs.nixosConfigurations

Resolved flake input shape

Every source with a flake.nix is fully resolved into the standard flake shape:

inputs.nixpkgs.outPath     # store / local path
inputs.nixpkgs.sourceInfo  # raw sourceInfo from sources
inputs.nixpkgs._type       # "flake"
inputs.nixpkgs.inputs      # nixpkgs' own resolved sub-inputs
inputs.nixpkgs.outputs     # nixpkgs' outputs attrset (explicit)
inputs.nixpkgs.lib         # shorthand — same as inputs.nixpkgs.outputs.lib

Dependency introspection works just like in flake-parts:

inputs.someFlake.inputs.nixpkgs          # someFlake's resolved nixpkgs
inputs.someFlake.inputs.nixpkgs.lib      # and its lib, etc.

Unresolvable follows

When a follows target doesn't exist in resolved inputs, the entry becomes null. Sub-flakes that declare that input as required will have their outputs call skipped (outputs stays {}), preventing evaluation errors — exactly like real flakes when a dependency is absent.

Input declaration quick reference

Input declaration Meaning
foo.outPath = ./path; Local checkout, loaded as flake if flake.nix present
foo = { outPath = ./path; inputs.dep.follows = "x"; }; Local checkout with sub-input overrides
foo = import ./path; Direct value, used as-is
foo = pinned-source; Direct value from npins or similar
b.follows = "a"; Alias to allInputs.a
b.follows = "a/x/y"; Nested alias via .inputs. chain
b.follows = ""; Empty — resolves to {}
a.inputs.b.follows = "x"; Override sub-input b of source a
a.inputs.b.follows = "x/y"; Override with nested follows
a = { inputs.b.follows = "x"; inputs.c.follows = "y"; }; Meta-spec: keep source, override several sub-inputs

A value is treated as a spec (not a direct value) when its only keys are follows and/or inputs, and every inputs.* value is a { follows = …; }. Anything with outPath, lib, packages, _type, etc. is a direct value.

Contributing

PR are welcome, make sure to run tests:

nix-unit tests.nix

Footnotes

  1. To use the experimental nix cli commands, create a flake.nix containing only

    { outputs = _: import ./.; }
    
  2. To additionally allow inputs overrides (eg, by a with-inputs-based consummer project):

    { outputs = inputsOverrides: import ./. { inherit inputsOverrides; }
    

About

A flake-inputs adapter for Nix projects that don't use `flake.nix`.

Topics

Resources

Stars

20 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages