From dfe4fa9eab443255c8981eda6b585640452201dd Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 3 Sep 2023 17:55:09 +0200 Subject: [PATCH 1/9] tests/flakes/inputs: Add root case and test outPath vs sourceInfo.outPath on inputs --- tests/flakes/inputs.sh | 53 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/flakes/inputs.sh b/tests/flakes/inputs.sh index 80620488a56..a3fb27265e8 100644 --- a/tests/flakes/inputs.sh +++ b/tests/flakes/inputs.sh @@ -70,7 +70,9 @@ EOF }; outputs = inputs: rec { - packages = inputs.inp.packages; + packages = + assert inputs.inp.outPath == inputs.inp.sourceInfo.outPath + "/b-low"; + inputs.inp.packages; }; } EOF @@ -78,3 +80,52 @@ EOF } test_git_subdir_self_path + + +test_git_root_self_path() { + repoDir=$TEST_ROOT/repo-$RANDOM + createGitRepo $repoDir + writeSimpleFlake $repoDir + flakeDir=$repoDir + + echo all good > $flakeDir/message + cat > $flakeDir/flake.nix < $clientDir/flake.nix < Date: Sun, 3 Sep 2023 17:56:31 +0200 Subject: [PATCH 2/9] flakes: Add robust meta argument to outputs arguments This way, flake frameworks have access to the flake location even when an error causes `self` not to evaluate. --- src/libexpr/flake/call-flake.nix | 40 +++++++++++++++++++++++++------- src/nix/flake.md | 13 +++++++++++ tests/flakes/inputs.sh | 28 ++++++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index 4beb0b0fef3..bc0aa48b069 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -43,7 +43,37 @@ let (resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path}) (builtins.tail path); - outputs = flake.outputs (inputs // { self = result; }); + # Attributes that are added to the final representation of the flake. + # NB: `attrNames extraAttributes` must be lazy in `outputs` (tested). Values may depend on `outputs`. + extraAttributes = + sourceInfo + // { + # This shadows the sourceInfo.outPath + inherit outPath; + + inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; + }; + + meta = { + # The source root, which may not correspond to the flake directory. + inherit sourceInfo; + # The base directory of the flake + inherit subdir; + # Extra attributes in the final representation of the flake, added to result of the output function + inherit extraAttributes; + # Extra inputs to the output function + inherit extraArguments; + }; + + # NB: `attrNames arguments` must be lazy in `outputs` (tested). + arguments = inputs // extraArguments; + + extraArguments = { + self = result; + inherit meta; + }; + + outputs = flake.outputs arguments; result = outputs @@ -52,13 +82,7 @@ let # sourceInfo does not necessarily match the outPath of the flake, # as the flake may be in a subdirectory of a source. # This is shadowed in the next // - // sourceInfo - // { - # This shadows the sourceInfo.outPath - inherit outPath; - - inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; - }; + // extraAttributes; in if node.flake or true then diff --git a/src/nix/flake.md b/src/nix/flake.md index 92f477917fd..19df4673107 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -517,6 +517,19 @@ way. Most flakes provide their functionality through Nixpkgs overlays or NixOS modules, which are composed into the top-level flake's `nixpkgs` input; so their own `nixpkgs` input is usually irrelevant. +# Other arguments + +As discussed, `self` is an example of an `outputs` argument that is not derived from `inputs`. + +The following are the extra attributes that are always passed to `outputs`: + +* `meta`: An attribute set relating to the flake. It contains the attributes: + * `sourceInfo`: Equal to `self.sourceInfo`, but accessible even when `self` is broken due to an evaluation error. + * `subdir`: The subdirectory within `sourceInfo`, where `flake.nix` resides. This is `""` when `flake.nix` is at `${sourceInfo.outPath}/flake.nix`. + * `extraAttributes`: Attributes that are added to the flake outputs by `getFlake` or the internal `inputs` logic. + * `extraArguments`: A reference to the attributes documented here - essentially the `outputs` arguments that aren't `inputs`. +* `self`: This flake, including the attributes returned by `outputs` and `meta.extraAttributes`. It has the same shape as the result of `getFlake` or the inputs that are flakes. + # Lock files Inputs specified in `flake.nix` are typically "unlocked" in the sense diff --git a/tests/flakes/inputs.sh b/tests/flakes/inputs.sh index a3fb27265e8..a008946943c 100644 --- a/tests/flakes/inputs.sh +++ b/tests/flakes/inputs.sh @@ -29,6 +29,24 @@ EOF } test_subdir_self_path +test_extraAttributes_outPath_fail_safe() { + baseDir=$TEST_ROOT/$RANDOM + flakeDir=$baseDir + mkdir -p $flakeDir + writeSimpleFlake $baseDir + + cat > $flakeDir/flake.nix <<"EOF" +{ + outputs = inputs: + throw "can't evaluate self, because outputs fails to return any attribute names, but I know I can be identified as ${toString inputs.meta.extraAttributes.outPath}/flake.nix}"; +} +EOF + ( + expectStderr 1 nix build $flakeDir?dir=b-low --no-link | grep -E "can't evaluate self, because outputs fails to return any attribute names, but I know I can be identified as .*/flake.nix" + ) +} +test_extraAttributes_outPath_fail_safe + test_git_subdir_self_path() { repoDir=$TEST_ROOT/repo-$RANDOM @@ -47,6 +65,11 @@ test_git_subdir_self_path() { assert builtins.readFile ./message == "all good\n"; assert builtins.readFile (inputs.self + "/message") == "all good\n"; assert inputs.self.outPath == inputs.self.sourceInfo.outPath + "/b-low"; + assert inputs.meta.extraArguments.self == inputs.self; + assert inputs.meta.extraAttributes.outPath == inputs.self.outPath; + assert inputs.meta.sourceInfo.outPath + "/b-low" == inputs.self.outPath; + assert inputs.meta.sourceInfo.outPath == inputs.self.sourceInfo.outPath; + assert inputs.meta.subdir == "b-low"; import ./simple.nix; }; }; @@ -97,6 +120,11 @@ test_git_root_self_path() { assert builtins.readFile ./message == "all good\n"; assert builtins.readFile (inputs.self + "/message") == "all good\n"; assert inputs.self.outPath == inputs.self.sourceInfo.outPath; + assert inputs.meta.extraArguments.self == inputs.self; + assert inputs.meta.extraAttributes.outPath == inputs.self.outPath; + assert inputs.meta.sourceInfo.outPath == inputs.self.outPath; + assert inputs.meta.sourceInfo.outPath == inputs.self.sourceInfo.outPath; + assert inputs.meta.subdir == ""; import ./simple.nix; }; }; From d10bc47b54496ee43a0e01f70dbc14f04d52910b Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 4 Sep 2023 12:30:44 +0200 Subject: [PATCH 3/9] flakes: Reserve input names `self` and `meta` ... and do not add `meta` to the lock when formal is present, like we do for `self`. --- src/libexpr/flake/flake.cc | 6 ++++- tests/flakes/check.sh | 47 +++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 6a27ea2e8dc..7e5b6827812 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -183,6 +183,10 @@ static std::map parseFlakeInputs( expectType(state, nAttrs, *value, pos); for (nix::Attr & inputAttr : *(*value).attrs) { + + if (inputAttr.name == state.sSelf || inputAttr.name == state.sMeta) + throw Error("flake input name '%s' is reserved", state.symbols[inputAttr.name]); + inputs.emplace(state.symbols[inputAttr.name], parseFlakeInput(state, state.symbols[inputAttr.name], @@ -244,7 +248,7 @@ static Flake getFlake( if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) { for (auto & formal : outputs->value->lambda.fun->formals->formals) { - if (formal.name != state.sSelf) + if (!(formal.name == state.sSelf || formal.name == state.sMeta)) flake.inputs.emplace(state.symbols[formal.name], FlakeInput { .ref = parseFlakeRef(state.symbols[formal.name]) }); diff --git a/tests/flakes/check.sh b/tests/flakes/check.sh index 0433e5335de..8ce024cc3f9 100644 --- a/tests/flakes/check.sh +++ b/tests/flakes/check.sh @@ -1,7 +1,15 @@ source common.sh flakeDir=$TEST_ROOT/flake3 -mkdir -p $flakeDir +depDir=$TEST_ROOT/flakedep +mkdir -p $flakeDir $depDir + +cat > $depDir/flake.nix < $flakeDir/flake.nix <&1 && fail "nix flake check --all-systems should have failed" || true) echo "$checkRes" | grepQuiet "packages.system-1.default" echo "$checkRes" | grepQuiet "packages.system-2.default" + +cat > $flakeDir/flake.nix < $flakeDir/flake.nix < $flakeDir/flake.nix < Date: Mon, 4 Sep 2023 12:11:02 +0200 Subject: [PATCH 4/9] libexpr: Add more builtins.function* reflection primops These are relevant for determining compatibility properties of functions. - Can I add an attribute? Is it open for extension? - If I omit an attribute that I'd be expected to add, does that lead to a conflict? Such a conflict arises when the function is closed and also binds all attributes using `@` syntax. It also answers a related question: is the function defined using the strict syntax? This is useful for explaining the misconception that changing a plain lambda to a strict lambda is a no-op refactor. The fact that it makes the function strict is overlooked and the error message, infinite recursion sends users into panic mode. With the new functionStrict primop we can write functions that catch the mistake before we enter it, which could be very helpful. --- doc/manual/src/release-notes/rl-next.md | 9 ++ src/libexpr/primops.cc | 103 +++++++++++++++++++ tests/lang/eval-okay-function-reflection.exp | 1 + tests/lang/eval-okay-function-reflection.nix | 55 ++++++++++ 4 files changed, 168 insertions(+) create mode 100644 tests/lang/eval-okay-function-reflection.exp create mode 100644 tests/lang/eval-okay-function-reflection.nix diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 02ad0a661b7..f5880282c21 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -7,6 +7,15 @@ have been added. These functions are useful for converting between flake references encoded as attribute sets and URLs. +- Function introspection in the language has been extended to allow more compatibility logic to be written. + + - [`builtins.functionOpen`](@docroot@/language/builtins.md#builtins-functionOpen): whether arbitrary attributes can be passed to the function; a boolean, or `null` for plain lambdas like `x: x`. + - [`builtins.functionBindsAllAttrs`](@docroot@/language/builtins.md#builtins-functionBindsAllAttrs): whether the function puts the whole attrset into a variable with `@`. + + The combination of being closed, but binding all attributes is not forward compatible and can now be reported as part of migrations that add an attribute to a function call. + +- [`builtins.functionStrict`](@docroot@/language/builtins.md#builtins-functionStrict): whether the function is written using strict syntax, such as `{ ... }: foo`. The fact that this function is strict in its argument is often forgotten, so this allows library or DSL authors to detect and report it in places where it might be common to encounter this. + - [`builtins.toJSON`](@docroot@/language/builtins.md#builtins-parseFlakeRef) now prints [--show-trace](@docroot@/command-ref/conf-file.html#conf-show-trace) items for the path in which it finds an evaluation error. - Error messages regarding malformed input to [`derivation add`](@docroot@/command-ref/new-cli/nix3-derivation-add.md) are now clearer and more detailed. diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index e2b1ac4f65e..bf70fe57be4 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2750,6 +2750,109 @@ static RegisterPrimOp primop_catAttrs({ .fun = prim_catAttrs, }); +static void prim_functionStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + state.forceValue(*args[0], pos); + if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) { + v.mkNull(); + return; + } + + state.forceFunction(*args[0], pos, "while evaluating the argument passed to builtins.functionStrict"); + const auto fun = args[0]->lambda.fun; + + if (args[0]->isLambda() && fun->hasFormals()) { + v.mkBool(true); + } else { + v.mkNull(); + } +} + +static RegisterPrimOp primop_functionStrict({ + .name = "__functionStrict", + .args = {"f"}, + .doc = R"( + Return `true` if *f* is a function that is defined using strict function + syntax, which is to say, using an argument attribute set declaration such + as `{ a }:` or `{ ... }:`. + + Proper strictness analysis is undecidable, so for all other functions the + return value is `null`. `false` is not returned. + )", + .fun = prim_functionStrict, +}); + +static void prim_functionOpen(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + state.forceValue(*args[0], pos); + if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) { + v.mkNull(); + return; + } + + state.forceFunction(*args[0], pos, "while evaluating the argument passed to builtins.functionOpen"); + const auto fun = args[0]->lambda.fun; + + if (args[0]->isLambda()) { + v.mkNull(); + } + + if (fun->hasFormals()) { + v.mkBool(fun->formals->ellipsis); + } else { + v.mkNull(); + } +} + +static RegisterPrimOp primop_functionOpen({ + .name = "__functionOpen", + .args = {"f"}, + .doc = R"( + Return `true` if *f* is a function that is defined using the ellipsis syntax, such as `{ ... }:` or `{ foo, ... }:`. + + Return `false` for functions defined with an attribute list but no ellipsis, such as `{ foo, bar }:`. + + Return `null` for functions defined using plain lambdas, such as `x: ...`, as well as for built-in functions. + )", + .fun = prim_functionOpen, +}); + +static void prim_functionBindsAllAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + state.forceValue(*args[0], pos); + if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) { + v.mkNull(); + return; + } + + state.forceFunction(*args[0], pos, "while evaluating the argument passed to builtins.functionBindsAllAttrs"); + const auto fun = args[0]->lambda.fun; + + if (!args[0]->isLambda()) { + v.mkNull(); + return; + } + + if (fun->hasFormals()) { + v.mkBool(fun->arg != Symbol {}); + } else { + v.mkNull(); + } +} + +static RegisterPrimOp primop_functionBindsAllAttrs({ + .name = "__functionBindsAllAttrs", + .args = {"f"}, + .doc = R"( + If the function is not defined with an argument list, return `null`. + + Return `true` if *f* is a function that is defined using the `@` syntax, which binds an identifier to the original attribute set. + + Return `false` for a function that does not use the `@` syntax. + )", + .fun = prim_functionBindsAllAttrs, +}); + static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); diff --git a/tests/lang/eval-okay-function-reflection.exp b/tests/lang/eval-okay-function-reflection.exp new file mode 100644 index 00000000000..ffcd4415b08 --- /dev/null +++ b/tests/lang/eval-okay-function-reflection.exp @@ -0,0 +1 @@ +{ } diff --git a/tests/lang/eval-okay-function-reflection.nix b/tests/lang/eval-okay-function-reflection.nix new file mode 100644 index 00000000000..8e140769b7d --- /dev/null +++ b/tests/lang/eval-okay-function-reflection.nix @@ -0,0 +1,55 @@ +let + inherit (builtins) mapAttrs; + reflect = f: { + args = builtins.functionArgs f; + strict = builtins.functionStrict f; + open = builtins.functionOpen f; + bindAll = builtins.functionBindsAllAttrs f; + }; + checkCase = k: ({ input, output }: + let actual = reflect input; + in + if actual == output + then [] + else + builtins.trace "\nTest case ${k} failed. Expected:" + builtins.trace output + builtins.trace "Actual:" + builtins.trace actual + [k]); + run = + let fails = builtins.concatLists (builtins.attrValues (mapAttrs checkCase cases)); + in if fails == [ ] then { } + else throw ("${toString (builtins.length fails)} FAILED\n" + builtins.concatStringsSep "\n" fails); + + cases = { + plain.input = x: x; + plain.output = { args = {}; bindAll = null; open = null; strict = null; }; + strict.input = x@{ ... }: x; + strict.output = { args = {}; bindAll = true; open = true; strict = true; }; + + closedEmpty.input = { }: null; + closedEmpty.output = { args = {}; bindAll = false; open = false; strict = true; }; + closedEmptyBind.input = x@{ }: null; + closedEmptyBind.output = { args = {}; bindAll = true; open = false; strict = true; }; + openEmpty.input = { ... }: null; + openEmpty.output = { args = {}; bindAll = false; open = true; strict = true; }; + openEmptyBind.input = x@{ ... }: null; + openEmptyBind.output = { args = {}; bindAll = true; open = true; strict = true; }; + closed.input = { name, value }: null; + closed.output = { args = { "name" = false; "value" = false; }; bindAll = false; open = false; strict = true; }; + closedBind.input = x@{ name, value }: null; + closedBind.output = { args = { "name" = false; "value" = false; }; bindAll = true; open = false; strict = true; }; + open.input = { ... }: null; + open.output = { args = {}; bindAll = false; open = true; strict = true; }; + openBind.input = x@{ name, value, def ? null, ... }: null; + openBind.output = { args = { "name" = false; "value" = false; "def" = true; }; bindAll = true; open = true; strict = true; }; + + primop.input = builtins.concatLists; + primop.output = { args = {}; bindAll = null; open = null; strict = null; }; + primopApp.input = builtins.mapAttrs (k: v: null); + primopApp.output = { args = {}; bindAll = null; open = null; strict = null; }; + }; + +in + run From e9e1897a63ecbcb589b31af3d8da1bef5dadc09d Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 4 Sep 2023 12:29:31 +0200 Subject: [PATCH 5/9] flakes: Add compatibility for most outputs functions that don't accept --- doc/manual/src/release-notes/rl-next.md | 4 ++++ src/libexpr/flake/call-flake.nix | 23 ++++++++++++++++++- tests/flakes/check.sh | 30 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index f5880282c21..953e1c4e003 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -32,6 +32,10 @@ - Introduce a new [`outputOf`](@docroot@/language/builtins.md#builtins-outputOf) builtin. It is part of the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature. +- The flake output function now accepts a parameter `meta`, which gives access to `sourceInfo` and such parameters in a robust manner, even when `self` can not be evaluated. This helps frameworks report error messages with locations, as trying to access the location previously resulted in an unhelpful infinite recursion when the error condition caused `self` to fail. + + If an `outputs` function has a binding for all attributes (using `@`), you will be asked to add an ellipsis to avoid later confusion as to why `meta` is missing - which is necessary because of a limitation of Nix function semantics. This warning may occur in a dependency. + - Flake follow paths at depths greater than 2 are now handled correctly, preventing "follows a non-existent input" errors. - [`nix-store --query`](@docroot@/command-ref/nix-store/query.md) gained a new type of query: `--valid-derivers`. It returns all `.drv` files in the local store that *can be* used to build the output passed in argument. diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index bc0aa48b069..8b6a67644aa 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -2,6 +2,10 @@ lockFileStr: rootSrc: rootSubdir: let + warn = msg: builtins.trace "${emphasize "warning"}: ${msg}"; + emphasize = x: "${x}"; + optional = cond: thing: if cond then [ thing ] else []; + lockFile = builtins.fromJSON lockFileStr; allNodes = @@ -66,7 +70,24 @@ let }; # NB: `attrNames arguments` must be lazy in `outputs` (tested). - arguments = inputs // extraArguments; + arguments = inputs // extraArgumentsCompat; + + # Find out if we have a conflict between a missing meta argument and the need for `meta` attr to be added to the @ binding. + # If possible, fix it up without complaining. + extraArgumentsCompat = builtins.removeAttrs extraArguments ( + let + # NB: some of these builtins can return null, hence the == + isClosed = builtins.functionOpen flake.outputs == false; + acceptsMeta = !isClosed || (builtins.functionArgs flake.outputs)?meta; + canRemove = isClosed && (builtins.functionBindsAllAttrs flake.outputs == false); + + removals = if acceptsMeta then [] else [ "meta" ]; + checked = if !acceptsMeta && !canRemove then warning else x: x; + warning = warn + "in flake ${toString outPath}: The flake's ${emphasize "outputs"} function does not accept the ${emphasize "meta"} argument.\nThis will become an error.\nPlease add ellipsis (${emphasize "..."}) to the function header for it to be compatible with both dated and upcoming versions of Flakes. Example use of ellipsis: ${emphasize "outputs = { self, ... }: "}."; + in + checked removals + ); extraArguments = { self = result; diff --git a/tests/flakes/check.sh b/tests/flakes/check.sh index 8ce024cc3f9..9c1e6880f39 100644 --- a/tests/flakes/check.sh +++ b/tests/flakes/check.sh @@ -134,3 +134,33 @@ cat > $flakeDir/flake.nix < $flakeDir/flake.nix <&1 | grep -F "Please add ellipsis" + + +cat > $flakeDir/flake.nix <&1 | grep -v "Please add ellipsis" From 2cf4b75f2eaafe3f4debfbf9074c2ddabc5f7c75 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 4 Sep 2023 12:40:21 +0200 Subject: [PATCH 6/9] tests: Rename outputs arguments to args instead of inputs Inputs is not quite an appropriate name because of `self` and `meta`, which aren't inputs. --- tests/flakes/common.sh | 2 +- tests/flakes/flakes.sh | 14 ++++++------ tests/flakes/inputs.sh | 52 +++++++++++++++++++++--------------------- tests/flakes/show.sh | 4 ++-- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/flakes/common.sh b/tests/flakes/common.sh index 427abcdde0c..7ae6fd3e635 100644 --- a/tests/flakes/common.sh +++ b/tests/flakes/common.sh @@ -8,7 +8,7 @@ writeSimpleFlake() { { description = "Bla bla"; - outputs = inputs: rec { + outputs = args: rec { packages.$system = rec { foo = import ./simple.nix; default = foo; diff --git a/tests/flakes/flakes.sh b/tests/flakes/flakes.sh index 128f759ea41..f7661fe7733 100644 --- a/tests/flakes/flakes.sh +++ b/tests/flakes/flakes.sh @@ -246,20 +246,20 @@ cat > $flake3Dir/flake.nix < \$out - [[ \$(cat \${inputs.nonFlake}/README.md) = \$(cat \${inputs.nonFlakeFile}) ]] - [[ \${inputs.nonFlakeFile} = \${inputs.nonFlakeFile2} ]] + cat \${args.nonFlake}/README.md > \$out + [[ \$(cat \${args.nonFlake}/README.md) = \$(cat \${args.nonFlakeFile}) ]] + [[ \${args.nonFlakeFile} = \${args.nonFlakeFile2} ]] ''; }; }; diff --git a/tests/flakes/inputs.sh b/tests/flakes/inputs.sh index a008946943c..eacab7056a2 100644 --- a/tests/flakes/inputs.sh +++ b/tests/flakes/inputs.sh @@ -13,11 +13,11 @@ test_subdir_self_path() { echo all good > $flakeDir/message cat > $flakeDir/flake.nix < $flakeDir/flake.nix <<"EOF" { - outputs = inputs: - throw "can't evaluate self, because outputs fails to return any attribute names, but I know I can be identified as ${toString inputs.meta.extraAttributes.outPath}/flake.nix}"; + outputs = args: + throw "can't evaluate self, because outputs fails to return any attribute names, but I know I can be identified as ${toString args.meta.extraAttributes.outPath}/flake.nix}"; } EOF ( @@ -59,17 +59,17 @@ test_git_subdir_self_path() { echo all good > $flakeDir/message cat > $flakeDir/flake.nix < $flakeDir/message cat > $flakeDir/flake.nix <flake.nix <flake.nix < Date: Mon, 4 Sep 2023 13:09:11 +0200 Subject: [PATCH 7/9] rl-next: Two functions -> A pair of functions This doesn't suggest that two is the total count, and I think it's a nice way to phrase it, because the functions are related. --- doc/manual/src/release-notes/rl-next.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 953e1c4e003..1c995f19f42 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,10 +1,10 @@ # Release X.Y (202?-??-??) -- Two new builtin functions, +- A pair of builtin functions, [`builtins.parseFlakeRef`](@docroot@/language/builtins.md#builtins-parseFlakeRef) and [`builtins.flakeRefToString`](@docroot@/language/builtins.md#builtins-flakeRefToString), - have been added. + has been added. These functions are useful for converting between flake references encoded as attribute sets and URLs. - Function introspection in the language has been extended to allow more compatibility logic to be written. From 1f739fff3f17acc197ba8ab9568d478c9100e54f Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 4 Sep 2023 13:36:08 +0200 Subject: [PATCH 8/9] refactor: Introduce isRoot in call-flake.nix --- src/libexpr/flake/call-flake.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index 8b6a67644aa..617723f33fc 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -12,13 +12,15 @@ let builtins.mapAttrs (key: node: let + # Flakes should be interchangeable regardless of whether they're at the root, so use with care. + isRoot = key == lockFile.root; sourceInfo = - if key == lockFile.root + if isRoot then rootSrc else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]); - subdir = if key == lockFile.root then rootSubdir else node.locked.dir or ""; + subdir = if isRoot then rootSubdir else node.locked.dir or ""; outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir); From 72675eb81b1ebf873afcda1d600d31db4e3ff59d Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 4 Sep 2023 13:36:59 +0200 Subject: [PATCH 9/9] flakes: Don't warn about meta and ellipsis except for root flake ... and fix an incorrect use of `grep -v` --- doc/manual/src/release-notes/rl-next.md | 2 +- src/libexpr/flake/call-flake.nix | 2 +- tests/flakes/check.sh | 32 ++++++++++++++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 1c995f19f42..b2e0911b9d1 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -34,7 +34,7 @@ - The flake output function now accepts a parameter `meta`, which gives access to `sourceInfo` and such parameters in a robust manner, even when `self` can not be evaluated. This helps frameworks report error messages with locations, as trying to access the location previously resulted in an unhelpful infinite recursion when the error condition caused `self` to fail. - If an `outputs` function has a binding for all attributes (using `@`), you will be asked to add an ellipsis to avoid later confusion as to why `meta` is missing - which is necessary because of a limitation of Nix function semantics. This warning may occur in a dependency. + If an `outputs` function has a binding for all attributes (using `@`), you will be asked to add an ellipsis to avoid later confusion as to why `meta` is missing - which is necessary because of a limitation of Nix function semantics. - Flake follow paths at depths greater than 2 are now handled correctly, preventing "follows a non-existent input" errors. diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index 617723f33fc..75d59a074eb 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -84,7 +84,7 @@ let canRemove = isClosed && (builtins.functionBindsAllAttrs flake.outputs == false); removals = if acceptsMeta then [] else [ "meta" ]; - checked = if !acceptsMeta && !canRemove then warning else x: x; + checked = if isRoot && !acceptsMeta && !canRemove then warning else x: x; warning = warn "in flake ${toString outPath}: The flake's ${emphasize "outputs"} function does not accept the ${emphasize "meta"} argument.\nThis will become an error.\nPlease add ellipsis (${emphasize "..."}) to the function header for it to be compatible with both dated and upcoming versions of Flakes. Example use of ellipsis: ${emphasize "outputs = { self, ... }: "}."; in diff --git a/tests/flakes/check.sh b/tests/flakes/check.sh index 9c1e6880f39..a681effb390 100644 --- a/tests/flakes/check.sh +++ b/tests/flakes/check.sh @@ -2,7 +2,8 @@ source common.sh flakeDir=$TEST_ROOT/flake3 depDir=$TEST_ROOT/flakedep -mkdir -p $flakeDir $depDir +depDirB=$TEST_ROOT/flakedep2 +mkdir -p $flakeDir $depDir $depDirB cat > $depDir/flake.nix < $flakeDir/flake.nix < $depDirB/flake.nix +cat > $depDirB/flake.nix <&1 | grep -F "Please add ellipsis" + +cat $depDirB/flake.nix + +# However it should not warn when the flake is used as a dependency, because in that case the user may not own the flake and can't change it. If they do own it, they only need to know about it when working on the flake itself. +cat > $flakeDir/flake.nix <&1 | grep -F "Please add ellipsis" +nix flake check $flakeDir 2>&1 | grepQuietInverse "Please add ellipsis" cat > $flakeDir/flake.nix < $flakeDir/flake.nix <&1 | grep -v "Please add ellipsis" +nix flake check $flakeDir 2>&1 | grepQuietInverse "Please add ellipsis"