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, unflake, nixlock, nixtamal.
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.nixOr use npins or builtins.fetchTarball with a fixed revision of it.
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 outputsThe 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.
{
# 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, …)
helper = import ./helper;
# 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";
};
}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