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 dc85ecb..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"; @@ -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-project; + } + { + 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: { @@ -407,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 dedc559..29de4ec 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. @@ -108,15 +106,68 @@ 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 - mkFlakeInput name sourceInfo (import flakePath) + 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 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 @@ -129,42 +180,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;