with-inputs and vic's dendritic libs made for you with Love++ and AI--. If you like my work, consider sponsoring
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.
with-inputs sources follows outputsThe with-inputs function takes three arguments:
- already fetched
<name>.outPathattrs. - a
specsattrs or functioninputs: specs(or a list of such function/attrs) for custom follows, input shims or sources overrides. - a function
inputs: outputslike 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.
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
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
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.nixOr 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 outputsor 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 outputsWhen 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.
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].
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.nixosConfigurationsEvery 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.libDependency introspection works just like in flake-parts:
inputs.someFlake.inputs.nixpkgs # someFlake's resolved nixpkgs
inputs.someFlake.inputs.nixpkgs.lib # and its lib, etc.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 | 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.
PR are welcome, make sure to run tests:
nix-unit tests.nix