From ec9f9b1acc0c4c2d04938b99a86a6b9000261d71 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Sun, 13 Sep 2026 12:16:22 +0200 Subject: [PATCH 1/3] fix: overrideSubSpec to handle case where input override take source arg --- tests.nix | 15 +++++++++++++++ with-inputs.nix | 6 ++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests.nix b/tests.nix index dc85ecb..cb053df 100644 --- a/tests.nix +++ b/tests.nix @@ -369,6 +369,21 @@ in expected = npins.nixpkgs.outPath; }; + introspection.test-follow-sub-npins-with-inputs-input-with-source-arg = { + # s: my-lib.inputs.nixpkgs.follows = "with-inputs-dep/nixpkgs" → traverse native inputs + expr = + (with-inputs + { + my-lib = mkSrc ./fixtures/fake-flake; + with-inputs-dep = mkSrc ./fixtures/with-inputs-flake; + } + { + my-lib = s: { inputs.nixpkgs.follows = "with-inputs-dep/nixpkgs2"; }; + } + ).my-lib.inputs.nixpkgs.outPath; + expected = npins.nixpkgs.outPath; + }; + real-flakes.test-npins-nix-maid-nixosModules-output-is-readable = { expr = (with-inputs npins { } (inputs: { diff --git a/with-inputs.nix b/with-inputs.nix index dedc559..8d300ec 100644 --- a/with-inputs.nix +++ b/with-inputs.nix @@ -62,11 +62,9 @@ let hostName: subName: let entry = inputs.${hostName} or null; + getSubInput = e: if builtins.isAttrs e && e ? inputs then e.inputs.${subName} or null else null; in - if entry != null && builtins.isAttrs entry && entry ? inputs then - entry.inputs.${subName} or null - else - null; + getSubInput (if builtins.isFunction entry then (entry sources.${hostName}) else entry); # Resolve an inputs entry to an actual input value, or null if unresolvable. # Values with outPath but no _type go through mkInput so their flake.nix is loaded. From 1091bc48d5b342465730288eb4a50cd940f62878 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Sun, 13 Sep 2026 17:12:12 +0200 Subject: [PATCH 2/3] fix: use default.nix to inject inputsOverrides if available using the flake api breaks when flakes use the following pattern: ```nix { outputs = args: let f = { self }: ...; in f args; } ``` (which is the case of nixpkgs-lib). --- README.md | 5 +- fixtures/with-inputs-flake/flake.nix | 1 - .../default.nix | 0 tests.nix | 6 +- with-inputs.nix | 81 +++++++++++-------- 5 files changed, 52 insertions(+), 41 deletions(-) delete mode 100644 fixtures/with-inputs-flake/flake.nix rename fixtures/{with-inputs-flake => with-inputs-project}/default.nix (100%) diff --git a/README.md b/README.md index 053387b..1764d47 100644 --- a/README.md +++ b/README.md @@ -106,15 +106,12 @@ let in with-inputs outputs ``` +Consumer projects that use `with-inputs` will automatically inject their own `inputs` as `inputsOverrides`. [^output-trick]: To use the experimental `nix` cli commands, create a `flake.nix` containing only ```nix { outputs = _: import ./.; } ``` -[^output-trick-2]: To additionally allow inputs overrides (eg, by a `with-inputs`-based consummer project): - ```nix - { outputs = inputsOverrides: import ./. { inherit inputsOverrides; } - ``` ### 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. diff --git a/fixtures/with-inputs-flake/flake.nix b/fixtures/with-inputs-flake/flake.nix deleted file mode 100644 index 4a3f569..0000000 --- a/fixtures/with-inputs-flake/flake.nix +++ /dev/null @@ -1 +0,0 @@ -{ outputs = inputsOverrides: import ./. { inherit inputsOverrides; }; } diff --git a/fixtures/with-inputs-flake/default.nix b/fixtures/with-inputs-project/default.nix similarity index 100% rename from fixtures/with-inputs-flake/default.nix rename to fixtures/with-inputs-project/default.nix diff --git a/tests.nix b/tests.nix index cb053df..166fe54 100644 --- a/tests.nix +++ b/tests.nix @@ -360,7 +360,7 @@ in (with-inputs { my-lib = mkSrc ./fixtures/fake-flake; - with-inputs-dep = mkSrc ./fixtures/with-inputs-flake; + with-inputs-dep = mkSrc ./fixtures/with-inputs-project; } { my-lib.inputs.nixpkgs.follows = "with-inputs-dep/nixpkgs2"; @@ -375,7 +375,7 @@ in (with-inputs { my-lib = mkSrc ./fixtures/fake-flake; - with-inputs-dep = mkSrc ./fixtures/with-inputs-flake; + with-inputs-dep = mkSrc ./fixtures/with-inputs-project; } { my-lib = s: { inputs.nixpkgs.follows = "with-inputs-dep/nixpkgs2"; }; @@ -422,7 +422,7 @@ in (with-inputs { my-lib = mkFlake { nixpkgs = mkSrc "/nested-nixpkgs"; } { }; - with-inputs-dep = mkSrc ./fixtures/with-inputs-flake; + with-inputs-dep = mkSrc ./fixtures/with-inputs-project; } { with-inputs-dep.inputs.nixpkgs2.follows = "my-lib/nixpkgs"; diff --git a/with-inputs.nix b/with-inputs.nix index 8d300ec..65b6757 100644 --- a/with-inputs.nix +++ b/with-inputs.nix @@ -106,15 +106,58 @@ let let hasPath = sourceInfo ? outPath; isFlake = hasPath && (sourceInfo.flake or true); + defaultPath = sourceInfo.outPath + "/default.nix"; + defaultNix = import defaultPath; + defaultArgs = builtins.functionArgs defaultNix; + hadDefaultValues = builtins.all (withDefault: withDefault) (builtins.attrValues defaultArgs); + defaultExists = isFlake && builtins.pathExists defaultPath; + defaultGood = builtins.tryEval ( + defaultExists && builtins.isFunction defaultNix && defaultArgs ? inputsOverrides && hadDefaultValues + ); flakePath = sourceInfo.outPath + "/flake.nix"; flakeExists = isFlake && builtins.pathExists flakePath; - allGood = builtins.tryEval flakeExists; + flakeGood = builtins.tryEval flakeExists; in - if allGood.success && allGood.value then + if defaultGood.success && defaultGood.value then + mkDefaultWithInputsInput name sourceInfo defaultNix + else if flakeGood.success && flakeGood.value then mkFlakeInput name sourceInfo (import flakePath) else sourceInfo // { inherit sourceInfo; }; + mkDefaultWithInputsInput = + name: sourceInfo: defaultNix: + let + inputsOverrides = + let + recFollows = + let + follows = + input: + isFollows inputs.${input} + && ( + let + followRoot = builtins.head (builtins.split "/" inputs.${input}.follows); + in + followRoot == name || follows followRoot + ); + in + [ name ] ++ builtins.filter follows (builtins.attrNames inputs); + in + removeAttrs allInputs recFollows + // (builtins.mapAttrs (sub: spec: resolveSubInput name sub spec) (inputs.${name}.inputs or { })); + outputs = defaultNix { inherit inputsOverrides; }; + self = + sourceInfo + // outputs + // { + _type = "flake"; + inputs = outputs.inputs or { inherit self; }; + inherit outputs sourceInfo; + }; + in + self; + mkFlakeInput = name: sourceInfo: flake: let @@ -127,42 +170,14 @@ let builtins.functionArgs flake.outputs ); nonEmptyInputs = direct != { } || indirect != { }; - inputs = - if nonEmptyInputs then - indirect // direct - else - # Assume inputs are not handled by flake, but output function - # may still accept inputs overrides: we give it allInputs minus - # those that would obviously trigger infinite recursion - # (inputs defined as follows of inputs of the flake we are importing) - # plus inputs overrides declared for this flake. - let - recFollows = - let - follows = - input: - isFollows topLevelInputs.${input} - && ( - let - followRoot = builtins.head (builtins.split "/" topLevelInputs.${input}.follows); - in - followRoot == name || follows followRoot - ); - in - [ name ] ++ builtins.filter follows (builtins.attrNames topLevelInputs); - in - removeAttrs allInputs recFollows - // (builtins.mapAttrs (sub: spec: resolveSubInput name sub spec) ( - topLevelInputs.${name}.inputs or { } - )); - outputs = flake.outputs (inputs // { inherit self; }); + inputs = indirect // direct // { inherit self; }; + outputs = flake.outputs inputs; self = sourceInfo // outputs // { _type = "flake"; - inputs = if nonEmptyInputs then inputs else outputs.inputs or { inherit self; }; - inherit outputs sourceInfo; + inherit outputs inputs sourceInfo; }; in self; From a5fc951ce258e4bb36cc9f0d808ee80877481a52 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Thu, 1 Oct 2026 14:14:52 +0200 Subject: [PATCH 3/3] fix: prefer flake.nix over default.nix if flake.inputs are not empty. this avoid failing on importing default.nix in flake context. --- with-inputs.nix | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/with-inputs.nix b/with-inputs.nix index 65b6757..29de4ec 100644 --- a/with-inputs.nix +++ b/with-inputs.nix @@ -117,11 +117,21 @@ let flakePath = sourceInfo.outPath + "/flake.nix"; flakeExists = isFlake && builtins.pathExists flakePath; flakeGood = builtins.tryEval flakeExists; + in - if defaultGood.success && defaultGood.value then + if flakeGood.success && flakeGood.value then + let + flake = import flakePath; + specs = flake.inputs or { }; + # Prefer default.nix if flake inputs specs is empty, + # unless in a flake evaluation context to avoid loading flake-compat (which would fail for using builtins.currentSystem) + in + if (specs == { } && builtins ? currentSystem && defaultGood.success && defaultGood.value) then + mkDefaultWithInputsInput name sourceInfo defaultNix + else + mkFlakeInput name sourceInfo flake + else if defaultGood.success && defaultGood.value then mkDefaultWithInputsInput name sourceInfo defaultNix - else if flakeGood.success && flakeGood.value then - mkFlakeInput name sourceInfo (import flakePath) else sourceInfo // { inherit sourceInfo; };