Sponsor Vic Dendritic Nix License CI Status
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 outputs
The with-inputs function takes three arguments:
- already fetched
<name>.outPathattrs. - a function
inputs: specsfor 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/vic/with-inputs/refs/heads/main/default.nix -o with-inputs.nix
Or use npins or builtins.fetchTarball with a fixed revision of it. 1
npins add github vic 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
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.
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.nixosConfigurations
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.
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
Footnotes
-
To use the experimental
nixcli commands, create aflake.nixcontaining only
↩{ outputs = _: import ./.; }