1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-09 12:06:01 +01:00

Parse deriving paths in DerivationOptions

This is an example of "Parse, don't validate" principle [1].

Before, we had a number of `StringSet`s in `DerivationOptions` that
were not *actually* allowed to be arbitrary sets of strings. Instead,
each set member had to be one of:

- a store path

- a CA "downstream placeholder"

- an output name

Only later, in the code that checks outputs, would these strings be
further parsed to match these cases. (Actually, only 2 by that point,
because the placeholders must be rewritten away by then.)

Now, we fully parse everything up front, and have an "honest" data type
that reflects these invariants:

- store paths are parsed, stored as (opaque) deriving paths

- CA "downstream placeholders" are rewritten to the output deriving
  paths they denote

- output names are the only arbitrary strings left

Since the first two cases both become deriving paths, that leaves us
with a `std::variant<SingleDerivedPath, String>` data type, which we use
in our sets instead.

Getting rid of placeholders is especially nice because we are replacing
them with something much more internally-structured / transparent.

[1]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
This commit is contained in:
John Ericson 2025-04-14 14:26:13 -04:00
parent c5f348db95
commit 00d2bf91b2
22 changed files with 701 additions and 244 deletions

View file

@ -4,10 +4,13 @@
"allowSubstitutes": false, "allowSubstitutes": false,
"exportReferencesGraph": { "exportReferencesGraph": {
"refs1": [ "refs1": [
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9" {
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
"output": "out"
}
], ],
"refs2": [ "refs2": [
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv" "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
] ]
}, },
"impureEnvVars": [ "impureEnvVars": [
@ -20,18 +23,36 @@
"outputChecks": { "outputChecks": {
"forAllOutputs": { "forAllOutputs": {
"allowedReferences": [ "allowedReferences": [
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9" {
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
"output": "out"
}
], ],
"allowedRequisites": [ "allowedRequisites": [
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z", {
"bin" "drvPath": "self",
"output": "bin"
},
{
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
"output": "dev"
}
], ],
"disallowedReferences": [ "disallowedReferences": [
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g", {
"dev" "drvPath": "self",
"output": "dev"
},
{
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
"output": "out"
}
], ],
"disallowedRequisites": [ "disallowedRequisites": [
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8" {
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
"output": "dev"
}
], ],
"ignoreSelfRefs": true, "ignoreSelfRefs": true,
"maxClosureSize": null, "maxClosureSize": null,

View file

@ -4,10 +4,13 @@
"allowSubstitutes": false, "allowSubstitutes": false,
"exportReferencesGraph": { "exportReferencesGraph": {
"refs1": [ "refs1": [
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9" {
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
"output": "out"
}
], ],
"refs2": [ "refs2": [
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv" "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
] ]
}, },
"impureEnvVars": [ "impureEnvVars": [
@ -23,11 +26,20 @@
"allowedReferences": null, "allowedReferences": null,
"allowedRequisites": null, "allowedRequisites": null,
"disallowedReferences": [ "disallowedReferences": [
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g", {
"dev" "drvPath": "self",
"output": "dev"
},
{
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
"output": "out"
}
], ],
"disallowedRequisites": [ "disallowedRequisites": [
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8" {
"drvPath": "qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
"output": "dev"
}
], ],
"ignoreSelfRefs": false, "ignoreSelfRefs": false,
"maxClosureSize": null, "maxClosureSize": null,
@ -44,11 +56,20 @@
}, },
"out": { "out": {
"allowedReferences": [ "allowedReferences": [
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9" {
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
"output": "out"
}
], ],
"allowedRequisites": [ "allowedRequisites": [
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z", {
"bin" "drvPath": "self",
"output": "bin"
},
{
"drvPath": "j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",
"output": "dev"
}
], ],
"disallowedReferences": [], "disallowedReferences": [],
"disallowedRequisites": [], "disallowedRequisites": [],

View file

@ -4,10 +4,10 @@
"allowSubstitutes": false, "allowSubstitutes": false,
"exportReferencesGraph": { "exportReferencesGraph": {
"refs1": [ "refs1": [
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo" "p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
], ],
"refs2": [ "refs2": [
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv" "vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
] ]
}, },
"impureEnvVars": [ "impureEnvVars": [
@ -20,18 +20,24 @@
"outputChecks": { "outputChecks": {
"forAllOutputs": { "forAllOutputs": {
"allowedReferences": [ "allowedReferences": [
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo" "p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
], ],
"allowedRequisites": [ "allowedRequisites": [
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev", {
"bin" "drvPath": "self",
"output": "bin"
},
"z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
], ],
"disallowedReferences": [ "disallowedReferences": [
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar", {
"dev" "drvPath": "self",
"output": "dev"
},
"r5cff30838majxk5mp3ip2diffi8vpaj-bar"
], ],
"disallowedRequisites": [ "disallowedRequisites": [
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev" "9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
], ],
"ignoreSelfRefs": true, "ignoreSelfRefs": true,
"maxClosureSize": null, "maxClosureSize": null,

View file

@ -4,10 +4,10 @@
"allowSubstitutes": false, "allowSubstitutes": false,
"exportReferencesGraph": { "exportReferencesGraph": {
"refs1": [ "refs1": [
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo" "p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
], ],
"refs2": [ "refs2": [
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv" "vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
] ]
}, },
"impureEnvVars": [ "impureEnvVars": [
@ -23,11 +23,14 @@
"allowedReferences": null, "allowedReferences": null,
"allowedRequisites": null, "allowedRequisites": null,
"disallowedReferences": [ "disallowedReferences": [
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar", {
"dev" "drvPath": "self",
"output": "dev"
},
"r5cff30838majxk5mp3ip2diffi8vpaj-bar"
], ],
"disallowedRequisites": [ "disallowedRequisites": [
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev" "9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
], ],
"ignoreSelfRefs": false, "ignoreSelfRefs": false,
"maxClosureSize": null, "maxClosureSize": null,
@ -44,11 +47,14 @@
}, },
"out": { "out": {
"allowedReferences": [ "allowedReferences": [
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo" "p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
], ],
"allowedRequisites": [ "allowedRequisites": [
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev", {
"bin" "drvPath": "self",
"output": "bin"
},
"z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
], ],
"disallowedReferences": [], "disallowedReferences": [],
"disallowedRequisites": [], "disallowedRequisites": [],

View file

@ -3,7 +3,7 @@
#include "nix/util/experimental-features.hh" #include "nix/util/experimental-features.hh"
#include "nix/store/derivations.hh" #include "nix/store/derivations.hh"
#include "nix/store/derivations.hh" #include "nix/store/derived-path.hh"
#include "nix/store/derivation-options.hh" #include "nix/store/derivation-options.hh"
#include "nix/store/parsed-derivations.hh" #include "nix/store/parsed-derivations.hh"
#include "nix/util/types.hh" #include "nix/util/types.hh"
@ -17,7 +17,7 @@ namespace nix {
using namespace nlohmann; using namespace nlohmann;
class DerivationAdvancedAttrsTest : public JsonCharacterizationTest<Derivation>, class DerivationAdvancedAttrsTest : public JsonCharacterizationTest<Derivation>,
public JsonCharacterizationTest<DerivationOptions>, public JsonCharacterizationTest<DerivationOptions<SingleDerivedPath>>,
public LibStoreTest public LibStoreTest
{ {
protected: protected:
@ -42,7 +42,8 @@ public:
{ {
this->readTest(fileName, [&](auto encoded) { this->readTest(fileName, [&](auto encoded) {
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings); auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs); auto options = derivationOptionsFromStructuredAttrs(
*this->store, got.inputDrvs, got.env, got.structuredAttrs, true, this->mockXpSettings);
EXPECT_EQ(options.getRequiredSystemFeatures(got), expectedFeatures); EXPECT_EQ(options.getRequiredSystemFeatures(got), expectedFeatures);
}); });
} }
@ -51,11 +52,14 @@ public:
* Helper function to test DerivationOptions parsing and comparison * Helper function to test DerivationOptions parsing and comparison
*/ */
void testDerivationOptions( void testDerivationOptions(
const std::string & fileName, const DerivationOptions & expected, const StringSet & expectedSystemFeatures) const std::string & fileName,
const DerivationOptions<SingleDerivedPath> & expected,
const StringSet & expectedSystemFeatures)
{ {
this->readTest(fileName, [&](auto encoded) { this->readTest(fileName, [&](auto encoded) {
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings); auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs); auto options = derivationOptionsFromStructuredAttrs(
*this->store, got.inputDrvs, got.env, got.structuredAttrs, true, this->mockXpSettings);
EXPECT_EQ(options, expected); EXPECT_EQ(options, expected);
EXPECT_EQ(options.getRequiredSystemFeatures(got), expectedSystemFeatures); EXPECT_EQ(options.getRequiredSystemFeatures(got), expectedSystemFeatures);
@ -131,22 +135,38 @@ TEST_ATERM_JSON(advancedAttributes_structuredAttrs_defaults, "advanced-attribute
* Since these are both repeated and sensative opaque values, it makes * Since these are both repeated and sensative opaque values, it makes
* sense to give them names in this file. * sense to give them names in this file.
*/ */
static std::string pathFoo = "/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo", static SingleDerivedPath
pathFooDev = "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev", pathFoo = SingleDerivedPath::Opaque{StorePath{"p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"}},
pathBar = "/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar", pathFooDev = SingleDerivedPath::Opaque{StorePath{"z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"}},
pathBarDev = "/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev", pathBar = SingleDerivedPath::Opaque{StorePath{"r5cff30838majxk5mp3ip2diffi8vpaj-bar"}},
pathBarDrvIA = "/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv", pathBarDev = SingleDerivedPath::Opaque{StorePath{"9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"}},
pathBarDrvCA = "/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv", pathBarDrvIA = SingleDerivedPath::Opaque{StorePath{"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"}},
placeholderFoo = "/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9", pathBarDrvCA = SingleDerivedPath::Opaque{StorePath{"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}},
placeholderFooDev = "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z", placeholderFoo =
placeholderBar = "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g", SingleDerivedPath::Built{
placeholderBarDev = "/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"; .drvPath = makeConstantStorePathRef(StorePath{"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv"}),
.output = "out",
},
placeholderFooDev =
SingleDerivedPath::Built{
.drvPath = makeConstantStorePathRef(StorePath{"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv"}),
.output = "dev",
},
placeholderBar =
SingleDerivedPath::Built{
.drvPath = makeConstantStorePathRef(StorePath{"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}),
.output = "out",
},
placeholderBarDev = SingleDerivedPath::Built{
.drvPath = makeConstantStorePathRef(StorePath{"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}),
.output = "dev",
};
using ExportReferencesMap = decltype(DerivationOptions::exportReferencesGraph); using ExportReferencesMap = decltype(DerivationOptions<SingleDerivedPath>::exportReferencesGraph);
static const DerivationOptions advancedAttributes_defaults = { static const DerivationOptions<SingleDerivedPath> advancedAttributes_defaults = {
.outputChecks = .outputChecks =
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.ignoreSelfRefs = true, .ignoreSelfRefs = true,
}, },
.unsafeDiscardReferences = {}, .unsafeDiscardReferences = {},
@ -167,7 +187,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_defaults)
this->readTest("advanced-attributes-defaults.drv", [&](auto encoded) { this->readTest("advanced-attributes-defaults.drv", [&](auto encoded) {
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings); auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs); auto options = derivationOptionsFromStructuredAttrs(
*this->store, got.inputDrvs, got.env, got.structuredAttrs, true, this->mockXpSettings);
EXPECT_TRUE(!got.structuredAttrs); EXPECT_TRUE(!got.structuredAttrs);
@ -192,9 +213,9 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes_defaults)
TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes) TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes)
{ {
DerivationOptions expected = { DerivationOptions<SingleDerivedPath> expected = {
.outputChecks = .outputChecks =
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.ignoreSelfRefs = true, .ignoreSelfRefs = true,
}, },
.unsafeDiscardReferences = {}, .unsafeDiscardReferences = {},
@ -212,12 +233,13 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes)
this->readTest("advanced-attributes.drv", [&](auto encoded) { this->readTest("advanced-attributes.drv", [&](auto encoded) {
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings); auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs); auto options = derivationOptionsFromStructuredAttrs(
*this->store, got.inputDrvs, got.env, got.structuredAttrs, true, this->mockXpSettings);
EXPECT_TRUE(!got.structuredAttrs); EXPECT_TRUE(!got.structuredAttrs);
// Reset fields that vary between test cases to enable whole-object comparison // Reset fields that vary between test cases to enable whole-object comparison
options.outputChecks = DerivationOptions::OutputChecks{.ignoreSelfRefs = true}; options.outputChecks = DerivationOptions<SingleDerivedPath>::OutputChecks{.ignoreSelfRefs = true};
options.exportReferencesGraph = {}; options.exportReferencesGraph = {};
EXPECT_EQ(options, expected); EXPECT_EQ(options, expected);
@ -227,14 +249,14 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes)
}); });
}; };
DerivationOptions advancedAttributes_ia = { DerivationOptions<SingleDerivedPath> advancedAttributes_ia = {
.outputChecks = .outputChecks =
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.ignoreSelfRefs = true, .ignoreSelfRefs = true,
.allowedReferences = StringSet{pathFoo}, .allowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathFoo},
.disallowedReferences = StringSet{pathBar, "dev"}, .disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathBar, OutputName{"dev"}},
.allowedRequisites = StringSet{pathFooDev, "bin"}, .allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathFooDev, OutputName{"bin"}},
.disallowedRequisites = StringSet{pathBarDev}, .disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathBarDev},
}, },
.unsafeDiscardReferences = {}, .unsafeDiscardReferences = {},
.passAsFile = {}, .passAsFile = {},
@ -257,14 +279,14 @@ TEST_F(DerivationAdvancedAttrsTest, advancedAttributes_ia)
testDerivationOptions("advanced-attributes.drv", advancedAttributes_ia, {"rainbow", "uid-range"}); testDerivationOptions("advanced-attributes.drv", advancedAttributes_ia, {"rainbow", "uid-range"});
}; };
DerivationOptions advancedAttributes_ca = { DerivationOptions<SingleDerivedPath> advancedAttributes_ca = {
.outputChecks = .outputChecks =
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.ignoreSelfRefs = true, .ignoreSelfRefs = true,
.allowedReferences = StringSet{placeholderFoo}, .allowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderFoo},
.disallowedReferences = StringSet{placeholderBar, "dev"}, .disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderBar, OutputName{"dev"}},
.allowedRequisites = StringSet{placeholderFooDev, "bin"}, .allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderFooDev, OutputName{"bin"}},
.disallowedRequisites = StringSet{placeholderBarDev}, .disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderBarDev},
}, },
.unsafeDiscardReferences = {}, .unsafeDiscardReferences = {},
.passAsFile = {}, .passAsFile = {},
@ -287,8 +309,8 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes)
testDerivationOptions("advanced-attributes.drv", advancedAttributes_ca, {"rainbow", "uid-range", "ca-derivations"}); testDerivationOptions("advanced-attributes.drv", advancedAttributes_ca, {"rainbow", "uid-range", "ca-derivations"});
}; };
DerivationOptions advancedAttributes_structuredAttrs_defaults = { DerivationOptions<SingleDerivedPath> advancedAttributes_structuredAttrs_defaults = {
.outputChecks = std::map<std::string, DerivationOptions::OutputChecks>{}, .outputChecks = std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{},
.unsafeDiscardReferences = {}, .unsafeDiscardReferences = {},
.passAsFile = {}, .passAsFile = {},
.exportReferencesGraph = {}, .exportReferencesGraph = {},
@ -307,7 +329,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs_d
this->readTest("advanced-attributes-structured-attrs-defaults.drv", [&](auto encoded) { this->readTest("advanced-attributes-structured-attrs-defaults.drv", [&](auto encoded) {
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings); auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs); auto options = derivationOptionsFromStructuredAttrs(
*this->store, got.inputDrvs, got.env, got.structuredAttrs, true, this->mockXpSettings);
EXPECT_TRUE(got.structuredAttrs); EXPECT_TRUE(got.structuredAttrs);
@ -332,11 +355,11 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes_structuredAttrs_default
TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs) TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
{ {
DerivationOptions expected = { DerivationOptions<SingleDerivedPath> expected = {
.outputChecks = .outputChecks =
std::map<std::string, DerivationOptions::OutputChecks>{ std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{
{"dev", {"dev",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.maxSize = 789, .maxSize = 789,
.maxClosureSize = 5909, .maxClosureSize = 5909,
}}, }},
@ -357,7 +380,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
this->readTest("advanced-attributes-structured-attrs.drv", [&](auto encoded) { this->readTest("advanced-attributes-structured-attrs.drv", [&](auto encoded) {
auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings); auto got = parseDerivation(*this->store, std::move(encoded), "foo", this->mockXpSettings);
DerivationOptions options = DerivationOptions::fromStructuredAttrs(got.env, got.structuredAttrs); auto options = derivationOptionsFromStructuredAttrs(
*this->store, got.inputDrvs, got.env, got.structuredAttrs, true, this->mockXpSettings);
EXPECT_TRUE(got.structuredAttrs); EXPECT_TRUE(got.structuredAttrs);
@ -365,7 +389,8 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
{ {
// Delete all keys but "dev" in options.outputChecks // Delete all keys but "dev" in options.outputChecks
auto * outputChecksMapP = auto * outputChecksMapP =
std::get_if<std::map<std::string, DerivationOptions::OutputChecks>>(&options.outputChecks); std::get_if<std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>>(
&options.outputChecks);
ASSERT_TRUE(outputChecksMapP); ASSERT_TRUE(outputChecksMapP);
auto & outputChecksMap = *outputChecksMapP; auto & outputChecksMap = *outputChecksMapP;
auto devEntry = outputChecksMap.find("dev"); auto devEntry = outputChecksMap.find("dev");
@ -385,21 +410,21 @@ TYPED_TEST(DerivationAdvancedAttrsBothTest, advancedAttributes_structuredAttrs)
}); });
}; };
DerivationOptions advancedAttributes_structuredAttrs_ia = { DerivationOptions<SingleDerivedPath> advancedAttributes_structuredAttrs_ia = {
.outputChecks = .outputChecks =
std::map<std::string, DerivationOptions::OutputChecks>{ std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{
{"out", {"out",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.allowedReferences = StringSet{pathFoo}, .allowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathFoo},
.allowedRequisites = StringSet{pathFooDev, "bin"}, .allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathFooDev, OutputName{"bin"}},
}}, }},
{"bin", {"bin",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.disallowedReferences = StringSet{pathBar, "dev"}, .disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{pathBar, OutputName{"dev"}},
.disallowedRequisites = StringSet{pathBarDev}, .disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{pathBarDev},
}}, }},
{"dev", {"dev",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.maxSize = 789, .maxSize = 789,
.maxClosureSize = 5909, .maxClosureSize = 5909,
}}, }},
@ -427,21 +452,21 @@ TEST_F(DerivationAdvancedAttrsTest, advancedAttributes_structuredAttrs)
"advanced-attributes-structured-attrs.drv", advancedAttributes_structuredAttrs_ia, {"rainbow", "uid-range"}); "advanced-attributes-structured-attrs.drv", advancedAttributes_structuredAttrs_ia, {"rainbow", "uid-range"});
}; };
DerivationOptions advancedAttributes_structuredAttrs_ca = { DerivationOptions<SingleDerivedPath> advancedAttributes_structuredAttrs_ca = {
.outputChecks = .outputChecks =
std::map<std::string, DerivationOptions::OutputChecks>{ std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks>{
{"out", {"out",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.allowedReferences = StringSet{placeholderFoo}, .allowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderFoo},
.allowedRequisites = StringSet{placeholderFooDev, "bin"}, .allowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderFooDev, OutputName{"bin"}},
}}, }},
{"bin", {"bin",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.disallowedReferences = StringSet{placeholderBar, "dev"}, .disallowedReferences = std::set<DrvRef<SingleDerivedPath>>{placeholderBar, OutputName{"dev"}},
.disallowedRequisites = StringSet{placeholderBarDev}, .disallowedRequisites = std::set<DrvRef<SingleDerivedPath>>{placeholderBarDev},
}}, }},
{"dev", {"dev",
DerivationOptions::OutputChecks{ DerivationOptions<SingleDerivedPath>::OutputChecks{
.maxSize = 789, .maxSize = 789,
.maxClosureSize = 5909, .maxClosureSize = 5909,
}}, }},
@ -471,14 +496,16 @@ TEST_F(CaDerivationAdvancedAttrsTest, advancedAttributes_structuredAttrs)
{"rainbow", "uid-range", "ca-derivations"}); {"rainbow", "uid-range", "ca-derivations"});
}; };
#define TEST_JSON_OPTIONS(FIXUTURE, VAR, VAR2) \ #define TEST_JSON_OPTIONS(FIXUTURE, VAR, VAR2) \
TEST_F(FIXUTURE, DerivationOptions_##VAR##_from_json) \ TEST_F(FIXUTURE, DerivationOptions_##VAR##_from_json) \
{ \ { \
this->JsonCharacterizationTest<DerivationOptions>::readJsonTest(#VAR, advancedAttributes_##VAR2); \ this->JsonCharacterizationTest<DerivationOptions<SingleDerivedPath>>::readJsonTest( \
} \ #VAR, advancedAttributes_##VAR2); \
TEST_F(FIXUTURE, DerivationOptions_##VAR##_to_json) \ } \
{ \ TEST_F(FIXUTURE, DerivationOptions_##VAR##_to_json) \
this->JsonCharacterizationTest<DerivationOptions>::writeJsonTest(#VAR, advancedAttributes_##VAR2); \ { \
this->JsonCharacterizationTest<DerivationOptions<SingleDerivedPath>>::writeJsonTest( \
#VAR, advancedAttributes_##VAR2); \
} }
TEST_JSON_OPTIONS(DerivationAdvancedAttrsTest, defaults, defaults) TEST_JSON_OPTIONS(DerivationAdvancedAttrsTest, defaults, defaults)

View file

@ -32,14 +32,27 @@ DerivationBuildingGoal::DerivationBuildingGoal(
, drv{std::make_unique<Derivation>(drv)} , drv{std::make_unique<Derivation>(drv)}
, buildMode(buildMode) , buildMode(buildMode)
{ {
DerivationOptions<SingleDerivedPath> temp;
try { try {
drvOptions = temp = derivationOptionsFromStructuredAttrs(worker.store, drv.inputDrvs, drv.env, drv.structuredAttrs);
std::make_unique<DerivationOptions>(DerivationOptions::fromStructuredAttrs(drv.env, drv.structuredAttrs));
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath)); e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
throw; throw;
} }
auto x = tryResolve(
temp, [&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
try {
return resolveDerivedPath(
worker.store, SingleDerivedPath::Built{drvPath, outputName}, &worker.evalStore);
} catch (Error &) {
return std::nullopt;
}
});
assert(x);
drvOptions = std::make_unique<DerivationOptions<StorePath>>(*x);
name = fmt("building derivation '%s'", worker.store.printStorePath(drvPath)); name = fmt("building derivation '%s'", worker.store.printStorePath(drvPath));
trace("created"); trace("created");

View file

@ -11,7 +11,7 @@ void checkOutputs(
Store & store, Store & store,
const StorePath & drvPath, const StorePath & drvPath,
const decltype(Derivation::outputs) & drvOutputs, const decltype(Derivation::outputs) & drvOutputs,
const decltype(DerivationOptions::outputChecks) & outputChecks, const decltype(DerivationOptions<StorePath>::outputChecks) & outputChecks,
const std::map<std::string, ValidPathInfo> & outputs) const std::map<std::string, ValidPathInfo> & outputs)
{ {
std::map<Path, const ValidPathInfo &> outputsByPath; std::map<Path, const ValidPathInfo &> outputsByPath;
@ -85,7 +85,7 @@ void checkOutputs(
return std::make_pair(std::move(pathsDone), closureSize); return std::make_pair(std::move(pathsDone), closureSize);
}; };
auto applyChecks = [&](const DerivationOptions::OutputChecks & checks) { auto applyChecks = [&](const DerivationOptions<StorePath>::OutputChecks & checks) {
if (checks.maxSize && info.narSize > *checks.maxSize) if (checks.maxSize && info.narSize > *checks.maxSize)
throw BuildError( throw BuildError(
BuildResult::Failure::OutputRejected, BuildResult::Failure::OutputRejected,
@ -105,28 +105,33 @@ void checkOutputs(
*checks.maxClosureSize); *checks.maxClosureSize);
} }
auto checkRefs = [&](const StringSet & value, bool allowed, bool recursive) { auto checkRefs = [&](const std::set<DrvRef<StorePath>> & value, bool allowed, bool recursive) {
/* Parse a list of reference specifiers. Each element must /* Parse a list of reference specifiers. Each element must
either be a store path, or the symbolic name of the output either be a store path, or the symbolic name of the output
of the derivation (such as `out'). */ of the derivation (such as `out'). */
StorePathSet spec; StorePathSet spec;
for (auto & i : value) { for (auto & i : value) {
if (store.isStorePath(i)) std::visit(
spec.insert(store.parseStorePath(i)); overloaded{
else if (auto output = get(outputs, i)) [&](const StorePath & path) { spec.insert(path); },
spec.insert(output->path); [&](const OutputName & refOutputName) {
else { if (auto output = get(outputs, refOutputName))
std::string outputsListing = spec.insert(output->path);
concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; }); else {
throw BuildError( std::string outputsListing =
BuildResult::Failure::OutputRejected, concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; });
"derivation '%s' output check for '%s' contains an illegal reference specifier '%s'," throw BuildError(
" expected store path or output name (one of [%s])", BuildResult::Failure::OutputRejected,
store.printStorePath(drvPath), "derivation '%s' output check for '%s' contains output name '%s',"
outputName, " but this is not a valid output of this derivation."
i, " (Valid outputs are [%s].)",
outputsListing); store.printStorePath(drvPath),
} outputName,
refOutputName,
outputsListing);
}
}},
i);
} }
auto used = recursive ? getClosure(info.path).first : info.references; auto used = recursive ? getClosure(info.path).first : info.references;
@ -180,8 +185,8 @@ void checkOutputs(
std::visit( std::visit(
overloaded{ overloaded{
[&](const DerivationOptions::OutputChecks & checks) { applyChecks(checks); }, [&](const DerivationOptions<StorePath>::OutputChecks & checks) { applyChecks(checks); },
[&](const std::map<std::string, DerivationOptions::OutputChecks> & checksPerOutput) { [&](const std::map<std::string, DerivationOptions<StorePath>::OutputChecks> & checksPerOutput) {
if (auto outputChecks = get(checksPerOutput, outputName)) if (auto outputChecks = get(checksPerOutput, outputName))
applyChecks(*outputChecks); applyChecks(*outputChecks);

View file

@ -21,7 +21,7 @@ void checkOutputs(
Store & store, Store & store,
const StorePath & drvPath, const StorePath & drvPath,
const decltype(Derivation::outputs) & drvOutputs, const decltype(Derivation::outputs) & drvOutputs,
const decltype(DerivationOptions::outputChecks) & drvOptions, const decltype(DerivationOptions<StorePath>::outputChecks) & drvOptions,
const std::map<std::string, ValidPathInfo> & outputs); const std::map<std::string, ValidPathInfo> & outputs);
} // namespace nix } // namespace nix

View file

@ -18,7 +18,10 @@ std::string & DesugaredEnv::atFileEnvPair(std::string_view name, std::string fil
} }
DesugaredEnv DesugaredEnv::create( DesugaredEnv DesugaredEnv::create(
Store & store, const Derivation & drv, const DerivationOptions & drvOptions, const StorePathSet & inputPaths) Store & store,
const Derivation & drv,
const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths)
{ {
DesugaredEnv res; DesugaredEnv res;
@ -46,7 +49,7 @@ DesugaredEnv DesugaredEnv::create(
} }
/* Handle exportReferencesGraph(), if set. */ /* Handle exportReferencesGraph(), if set. */
for (auto & [fileName, storePaths] : drvOptions.getParsedExportReferencesGraph(store)) { for (auto & [fileName, storePaths] : drvOptions.exportReferencesGraph) {
/* Write closure info to <fileName>. */ /* Write closure info to <fileName>. */
res.extraFiles.insert_or_assign( res.extraFiles.insert_or_assign(
fileName, store.makeValidityRegistration(store.exportReferences(storePaths, inputPaths), false, false)); fileName, store.makeValidityRegistration(store.exportReferences(storePaths, inputPaths), false, false));

View file

@ -64,9 +64,9 @@ Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
{ {
trace("have derivation"); trace("have derivation");
auto drvOptions = [&]() -> DerivationOptions { auto drvOptions = [&]() -> DerivationOptions<SingleDerivedPath> {
try { try {
return DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs); return derivationOptionsFromStructuredAttrs(worker.store, drv->inputDrvs, drv->env, drv->structuredAttrs);
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath)); e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
throw; throw;

View file

@ -2,15 +2,18 @@
#include "nix/util/json-utils.hh" #include "nix/util/json-utils.hh"
#include "nix/store/parsed-derivations.hh" #include "nix/store/parsed-derivations.hh"
#include "nix/store/derivations.hh" #include "nix/store/derivations.hh"
#include "nix/store/derived-path.hh"
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/util/types.hh" #include "nix/util/types.hh"
#include "nix/util/util.hh" #include "nix/util/util.hh"
#include "nix/store/globals.hh" #include "nix/store/globals.hh"
#include "nix/util/variant-wrapper.hh"
#include <optional> #include <optional>
#include <string> #include <string>
#include <variant> #include <variant>
#include <regex> #include <regex>
#include <ranges>
namespace nix { namespace nix {
@ -90,14 +93,60 @@ getStringSetAttr(const StringMap & env, const StructuredAttrs * parsed, const st
return ss ? (std::optional{StringSet{ss->begin(), ss->end()}}) : (std::optional<StringSet>{}); return ss ? (std::optional{StringSet{ss->begin(), ss->end()}}) : (std::optional<StringSet>{});
} }
using OutputChecks = DerivationOptions::OutputChecks; template<typename Inputs>
using OutputChecks = DerivationOptions<Inputs>::OutputChecks;
using OutputChecksVariant = std::variant<OutputChecks, std::map<std::string, OutputChecks>>; template<typename Inputs>
using OutputChecksVariant = std::variant<OutputChecks<Inputs>, std::map<std::string, OutputChecks<Inputs>>>;
DerivationOptions DerivationOptions::fromStructuredAttrs( DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
const StringMap & env, const std::optional<StructuredAttrs> & parsed, bool shouldWarn) const StoreDirConfig & store,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn,
const ExperimentalFeatureSettings & mockXpSettings)
{ {
return fromStructuredAttrs(env, parsed ? &*parsed : nullptr); /* Use the SingleDerivedPath version with empty inputDrvs, then
resolve. */
DerivedPathMap<StringSet> emptyInputDrvs{};
auto singleDerivedPathOptions =
derivationOptionsFromStructuredAttrs(store, emptyInputDrvs, env, parsed, shouldWarn, mockXpSettings);
/* "Resolve" all SingleDerivedPath inputs to StorePath. */
auto resolved = tryResolve(
singleDerivedPathOptions,
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
// there should be nothing to resolve
assert(false);
});
/* Since we should never need to call the call back, there should be
no way it fails. */
assert(resolved);
return *resolved;
}
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const StringMap & env,
const std::optional<StructuredAttrs> & parsed,
bool shouldWarn,
const ExperimentalFeatureSettings & mockXpSettings)
{
return derivationOptionsFromStructuredAttrs(store, env, parsed ? &*parsed : nullptr, shouldWarn, mockXpSettings);
}
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const DerivedPathMap<StringSet> & inputDrvs,
const StringMap & env,
const std::optional<StructuredAttrs> & parsed,
bool shouldWarn,
const ExperimentalFeatureSettings & mockXpSettings)
{
return derivationOptionsFromStructuredAttrs(
store, inputDrvs, env, parsed ? &*parsed : nullptr, shouldWarn, mockXpSettings);
} }
static void flatten(const nlohmann::json & value, StringSet & res) static void flatten(const nlohmann::json & value, StringSet & res)
@ -111,10 +160,63 @@ static void flatten(const nlohmann::json & value, StringSet & res)
throw Error("'exportReferencesGraph' value is not an array or a string"); throw Error("'exportReferencesGraph' value is not an array or a string");
} }
DerivationOptions DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn) const StoreDirConfig & store,
const DerivedPathMap<StringSet> & inputDrvs,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn,
const ExperimentalFeatureSettings & mockXpSettings)
{ {
DerivationOptions defaults = {}; DerivationOptions<SingleDerivedPath> defaults = {};
std::map<std::string, SingleDerivedPath::Built> placeholders;
if (mockXpSettings.isEnabled(Xp::CaDerivations)) {
/* Initialize placeholder map from inputDrvs */
auto initPlaceholders = [&](this const auto & initPlaceholders,
ref<const SingleDerivedPath> basePath,
const DerivedPathMap<StringSet>::ChildNode & node) -> void {
for (const auto & outputName : node.value) {
auto built = SingleDerivedPath::Built{
.drvPath = basePath,
.output = outputName,
};
placeholders.insert_or_assign(
DownstreamPlaceholder::fromSingleDerivedPathBuilt(built, mockXpSettings).render(),
std::move(built));
}
for (const auto & [outputName, childNode] : node.childMap) {
initPlaceholders(
make_ref<const SingleDerivedPath>(SingleDerivedPath::Built{
.drvPath = basePath,
.output = outputName,
}),
childNode);
}
};
for (const auto & [drvPath, outputs] : inputDrvs.map) {
auto basePath = make_ref<const SingleDerivedPath>(SingleDerivedPath::Opaque{drvPath});
initPlaceholders(basePath, outputs);
}
}
auto parseSingleDerivedPath = [&](const std::string & pathS) -> SingleDerivedPath {
if (auto it = placeholders.find(pathS); it != placeholders.end())
return it->second;
else
return SingleDerivedPath::Opaque{store.toStorePath(pathS).first};
};
auto parseRef = [&](const std::string & pathS) -> DrvRef<SingleDerivedPath> {
if (auto it = placeholders.find(pathS); it != placeholders.end())
return it->second;
if (store.isStorePath(pathS))
return SingleDerivedPath::Opaque{store.toStorePath(pathS).first};
else
return pathS;
};
if (shouldWarn && parsed) { if (shouldWarn && parsed) {
auto & structuredAttrs = parsed->structuredAttrs; auto & structuredAttrs = parsed->structuredAttrs;
@ -146,14 +248,14 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
} }
return { return {
.outputChecks = [&]() -> OutputChecksVariant { .outputChecks = [&]() -> OutputChecksVariant<SingleDerivedPath> {
if (parsed) { if (parsed) {
auto & structuredAttrs = parsed->structuredAttrs; auto & structuredAttrs = parsed->structuredAttrs;
std::map<std::string, OutputChecks> res; std::map<std::string, OutputChecks<SingleDerivedPath>> res;
if (auto * outputChecks = get(structuredAttrs, "outputChecks")) { if (auto * outputChecks = get(structuredAttrs, "outputChecks")) {
for (auto & [outputName, output_] : getObject(*outputChecks)) { for (auto & [outputName, output_] : getObject(*outputChecks)) {
OutputChecks checks; OutputChecks<SingleDerivedPath> checks;
auto & output = getObject(output_); auto & output = getObject(output_);
@ -163,13 +265,14 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
if (auto maxClosureSize = get(output, "maxClosureSize")) if (auto maxClosureSize = get(output, "maxClosureSize"))
checks.maxClosureSize = maxClosureSize->get<uint64_t>(); checks.maxClosureSize = maxClosureSize->get<uint64_t>();
auto get_ = [&output = output](const std::string & name) -> std::optional<StringSet> { auto get_ =
[&](const std::string & name) -> std::optional<std::set<DrvRef<SingleDerivedPath>>> {
if (auto i = get(output, name)) { if (auto i = get(output, name)) {
StringSet res; std::set<DrvRef<SingleDerivedPath>> res;
for (auto j = i->begin(); j != i->end(); ++j) { for (auto j = i->begin(); j != i->end(); ++j) {
if (!j->is_string()) if (!j->is_string())
throw Error("attribute '%s' must be a list of strings", name); throw Error("attribute '%s' must be a list of strings", name);
res.insert(j->get<std::string>()); res.insert(parseRef(j->get<std::string>()));
} }
return res; return res;
} }
@ -178,7 +281,7 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
res.insert_or_assign( res.insert_or_assign(
outputName, outputName,
OutputChecks{ OutputChecks<SingleDerivedPath>{
.maxSize = [&]() -> std::optional<uint64_t> { .maxSize = [&]() -> std::optional<uint64_t> {
if (auto maxSize = get(output, "maxSize")) if (auto maxSize = get(output, "maxSize"))
return maxSize->get<uint64_t>(); return maxSize->get<uint64_t>();
@ -192,21 +295,32 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
return std::nullopt; return std::nullopt;
}(), }(),
.allowedReferences = get_("allowedReferences"), .allowedReferences = get_("allowedReferences"),
.disallowedReferences = get_("disallowedReferences").value_or(StringSet{}), .disallowedReferences =
get_("disallowedReferences").value_or(std::set<DrvRef<SingleDerivedPath>>{}),
.allowedRequisites = get_("allowedRequisites"), .allowedRequisites = get_("allowedRequisites"),
.disallowedRequisites = get_("disallowedRequisites").value_or(StringSet{}), .disallowedRequisites =
get_("disallowedRequisites").value_or(std::set<DrvRef<SingleDerivedPath>>{}),
}); });
} }
} }
return res; return res;
} else { } else {
return OutputChecks{ auto parseRefSet = [&](const std::optional<StringSet> optionalStringSet)
-> std::optional<std::set<DrvRef<SingleDerivedPath>>> {
if (!optionalStringSet)
return std::nullopt;
auto range = *optionalStringSet | std::views::transform(parseRef);
return std::set<DrvRef<SingleDerivedPath>>(range.begin(), range.end());
};
return OutputChecks<SingleDerivedPath>{
// legacy non-structured-attributes case // legacy non-structured-attributes case
.ignoreSelfRefs = true, .ignoreSelfRefs = true,
.allowedReferences = getStringSetAttr(env, parsed, "allowedReferences"), .allowedReferences = parseRefSet(getStringSetAttr(env, parsed, "allowedReferences")),
.disallowedReferences = getStringSetAttr(env, parsed, "disallowedReferences").value_or(StringSet{}), .disallowedReferences = parseRefSet(getStringSetAttr(env, parsed, "disallowedReferences"))
.allowedRequisites = getStringSetAttr(env, parsed, "allowedRequisites"), .value_or(std::set<DrvRef<SingleDerivedPath>>{}),
.disallowedRequisites = getStringSetAttr(env, parsed, "disallowedRequisites").value_or(StringSet{}), .allowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "allowedRequisites")),
.disallowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "disallowedRequisites"))
.value_or(std::set<DrvRef<SingleDerivedPath>>{}),
}; };
} }
}(), }(),
@ -245,16 +359,19 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
}(), }(),
.exportReferencesGraph = .exportReferencesGraph =
[&] { [&] {
std::map<std::string, StringSet> ret; std::map<std::string, std::set<SingleDerivedPath>> ret;
if (parsed) { if (parsed) {
auto * e = optionalValueAt(parsed->structuredAttrs, "exportReferencesGraph"); auto * e = optionalValueAt(parsed->structuredAttrs, "exportReferencesGraph");
if (!e || !e->is_object()) if (!e || !e->is_object())
return ret; return ret;
for (auto & [key, value] : getObject(*e)) { for (auto & [key, storePathsJson] : getObject(*e)) {
StringSet ss; StringSet ss;
flatten(value, ss); flatten(storePathsJson, ss);
ret.insert_or_assign(key, std::move(ss)); std::set<SingleDerivedPath> storePaths;
for (auto & s : ss)
storePaths.insert(parseSingleDerivedPath(s));
ret.insert_or_assign(key, std::move(storePaths));
} }
} else { } else {
auto s = getOr(env, "exportReferencesGraph", ""); auto s = getOr(env, "exportReferencesGraph", "");
@ -268,7 +385,7 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName); throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName);
auto & storePathS = *i++; auto & storePathS = *i++;
ret.insert_or_assign(std::move(fileName), StringSet{storePathS}); ret.insert_or_assign(std::move(fileName), std::set{parseSingleDerivedPath(storePathS)});
} }
} }
return ret; return ret;
@ -286,28 +403,8 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
}; };
} }
std::map<std::string, StorePathSet> template<typename Input>
DerivationOptions::getParsedExportReferencesGraph(const StoreDirConfig & store) const StringSet DerivationOptions<Input>::getRequiredSystemFeatures(const BasicDerivation & drv) const
{
std::map<std::string, StorePathSet> res;
for (auto & [fileName, ss] : exportReferencesGraph) {
StorePathSet storePaths;
for (auto & storePathS : ss) {
if (!store.isInStore(storePathS))
throw BuildError(
BuildResult::Failure::InputRejected,
"'exportReferencesGraph' contains a non-store path '%1%'",
storePathS);
storePaths.insert(store.toStorePath(storePathS).first);
}
res.insert_or_assign(fileName, storePaths);
}
return res;
}
StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & drv) const
{ {
// FIXME: cache this? // FIXME: cache this?
StringSet res; StringSet res;
@ -318,7 +415,8 @@ StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & d
return res; return res;
} }
bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivation & drv) const template<typename Input>
bool DerivationOptions<Input>::canBuildLocally(Store & localStore, const BasicDerivation & drv) const
{ {
if (drv.platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv.platform) if (drv.platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv.platform)
&& !drv.isBuiltin()) && !drv.isBuiltin())
@ -334,42 +432,194 @@ bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivatio
return true; return true;
} }
bool DerivationOptions::willBuildLocally(Store & localStore, const BasicDerivation & drv) const template<typename Input>
bool DerivationOptions<Input>::willBuildLocally(Store & localStore, const BasicDerivation & drv) const
{ {
return preferLocalBuild && canBuildLocally(localStore, drv); return preferLocalBuild && canBuildLocally(localStore, drv);
} }
bool DerivationOptions::substitutesAllowed() const template<typename Input>
bool DerivationOptions<Input>::substitutesAllowed() const
{ {
return settings.alwaysAllowSubstitutes ? true : allowSubstitutes; return settings.alwaysAllowSubstitutes ? true : allowSubstitutes;
} }
bool DerivationOptions::useUidRange(const BasicDerivation & drv) const template<typename Input>
bool DerivationOptions<Input>::useUidRange(const BasicDerivation & drv) const
{ {
return getRequiredSystemFeatures(drv).count("uid-range"); return getRequiredSystemFeatures(drv).count("uid-range");
} }
std::optional<DerivationOptions<StorePath>> tryResolve(
const DerivationOptions<SingleDerivedPath> & drvOptions,
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)>
queryResolutionChain)
{
auto tryResolvePath = [&](const SingleDerivedPath & input) -> std::optional<StorePath> {
return std::visit(
overloaded{
[](const SingleDerivedPath::Opaque & p) -> std::optional<StorePath> { return p.path; },
[&](const SingleDerivedPath::Built & p) -> std::optional<StorePath> {
return queryResolutionChain(p.drvPath, p.output);
}},
input.raw());
};
auto tryResolveRef = [&](const DrvRef<SingleDerivedPath> & ref) -> std::optional<DrvRef<StorePath>> {
return std::visit(
overloaded{
[](const OutputName & outputName) -> std::optional<DrvRef<StorePath>> { return outputName; },
[&](const SingleDerivedPath & input) -> std::optional<DrvRef<StorePath>> {
return tryResolvePath(input);
}},
ref);
};
auto tryResolveRefSet =
[&](const std::set<DrvRef<SingleDerivedPath>> & refSet) -> std::optional<std::set<DrvRef<StorePath>>> {
std::set<DrvRef<StorePath>> resolvedSet;
for (const auto & ref : refSet) {
auto resolvedRef = tryResolveRef(ref);
if (!resolvedRef)
return std::nullopt;
resolvedSet.insert(*resolvedRef);
}
return resolvedSet;
};
// Helper function to try resolving OutputChecks using functional style
auto tryResolveOutputChecks = [&](const DerivationOptions<SingleDerivedPath>::OutputChecks & checks)
-> std::optional<DerivationOptions<StorePath>::OutputChecks> {
std::optional<std::set<DrvRef<StorePath>>> resolvedAllowedReferences;
if (checks.allowedReferences) {
resolvedAllowedReferences = tryResolveRefSet(*checks.allowedReferences);
if (!resolvedAllowedReferences)
return std::nullopt;
}
std::optional<std::set<DrvRef<StorePath>>> resolvedAllowedRequisites;
if (checks.allowedRequisites) {
resolvedAllowedRequisites = tryResolveRefSet(*checks.allowedRequisites);
if (!resolvedAllowedRequisites)
return std::nullopt;
}
auto resolvedDisallowedReferences = tryResolveRefSet(checks.disallowedReferences);
if (!resolvedDisallowedReferences)
return std::nullopt;
auto resolvedDisallowedRequisites = tryResolveRefSet(checks.disallowedRequisites);
if (!resolvedDisallowedRequisites)
return std::nullopt;
return DerivationOptions<StorePath>::OutputChecks{
.ignoreSelfRefs = checks.ignoreSelfRefs,
.maxSize = checks.maxSize,
.maxClosureSize = checks.maxClosureSize,
.allowedReferences = resolvedAllowedReferences,
.disallowedReferences = *resolvedDisallowedReferences,
.allowedRequisites = resolvedAllowedRequisites,
.disallowedRequisites = *resolvedDisallowedRequisites,
};
};
// Helper function to resolve exportReferencesGraph using functional style
auto tryResolveExportReferencesGraph = [&](const std::map<std::string, std::set<SingleDerivedPath>> & exportGraph)
-> std::optional<std::map<std::string, std::set<StorePath>>> {
std::map<std::string, std::set<StorePath>> resolved;
for (const auto & [name, inputPaths] : exportGraph) {
std::set<StorePath> resolvedPaths;
for (const auto & inputPath : inputPaths) {
auto resolvedPath = tryResolvePath(inputPath);
if (!resolvedPath)
return std::nullopt;
resolvedPaths.insert(*resolvedPath);
}
resolved.emplace(name, std::move(resolvedPaths));
}
return resolved;
};
// Resolve outputChecks using functional style with std::visit
auto resolvedOutputChecks = std::visit(
overloaded{
[&](const DerivationOptions<SingleDerivedPath>::OutputChecks & checks)
-> std::optional<std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>> {
auto resolved = tryResolveOutputChecks(checks);
if (!resolved)
return std::nullopt;
return std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>(*resolved);
},
[&](const std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks> & checksMap)
-> std::optional<std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>> {
std::map<std::string, DerivationOptions<StorePath>::OutputChecks> resolvedMap;
for (const auto & [outputName, checks] : checksMap) {
auto resolved = tryResolveOutputChecks(checks);
if (!resolved)
return std::nullopt;
resolvedMap.emplace(outputName, *resolved);
}
return std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>(resolvedMap);
}},
drvOptions.outputChecks);
if (!resolvedOutputChecks)
return std::nullopt;
// Resolve exportReferencesGraph
auto resolvedExportGraph = tryResolveExportReferencesGraph(drvOptions.exportReferencesGraph);
if (!resolvedExportGraph)
return std::nullopt;
// Return resolved DerivationOptions using designated initializers
return DerivationOptions<StorePath>{
.outputChecks = *resolvedOutputChecks,
.unsafeDiscardReferences = drvOptions.unsafeDiscardReferences,
.passAsFile = drvOptions.passAsFile,
.exportReferencesGraph = *resolvedExportGraph,
.additionalSandboxProfile = drvOptions.additionalSandboxProfile,
.noChroot = drvOptions.noChroot,
.impureHostDeps = drvOptions.impureHostDeps,
.impureEnvVars = drvOptions.impureEnvVars,
.allowLocalNetworking = drvOptions.allowLocalNetworking,
.requiredSystemFeatures = drvOptions.requiredSystemFeatures,
.preferLocalBuild = drvOptions.preferLocalBuild,
.allowSubstitutes = drvOptions.allowSubstitutes,
};
}
template struct DerivationOptions<StorePath>;
template struct DerivationOptions<SingleDerivedPath>;
} // namespace nix } // namespace nix
namespace nlohmann { namespace nlohmann {
using namespace nix; using namespace nix;
DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json_) DerivationOptions<SingleDerivedPath> adl_serializer<DerivationOptions<SingleDerivedPath>>::from_json(const json & json_)
{ {
auto & json = getObject(json_); auto & json = getObject(json_);
return { return {
.outputChecks = [&]() -> OutputChecksVariant { .outputChecks = [&]() -> OutputChecksVariant<SingleDerivedPath> {
auto outputChecks = getObject(valueAt(json, "outputChecks")); auto outputChecks = getObject(valueAt(json, "outputChecks"));
auto forAllOutputsOpt = optionalValueAt(outputChecks, "forAllOutputs"); auto forAllOutputsOpt = optionalValueAt(outputChecks, "forAllOutputs");
auto perOutputOpt = optionalValueAt(outputChecks, "perOutput"); auto perOutputOpt = optionalValueAt(outputChecks, "perOutput");
if (forAllOutputsOpt && !perOutputOpt) { if (forAllOutputsOpt && !perOutputOpt) {
return static_cast<OutputChecks>(*forAllOutputsOpt); return static_cast<OutputChecks<SingleDerivedPath>>(*forAllOutputsOpt);
} else if (perOutputOpt && !forAllOutputsOpt) { } else if (perOutputOpt && !forAllOutputsOpt) {
return static_cast<std::map<std::string, OutputChecks>>(*perOutputOpt); return static_cast<std::map<std::string, OutputChecks<SingleDerivedPath>>>(*perOutputOpt);
} else { } else {
throw Error("Exactly one of 'perOutput' or 'forAllOutputs' is required"); throw Error("Exactly one of 'perOutput' or 'forAllOutputs' is required");
} }
@ -377,7 +627,7 @@ DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json
.unsafeDiscardReferences = valueAt(json, "unsafeDiscardReferences"), .unsafeDiscardReferences = valueAt(json, "unsafeDiscardReferences"),
.passAsFile = getStringSet(valueAt(json, "passAsFile")), .passAsFile = getStringSet(valueAt(json, "passAsFile")),
.exportReferencesGraph = getMap<StringSet>(getObject(valueAt(json, "exportReferencesGraph")), getStringSet), .exportReferencesGraph = valueAt(json, "exportReferencesGraph"),
.additionalSandboxProfile = getString(valueAt(json, "additionalSandboxProfile")), .additionalSandboxProfile = getString(valueAt(json, "additionalSandboxProfile")),
.noChroot = getBoolean(valueAt(json, "noChroot")), .noChroot = getBoolean(valueAt(json, "noChroot")),
@ -391,16 +641,17 @@ DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json
}; };
} }
void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOptions & o) void adl_serializer<DerivationOptions<SingleDerivedPath>>::to_json(
json & json, const DerivationOptions<SingleDerivedPath> & o)
{ {
json["outputChecks"] = std::visit( json["outputChecks"] = std::visit(
overloaded{ overloaded{
[&](const OutputChecks & checks) { [&](const OutputChecks<SingleDerivedPath> & checks) {
nlohmann::json outputChecks; nlohmann::json outputChecks;
outputChecks["forAllOutputs"] = checks; outputChecks["forAllOutputs"] = checks;
return outputChecks; return outputChecks;
}, },
[&](const std::map<std::string, OutputChecks> & checksPerOutput) { [&](const std::map<std::string, OutputChecks<SingleDerivedPath>> & checksPerOutput) {
nlohmann::json outputChecks; nlohmann::json outputChecks;
outputChecks["perOutput"] = checksPerOutput; outputChecks["perOutput"] = checksPerOutput;
return outputChecks; return outputChecks;
@ -432,7 +683,7 @@ static inline std::optional<T> ptrToOwned(const json * ptr)
return std::nullopt; return std::nullopt;
} }
DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>::from_json(const json & json_) OutputChecks<SingleDerivedPath> adl_serializer<OutputChecks<SingleDerivedPath>>::from_json(const json & json_)
{ {
auto & json = getObject(json_); auto & json = getObject(json_);
@ -440,14 +691,16 @@ DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>:
.ignoreSelfRefs = getBoolean(valueAt(json, "ignoreSelfRefs")), .ignoreSelfRefs = getBoolean(valueAt(json, "ignoreSelfRefs")),
.maxSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxSize"))), .maxSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxSize"))),
.maxClosureSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxClosureSize"))), .maxClosureSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxClosureSize"))),
.allowedReferences = ptrToOwned<StringSet>(getNullable(valueAt(json, "allowedReferences"))), .allowedReferences =
.disallowedReferences = getStringSet(valueAt(json, "disallowedReferences")), ptrToOwned<std::set<DrvRef<SingleDerivedPath>>>(getNullable(valueAt(json, "allowedReferences"))),
.allowedRequisites = ptrToOwned<StringSet>(getNullable(valueAt(json, "allowedRequisites"))), .disallowedReferences = valueAt(json, "disallowedReferences"),
.disallowedRequisites = getStringSet(valueAt(json, "disallowedRequisites")), .allowedRequisites =
ptrToOwned<std::set<DrvRef<SingleDerivedPath>>>(getNullable(valueAt(json, "allowedRequisites"))),
.disallowedRequisites = valueAt(json, "disallowedRequisites"),
}; };
} }
void adl_serializer<DerivationOptions::OutputChecks>::to_json(json & json, const DerivationOptions::OutputChecks & c) void adl_serializer<OutputChecks<SingleDerivedPath>>::to_json(json & json, const OutputChecks<SingleDerivedPath> & c)
{ {
json["ignoreSelfRefs"] = c.ignoreSelfRefs; json["ignoreSelfRefs"] = c.ignoreSelfRefs;
json["maxSize"] = c.maxSize; json["maxSize"] = c.maxSize;

View file

@ -1,5 +1,6 @@
#include "nix/store/downstream-placeholder.hh" #include "nix/store/downstream-placeholder.hh"
#include "nix/store/derivations.hh" #include "nix/store/derivations.hh"
#include "nix/util/json-utils.hh"
namespace nix { namespace nix {
@ -49,3 +50,45 @@ DownstreamPlaceholder DownstreamPlaceholder::fromSingleDerivedPathBuilt(
} }
} // namespace nix } // namespace nix
namespace nlohmann {
using namespace nix;
template<typename Item>
DrvRef<Item> adl_serializer<DrvRef<Item>>::from_json(const json & json)
{
// OutputName case: { "drvPath": "self", "output": <output> }
if (json.type() == nlohmann::json::value_t::object) {
auto & obj = getObject(json);
if (auto * drvPath_ = get(obj, "drvPath")) {
auto & drvPath = *drvPath_;
if (drvPath.type() == nlohmann::json::value_t::string && getString(drvPath) == "self") {
return getString(valueAt(obj, "output"));
}
}
}
// Input case
return adl_serializer<Item>::from_json(json);
}
template<typename Item>
void adl_serializer<DrvRef<Item>>::to_json(json & json, const DrvRef<Item> & ref)
{
std::visit(
overloaded{
[&](const OutputName & outputName) {
json = nlohmann::json::object();
json["drvPath"] = "self";
json["output"] = outputName;
},
[&](const Item & item) { json = item; },
},
ref);
}
template struct adl_serializer<nix::DrvRef<StorePath>>;
template struct adl_serializer<nix::DrvRef<SingleDerivedPath>>;
} // namespace nlohmann

View file

@ -69,7 +69,7 @@ struct DerivationBuilderParams
* *
* @todo this should be part of `Derivation`. * @todo this should be part of `Derivation`.
*/ */
const DerivationOptions & drvOptions; const DerivationOptions<StorePath> & drvOptions;
// The remainder is state held during the build. // The remainder is state held during the build.

View file

@ -52,7 +52,7 @@ private:
*/ */
std::unique_ptr<Derivation> drv; std::unique_ptr<Derivation> drv;
std::unique_ptr<DerivationOptions> drvOptions; std::unique_ptr<DerivationOptions<StorePath>> drvOptions;
/** /**
* The remainder is state held during the build. * The remainder is state held during the build.

View file

@ -8,6 +8,7 @@ namespace nix {
class Store; class Store;
struct Derivation; struct Derivation;
template<typename Input>
struct DerivationOptions; struct DerivationOptions;
/** /**
@ -77,7 +78,10 @@ struct DesugaredEnv
* just part of `Derivation`. * just part of `Derivation`.
*/ */
static DesugaredEnv create( static DesugaredEnv create(
Store & store, const Derivation & drv, const DerivationOptions & drvOptions, const StorePathSet & inputPaths); Store & store,
const Derivation & drv,
const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths);
}; };
} // namespace nix } // namespace nix

View file

@ -8,7 +8,8 @@
#include "nix/util/types.hh" #include "nix/util/types.hh"
#include "nix/util/json-impls.hh" #include "nix/util/json-impls.hh"
#include "nix/store/path.hh" #include "nix/store/store-dir-config.hh"
#include "nix/store/downstream-placeholder.hh"
namespace nix { namespace nix {
@ -17,6 +18,9 @@ struct StoreDirConfig;
struct BasicDerivation; struct BasicDerivation;
struct StructuredAttrs; struct StructuredAttrs;
template<typename V>
struct DerivedPathMap;
/** /**
* This represents all the special options on a `Derivation`. * This represents all the special options on a `Derivation`.
* *
@ -34,6 +38,7 @@ struct StructuredAttrs;
* separately. That would be nice to separate concerns, and not make any * separately. That would be nice to separate concerns, and not make any
* environment variable names magical. * environment variable names magical.
*/ */
template<typename Input>
struct DerivationOptions struct DerivationOptions
{ {
struct OutputChecks struct OutputChecks
@ -41,13 +46,15 @@ struct DerivationOptions
bool ignoreSelfRefs = false; bool ignoreSelfRefs = false;
std::optional<uint64_t> maxSize, maxClosureSize; std::optional<uint64_t> maxSize, maxClosureSize;
using DrvRef = nix::DrvRef<Input>;
/** /**
* env: allowedReferences * env: allowedReferences
* *
* A value of `nullopt` indicates that the check is skipped. * A value of `nullopt` indicates that the check is skipped.
* This means that all references are allowed. * This means that all references are allowed.
*/ */
std::optional<StringSet> allowedReferences; std::optional<std::set<DrvRef>> allowedReferences;
/** /**
* env: disallowedReferences * env: disallowedReferences
@ -55,21 +62,21 @@ struct DerivationOptions
* No needed for `std::optional`, because skipping the check is * No needed for `std::optional`, because skipping the check is
* the same as disallowing the references. * the same as disallowing the references.
*/ */
StringSet disallowedReferences; std::set<DrvRef> disallowedReferences;
/** /**
* env: allowedRequisites * env: allowedRequisites
* *
* See `allowedReferences` * See `allowedReferences`
*/ */
std::optional<StringSet> allowedRequisites; std::optional<std::set<DrvRef>> allowedRequisites;
/** /**
* env: disallowedRequisites * env: disallowedRequisites
* *
* See `disallowedReferences` * See `disallowedReferences`
*/ */
StringSet disallowedRequisites; std::set<DrvRef> disallowedRequisites;
bool operator==(const OutputChecks &) const = default; bool operator==(const OutputChecks &) const = default;
}; };
@ -116,23 +123,7 @@ struct DerivationOptions
* attributes give to the builder. The set of paths in the original JSON * attributes give to the builder. The set of paths in the original JSON
* is replaced with a list of `PathInfo` in JSON format. * is replaced with a list of `PathInfo` in JSON format.
*/ */
std::map<std::string, StringSet> exportReferencesGraph; std::map<std::string, std::set<Input>> exportReferencesGraph;
/**
* Once a derivations is resolved, the strings in in
* `exportReferencesGraph` should all be store paths (with possible
* suffix paths, but those are discarded).
*
* @return The parsed path set for for each key in the map.
*
* @todo Ideally, `exportReferencesGraph` would just store
* `StorePath`s for this, but we can't just do that, because for CA
* derivations they is actually in general `DerivedPath`s (via
* placeholder strings) until the derivation is resolved and exact
* inputs store paths are known. We can use better types for that
* too, but that is a longer project.
*/
std::map<std::string, StorePathSet> getParsedExportReferencesGraph(const StoreDirConfig & store) const;
/** /**
* env: __sandboxProfile * env: __sandboxProfile
@ -185,18 +176,6 @@ struct DerivationOptions
bool operator==(const DerivationOptions &) const = default; bool operator==(const DerivationOptions &) const = default;
/**
* Parse this information from its legacy encoding as part of the
* environment. This should not be used with nice greenfield formats
* (e.g. JSON) but is necessary for supporting old formats (e.g.
* ATerm).
*/
static DerivationOptions
fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn = true);
static DerivationOptions
fromStructuredAttrs(const StringMap & env, const std::optional<StructuredAttrs> & parsed, bool shouldWarn = true);
/** /**
* @param drv Must be the same derivation we parsed this from. In * @param drv Must be the same derivation we parsed this from. In
* the future we'll flip things around so a `BasicDerivation` has * the future we'll flip things around so a `BasicDerivation` has
@ -222,7 +201,55 @@ struct DerivationOptions
bool useUidRange(const BasicDerivation & drv) const; bool useUidRange(const BasicDerivation & drv) const;
}; };
extern template struct DerivationOptions<StorePath>;
extern template struct DerivationOptions<SingleDerivedPath>;
struct DerivationOutput;
/**
* Parse this information from its legacy encoding as part of the
* environment. This should not be used with nice greenfield formats
* (e.g. JSON) but is necessary for supporting old formats (e.g.
* ATerm).
*/
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const DerivedPathMap<StringSet> & inputDrvs,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn = true,
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const DerivedPathMap<StringSet> & inputDrvs,
const StringMap & env,
const std::optional<StructuredAttrs> & parsed,
bool shouldWarn = true,
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn = true,
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const StringMap & env,
const std::optional<StructuredAttrs> & parsed,
bool shouldWarn = true,
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
std::optional<DerivationOptions<StorePath>> tryResolve(
const DerivationOptions<SingleDerivedPath> & drvOptions,
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)>
queryResolutionChain);
}; // namespace nix }; // namespace nix
JSON_IMPL(DerivationOptions); JSON_IMPL(nix::DerivationOptions<nix::StorePath>);
JSON_IMPL(DerivationOptions::OutputChecks) JSON_IMPL(nix::DerivationOptions<nix::SingleDerivedPath>);
JSON_IMPL(nix::DerivationOptions<nix::StorePath>::OutputChecks)
JSON_IMPL(nix::DerivationOptions<nix::SingleDerivedPath>::OutputChecks)

View file

@ -2,11 +2,23 @@
///@file ///@file
#include "nix/util/hash.hh" #include "nix/util/hash.hh"
#include "nix/util/json-impls.hh"
#include "nix/store/path.hh" #include "nix/store/path.hh"
#include "nix/store/derived-path.hh" #include "nix/store/derived-path.hh"
namespace nix { namespace nix {
/**
* A reference is either to a to-be-registered output (by name),
* or to an already-registered store object (by `Input`).
*
* `Ref<SingleDerivedPath` is a representation of something that can be
* turned into a placeholder. (Regular own-output placeholder in the
* first case, `DownstreamPlaceholder` in the second case.)
*/
template<typename Input>
using DrvRef = std::variant<OutputName, Input>;
/** /**
* Downstream Placeholders are opaque and almost certainly unique values * Downstream Placeholders are opaque and almost certainly unique values
* used to allow derivations to refer to store objects which are yet to * used to allow derivations to refer to store objects which are yet to
@ -92,3 +104,17 @@ public:
}; };
} // namespace nix } // namespace nix
namespace nlohmann {
template<typename Item>
struct adl_serializer<nix::DrvRef<Item>>
{
static nix::DrvRef<Item> from_json(const json & json);
static void to_json(json & json, const nix::DrvRef<Item> & t);
};
extern template struct adl_serializer<nix::DrvRef<nix::StorePath>>;
extern template struct adl_serializer<nix::DrvRef<nix::SingleDerivedPath>>;
} // namespace nlohmann

View file

@ -9,6 +9,7 @@
namespace nix { namespace nix {
class Store; class Store;
template<typename Input>
struct DerivationOptions; struct DerivationOptions;
struct DerivationOutput; struct DerivationOutput;
@ -47,7 +48,7 @@ struct StructuredAttrs
nlohmann::json::object_t prepareStructuredAttrs( nlohmann::json::object_t prepareStructuredAttrs(
Store & store, Store & store,
const DerivationOptions & drvOptions, const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths, const StorePathSet & inputPaths,
const DerivationOutputs & outputs) const; const DerivationOutputs & outputs) const;

View file

@ -224,11 +224,12 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
return; return;
auto drv = make_ref<Derivation>(derivationFromPath(drvPath)); auto drv = make_ref<Derivation>(derivationFromPath(drvPath));
DerivationOptions drvOptions; DerivationOptions<SingleDerivedPath> drvOptions;
try { try {
// FIXME: this is a lot of work just to get the value // FIXME: this is a lot of work just to get the value
// of `allowSubstitutes`. // of `allowSubstitutes`.
drvOptions = DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs); drvOptions =
derivationOptionsFromStructuredAttrs(*this, drv->inputDrvs, drv->env, drv->structuredAttrs);
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", printStorePath(drvPath)); e.addTrace({}, "while parsing derivation '%s'", printStorePath(drvPath));
throw; throw;

View file

@ -100,7 +100,7 @@ static nlohmann::json pathInfoToJSON(Store & store, const StorePathSet & storePa
nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs( nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs(
Store & store, Store & store,
const DerivationOptions & drvOptions, const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths, const StorePathSet & inputPaths,
const DerivationOutputs & outputs) const const DerivationOutputs & outputs) const
{ {
@ -114,8 +114,8 @@ nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs(
json["outputs"] = std::move(outputsJson); json["outputs"] = std::move(outputsJson);
/* Handle exportReferencesGraph. */ /* Handle exportReferencesGraph. */
for (auto & [key, storePaths] : drvOptions.getParsedExportReferencesGraph(store)) { for (auto & [key, storePaths] : drvOptions.exportReferencesGraph) {
json[key] = pathInfoToJSON(store, store.exportReferences(storePaths, storePaths)); json[key] = pathInfoToJSON(store, store.exportReferences(storePaths, inputPaths));
} }
return json; return json;

View file

@ -554,9 +554,9 @@ static void main_nix_build(int argc, char ** argv)
env["NIX_STORE"] = store->storeDir; env["NIX_STORE"] = store->storeDir;
env["NIX_BUILD_CORES"] = fmt("%d", settings.buildCores ? settings.buildCores : settings.getDefaultCores()); env["NIX_BUILD_CORES"] = fmt("%d", settings.buildCores ? settings.buildCores : settings.getDefaultCores());
DerivationOptions drvOptions; DerivationOptions<StorePath> drvOptions;
try { try {
drvOptions = DerivationOptions::fromStructuredAttrs(drv.env, drv.structuredAttrs); drvOptions = derivationOptionsFromStructuredAttrs(*store, drv.env, drv.structuredAttrs);
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", store->printStorePath(packageInfo.requireDrvPath())); e.addTrace({}, "while parsing derivation '%s'", store->printStorePath(packageInfo.requireDrvPath()));
throw; throw;

View file

@ -64,5 +64,5 @@ fi
if isDaemonNewer "2.28pre20241225"; then if isDaemonNewer "2.28pre20241225"; then
# test12 should fail (syntactically invalid). # test12 should fail (syntactically invalid).
expectStderr 1 nix-build -vvv -o "$RESULT" check-refs.nix -A test12 >"$TEST_ROOT/test12.stderr" expectStderr 1 nix-build -vvv -o "$RESULT" check-refs.nix -A test12 >"$TEST_ROOT/test12.stderr"
grepQuiet -F "output check for 'lib' contains an illegal reference specifier 'dev', expected store path or output name (one of [lib, out])" < "$TEST_ROOT/test12.stderr" grepQuiet -F "output check for 'lib' contains output name 'dev', but this is not a valid output of this derivation. (Valid outputs are [lib, out].)" < "$TEST_ROOT/test12.stderr"
fi fi