aboutsummaryrefslogtreecommitdiff
path: root/src/libstore
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2023-01-15 17:39:04 -0500
committerJohn Ericson <John.Ericson@Obsidian.Systems>2023-08-10 00:08:32 -0400
commit60b7121d2c6d4322b7c2e8e7acfec7b701b2d3a1 (patch)
treec07508902903edf2d1a11b135ddd2bb512819ea6 /src/libstore
parentd00fe5f22559efc6f8b4b92eab537b08c0e43dee (diff)
Make the Derived Path family of types inductive for dynamic derivations
We want to be able to write down `foo.drv^bar.drv^baz`: `foo.drv^bar.drv` is the dynamic derivation (since it is itself a derivation output, `bar.drv` from `foo.drv`). To that end, we create `Single{Derivation,BuiltPath}` types, that are very similar except instead of having multiple outputs (in a set or map), they have a single one. This is for everything to the left of the rightmost `^`. `NixStringContextElem` has an analogous change, and now can reuse `SingleDerivedPath` at the top level. In fact, if we ever get rid of `DrvDeep`, `NixStringContextElem` could be replaced with `SingleDerivedPath` entirely! Important note: some JSON formats have changed. We already can *produce* dynamic derivations, but we can't refer to them directly. Today, we can merely express building or example at the top imperatively over time by building `foo.drv^bar.drv`, and then with a second nix invocation doing `<result-from-first>^baz`, but this is not declarative. The ethos of Nix of being able to write down the full plan everything you want to do, and then execute than plan with a single command, and for that we need the new inductive form of these types. Co-authored-by: Robert Hensing <roberth@users.noreply.github.com> Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
Diffstat (limited to 'src/libstore')
-rw-r--r--src/libstore/build/derivation-goal.cc10
-rw-r--r--src/libstore/build/entry-points.cc2
-rw-r--r--src/libstore/build/local-derivation-goal.cc15
-rw-r--r--src/libstore/build/worker.cc7
-rw-r--r--src/libstore/derived-path.cc247
-rw-r--r--src/libstore/derived-path.hh202
-rw-r--r--src/libstore/downstream-placeholder.cc15
-rw-r--r--src/libstore/downstream-placeholder.hh12
-rw-r--r--src/libstore/legacy-ssh-store.cc3
-rw-r--r--src/libstore/misc.cc85
-rw-r--r--src/libstore/path-with-outputs.cc47
-rw-r--r--src/libstore/path-with-outputs.hh4
-rw-r--r--src/libstore/remote-store.cc21
-rw-r--r--src/libstore/store-api.hh1
-rw-r--r--src/libstore/tests/derived-path.cc91
-rw-r--r--src/libstore/tests/derived-path.hh14
16 files changed, 692 insertions, 84 deletions
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 5e37f7ecb..8bdf2f367 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -65,7 +65,7 @@ namespace nix {
DerivationGoal::DerivationGoal(const StorePath & drvPath,
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
- : Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
+ : Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
, useDerivation(true)
, drvPath(drvPath)
, wantedOutputs(wantedOutputs)
@@ -74,7 +74,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
state = &DerivationGoal::getDerivation;
name = fmt(
"building of '%s' from .drv file",
- DerivedPath::Built { drvPath, wantedOutputs }.to_string(worker.store));
+ DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
trace("created");
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
@@ -84,7 +84,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
- : Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
+ : Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
, useDerivation(false)
, drvPath(drvPath)
, wantedOutputs(wantedOutputs)
@@ -95,7 +95,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
state = &DerivationGoal::haveDerivation;
name = fmt(
"building of '%s' from in-memory derivation",
- DerivedPath::Built { drvPath, drv.outputNames() }.to_string(worker.store));
+ DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
trace("created");
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
@@ -1490,7 +1490,7 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
for (auto & outputName : outputs->second) {
auto buildResult = dg->getBuildResult(DerivedPath::Built {
- .drvPath = dg->drvPath,
+ .drvPath = makeConstantStorePathRef(dg->drvPath),
.outputs = OutputsSpec::Names { outputName },
});
if (buildResult.success()) {
diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc
index 4aa4d6dca..e941b4e65 100644
--- a/src/libstore/build/entry-points.cc
+++ b/src/libstore/build/entry-points.cc
@@ -77,7 +77,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
try {
worker.run(Goals{goal});
return goal->getBuildResult(DerivedPath::Built {
- .drvPath = drvPath,
+ .drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All {},
});
} catch (Error & e) {
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 8b7fbdcda..920097680 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -1172,6 +1172,19 @@ void LocalDerivationGoal::writeStructuredAttrs()
}
+static StorePath pathPartOfReq(const SingleDerivedPath & req)
+{
+ return std::visit(overloaded {
+ [&](const SingleDerivedPath::Opaque & bo) {
+ return bo.path;
+ },
+ [&](const SingleDerivedPath::Built & bfd) {
+ return pathPartOfReq(*bfd.drvPath);
+ },
+ }, req.raw());
+}
+
+
static StorePath pathPartOfReq(const DerivedPath & req)
{
return std::visit(overloaded {
@@ -1179,7 +1192,7 @@ static StorePath pathPartOfReq(const DerivedPath & req)
return bo.path;
},
[&](const DerivedPath::Built & bfd) {
- return bfd.drvPath;
+ return pathPartOfReq(*bfd.drvPath);
},
}, req.raw());
}
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index a9ca9cbbc..6779dbcf3 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -111,7 +111,10 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
{
return std::visit(overloaded {
[&](const DerivedPath::Built & bfd) -> GoalPtr {
- return makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode);
+ if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
+ return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
+ else
+ throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
},
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
@@ -265,7 +268,7 @@ void Worker::run(const Goals & _topGoals)
for (auto & i : _topGoals) {
topGoals.insert(i);
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
- topPaths.push_back(DerivedPath::Built{goal->drvPath, goal->wantedOutputs});
+ topPaths.push_back(DerivedPath::Built{makeConstantStorePathRef(goal->drvPath), goal->wantedOutputs});
} else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Opaque{goal->storePath});
}
diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc
index 52d073f81..3594b7570 100644
--- a/src/libstore/derived-path.cc
+++ b/src/libstore/derived-path.cc
@@ -7,52 +7,133 @@
namespace nix {
-nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
+#define CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, COMPARATOR) \
+ bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
+ { \
+ const MY_TYPE* me = this; \
+ auto fields1 = std::make_tuple<const CHILD_TYPE &, const FIELD_TYPE &>(*me->drvPath, me->FIELD); \
+ me = &other; \
+ auto fields2 = std::make_tuple<const CHILD_TYPE &, const FIELD_TYPE &>(*me->drvPath, me->FIELD); \
+ return fields1 COMPARATOR fields2; \
+ }
+#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
+ CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, ==) \
+ CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
+ CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
+
+#define FIELD_TYPE std::string
+CMP(SingleDerivedPath, SingleDerivedPathBuilt, output)
+#undef FIELD_TYPE
+
+#define FIELD_TYPE OutputsSpec
+CMP(SingleDerivedPath, DerivedPathBuilt, outputs)
+#undef FIELD_TYPE
+
+#undef CMP
+#undef CMP_ONE
+
+nlohmann::json DerivedPath::Opaque::toJSON(const Store & store) const
+{
+ return store.printStorePath(path);
+}
+
+nlohmann::json SingleDerivedPath::Built::toJSON(Store & store) const {
nlohmann::json res;
- res["path"] = store->printStorePath(path);
+ res["drvPath"] = drvPath->toJSON(store);
+ // Fallback for the input-addressed derivation case: We expect to always be
+ // able to print the output paths, so let’s do it
+ // FIXME try-resolve on drvPath
+ const auto outputMap = store.queryPartialDerivationOutputMap(resolveDerivedPath(store, *drvPath));
+ res["output"] = output;
+ auto outputPathIter = outputMap.find(output);
+ if (outputPathIter == outputMap.end())
+ res["outputPath"] = nullptr;
+ else if (std::optional p = outputPathIter->second)
+ res["outputPath"] = store.printStorePath(*p);
+ else
+ res["outputPath"] = nullptr;
return res;
}
-nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
+nlohmann::json DerivedPath::Built::toJSON(Store & store) const {
nlohmann::json res;
- res["drvPath"] = store->printStorePath(drvPath);
+ res["drvPath"] = drvPath->toJSON(store);
// Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so let’s do it
- const auto outputMap = store->queryPartialDerivationOutputMap(drvPath);
+ // FIXME try-resolve on drvPath
+ const auto outputMap = store.queryPartialDerivationOutputMap(resolveDerivedPath(store, *drvPath));
for (const auto & [output, outputPathOpt] : outputMap) {
if (!outputs.contains(output)) continue;
if (outputPathOpt)
- res["outputs"][output] = store->printStorePath(*outputPathOpt);
+ res["outputs"][output] = store.printStorePath(*outputPathOpt);
else
res["outputs"][output] = nullptr;
}
return res;
}
+nlohmann::json SingleDerivedPath::toJSON(Store & store) const
+{
+ return std::visit([&](const auto & buildable) {
+ return buildable.toJSON(store);
+ }, raw());
+}
+
+nlohmann::json DerivedPath::toJSON(Store & store) const
+{
+ return std::visit([&](const auto & buildable) {
+ return buildable.toJSON(store);
+ }, raw());
+}
+
std::string DerivedPath::Opaque::to_string(const Store & store) const
{
return store.printStorePath(path);
}
+std::string SingleDerivedPath::Built::to_string(const Store & store) const
+{
+ return drvPath->to_string(store) + "^" + output;
+}
+
+std::string SingleDerivedPath::Built::to_string_legacy(const Store & store) const
+{
+ return drvPath->to_string(store) + "!" + output;
+}
+
std::string DerivedPath::Built::to_string(const Store & store) const
{
- return store.printStorePath(drvPath)
+ return drvPath->to_string(store)
+ '^'
+ outputs.to_string();
}
std::string DerivedPath::Built::to_string_legacy(const Store & store) const
{
- return store.printStorePath(drvPath)
- + '!'
+ return drvPath->to_string_legacy(store)
+ + "!"
+ outputs.to_string();
}
+std::string SingleDerivedPath::to_string(const Store & store) const
+{
+ return std::visit(
+ [&](const auto & req) { return req.to_string(store); },
+ raw());
+}
+
std::string DerivedPath::to_string(const Store & store) const
{
+ return std::visit(
+ [&](const auto & req) { return req.to_string(store); },
+ raw());
+}
+
+std::string SingleDerivedPath::to_string_legacy(const Store & store) const
+{
return std::visit(overloaded {
- [&](const DerivedPath::Built & req) { return req.to_string(store); },
- [&](const DerivedPath::Opaque & req) { return req.to_string(store); },
+ [&](const SingleDerivedPath::Built & req) { return req.to_string_legacy(store); },
+ [&](const SingleDerivedPath::Opaque & req) { return req.to_string(store); },
}, this->raw());
}
@@ -70,30 +151,156 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_
return {store.parseStorePath(s)};
}
-DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS)
+void drvRequireExperiment(
+ const SingleDerivedPath & drv,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ std::visit(overloaded {
+ [&](const SingleDerivedPath::Opaque &) {
+ // plain drv path; no experimental features required.
+ },
+ [&](const SingleDerivedPath::Built &) {
+ xpSettings.require(Xp::DynamicDerivations);
+ },
+ }, drv.raw());
+}
+
+SingleDerivedPath::Built SingleDerivedPath::Built::parse(
+ const Store & store, ref<SingleDerivedPath> drv,
+ std::string_view output,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ drvRequireExperiment(*drv, xpSettings);
+ return {
+ .drvPath = drv,
+ .output = std::string { output },
+ };
+}
+
+DerivedPath::Built DerivedPath::Built::parse(
+ const Store & store, ref<SingleDerivedPath> drv,
+ std::string_view outputsS,
+ const ExperimentalFeatureSettings & xpSettings)
{
+ drvRequireExperiment(*drv, xpSettings);
return {
- .drvPath = store.parseStorePath(drvS),
+ .drvPath = drv,
.outputs = OutputsSpec::parse(outputsS),
};
}
-static inline DerivedPath parseWith(const Store & store, std::string_view s, std::string_view separator)
+static SingleDerivedPath parseWithSingle(
+ const Store & store, std::string_view s, std::string_view separator,
+ const ExperimentalFeatureSettings & xpSettings)
{
- size_t n = s.find(separator);
+ size_t n = s.rfind(separator);
+ return n == s.npos
+ ? (SingleDerivedPath) SingleDerivedPath::Opaque::parse(store, s)
+ : (SingleDerivedPath) SingleDerivedPath::Built::parse(store,
+ make_ref<SingleDerivedPath>(parseWithSingle(
+ store,
+ s.substr(0, n),
+ separator,
+ xpSettings)),
+ s.substr(n + 1),
+ xpSettings);
+}
+
+SingleDerivedPath SingleDerivedPath::parse(
+ const Store & store,
+ std::string_view s,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ return parseWithSingle(store, s, "^", xpSettings);
+}
+
+SingleDerivedPath SingleDerivedPath::parseLegacy(
+ const Store & store,
+ std::string_view s,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ return parseWithSingle(store, s, "!", xpSettings);
+}
+
+static DerivedPath parseWith(
+ const Store & store, std::string_view s, std::string_view separator,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ size_t n = s.rfind(separator);
return n == s.npos
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
- : (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1));
+ : (DerivedPath) DerivedPath::Built::parse(store,
+ make_ref<SingleDerivedPath>(parseWithSingle(
+ store,
+ s.substr(0, n),
+ separator,
+ xpSettings)),
+ s.substr(n + 1),
+ xpSettings);
+}
+
+DerivedPath DerivedPath::parse(
+ const Store & store,
+ std::string_view s,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ return parseWith(store, s, "^", xpSettings);
+}
+
+DerivedPath DerivedPath::parseLegacy(
+ const Store & store,
+ std::string_view s,
+ const ExperimentalFeatureSettings & xpSettings)
+{
+ return parseWith(store, s, "!", xpSettings);
+}
+
+DerivedPath DerivedPath::fromSingle(const SingleDerivedPath & req)
+{
+ return std::visit(overloaded {
+ [&](const SingleDerivedPath::Opaque & o) -> DerivedPath {
+ return o;
+ },
+ [&](const SingleDerivedPath::Built & b) -> DerivedPath {
+ return DerivedPath::Built {
+ .drvPath = b.drvPath,
+ .outputs = OutputsSpec::Names { b.output },
+ };
+ },
+ }, req.raw());
+}
+
+const StorePath & SingleDerivedPath::Built::getBaseStorePath() const
+{
+ return drvPath->getBaseStorePath();
+}
+
+const StorePath & DerivedPath::Built::getBaseStorePath() const
+{
+ return drvPath->getBaseStorePath();
+}
+
+template<typename DP>
+static inline const StorePath & getBaseStorePath_(const DP & derivedPath)
+{
+ return std::visit(overloaded {
+ [&](const typename DP::Built & bfd) -> auto & {
+ return bfd.drvPath->getBaseStorePath();
+ },
+ [&](const typename DP::Opaque & bo) -> auto & {
+ return bo.path;
+ },
+ }, derivedPath.raw());
}
-DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
+const StorePath & SingleDerivedPath::getBaseStorePath() const
{
- return parseWith(store, s, "^");
+ return getBaseStorePath_(*this);
}
-DerivedPath DerivedPath::parseLegacy(const Store & store, std::string_view s)
+const StorePath & DerivedPath::getBaseStorePath() const
{
- return parseWith(store, s, "!");
+ return getBaseStorePath_(*this);
}
}
diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh
index 7a4261ce0..ec30dac61 100644
--- a/src/libstore/derived-path.hh
+++ b/src/libstore/derived-path.hh
@@ -24,15 +24,135 @@ class Store;
struct DerivedPathOpaque {
StorePath path;
- nlohmann::json toJSON(ref<Store> store) const;
std::string to_string(const Store & store) const;
static DerivedPathOpaque parse(const Store & store, std::string_view);
+ nlohmann::json toJSON(const Store & store) const;
GENERATE_CMP(DerivedPathOpaque, me->path);
};
+struct SingleDerivedPath;
+
+/**
+ * A single derived path that is built from a derivation
+ *
+ * Built derived paths are pair of a derivation and an output name. They are
+ * evaluated by building the derivation, and then taking the resulting output
+ * path of the given output name.
+ */
+struct SingleDerivedPathBuilt {
+ ref<SingleDerivedPath> drvPath;
+ std::string output;
+
+ /**
+ * Get the store path this is ultimately derived from (by realising
+ * and projecting outputs).
+ *
+ * Note that this is *not* a property of the store object being
+ * referred to, but just of this path --- how we happened to be
+ * referring to that store object. In other words, this means this
+ * function breaks "referential transparency". It should therefore
+ * be used only with great care.
+ */
+ const StorePath & getBaseStorePath() const;
+
+ /**
+ * Uses `^` as the separator
+ */
+ std::string to_string(const Store & store) const;
+ /**
+ * Uses `!` as the separator
+ */
+ std::string to_string_legacy(const Store & store) const;
+ /**
+ * The caller splits on the separator, so it works for both variants.
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
+ */
+ static SingleDerivedPathBuilt parse(
+ const Store & store, ref<SingleDerivedPath> drvPath,
+ std::string_view outputs,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
+ nlohmann::json toJSON(Store & store) const;
+
+ DECLARE_CMP(SingleDerivedPathBuilt);
+};
+
+using _SingleDerivedPathRaw = std::variant<
+ DerivedPathOpaque,
+ SingleDerivedPathBuilt
+>;
+
/**
- * A derived path that is built from a derivation
+ * A "derived path" is a very simple sort of expression (not a Nix
+ * language expression! But an expression in a the general sense) that
+ * evaluates to (concrete) store path. It is either:
+ *
+ * - opaque, in which case it is just a concrete store path with
+ * possibly no known derivation
+ *
+ * - built, in which case it is a pair of a derivation path and an
+ * output name.
+ */
+struct SingleDerivedPath : _SingleDerivedPathRaw {
+ using Raw = _SingleDerivedPathRaw;
+ using Raw::Raw;
+
+ using Opaque = DerivedPathOpaque;
+ using Built = SingleDerivedPathBuilt;
+
+ inline const Raw & raw() const {
+ return static_cast<const Raw &>(*this);
+ }
+
+ /**
+ * Get the store path this is ultimately derived from (by realising
+ * and projecting outputs).
+ *
+ * Note that this is *not* a property of the store object being
+ * referred to, but just of this path --- how we happened to be
+ * referring to that store object. In other words, this means this
+ * function breaks "referential transparency". It should therefore
+ * be used only with great care.
+ */
+ const StorePath & getBaseStorePath() const;
+
+ /**
+ * Uses `^` as the separator
+ */
+ std::string to_string(const Store & store) const;
+ /**
+ * Uses `!` as the separator
+ */
+ std::string to_string_legacy(const Store & store) const;
+ /**
+ * Uses `^` as the separator
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
+ */
+ static SingleDerivedPath parse(
+ const Store & store,
+ std::string_view,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
+ /**
+ * Uses `!` as the separator
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
+ */
+ static SingleDerivedPath parseLegacy(
+ const Store & store,
+ std::string_view,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
+ nlohmann::json toJSON(Store & store) const;
+};
+
+static inline ref<SingleDerivedPath> makeConstantStorePathRef(StorePath drvPath)
+{
+ return make_ref<SingleDerivedPath>(SingleDerivedPath::Opaque { drvPath });
+}
+
+/**
+ * A set of derived paths that are built from a derivation
*
* Built derived paths are pair of a derivation and some output names.
* They are evaluated by building the derivation, and then replacing the
@@ -44,10 +164,22 @@ struct DerivedPathOpaque {
* output name.
*/
struct DerivedPathBuilt {
- StorePath drvPath;
+ ref<SingleDerivedPath> drvPath;
OutputsSpec outputs;
/**
+ * Get the store path this is ultimately derived from (by realising
+ * and projecting outputs).
+ *
+ * Note that this is *not* a property of the store object being
+ * referred to, but just of this path --- how we happened to be
+ * referring to that store object. In other words, this means this
+ * function breaks "referential transparency". It should therefore
+ * be used only with great care.
+ */
+ const StorePath & getBaseStorePath() const;
+
+ /**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
@@ -57,11 +189,16 @@ struct DerivedPathBuilt {
std::string to_string_legacy(const Store & store) const;
/**
* The caller splits on the separator, so it works for both variants.
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
*/
- static DerivedPathBuilt parse(const Store & store, std::string_view drvPath, std::string_view outputs);
- nlohmann::json toJSON(ref<Store> store) const;
+ static DerivedPathBuilt parse(
+ const Store & store, ref<SingleDerivedPath>,
+ std::string_view,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
+ nlohmann::json toJSON(Store & store) const;
- GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
+ DECLARE_CMP(DerivedPathBuilt);
};
using _DerivedPathRaw = std::variant<
@@ -71,13 +208,13 @@ using _DerivedPathRaw = std::variant<
/**
* A "derived path" is a very simple sort of expression that evaluates
- * to (concrete) store path. It is either:
+ * to one or more (concrete) store paths. It is either:
*
- * - opaque, in which case it is just a concrete store path with
+ * - opaque, in which case it is just a single concrete store path with
* possibly no known derivation
*
- * - built, in which case it is a pair of a derivation path and an
- * output name.
+ * - built, in which case it is a pair of a derivation path and some
+ * output names.
*/
struct DerivedPath : _DerivedPathRaw {
using Raw = _DerivedPathRaw;
@@ -91,6 +228,18 @@ struct DerivedPath : _DerivedPathRaw {
}
/**
+ * Get the store path this is ultimately derived from (by realising
+ * and projecting outputs).
+ *
+ * Note that this is *not* a property of the store object being
+ * referred to, but just of this path --- how we happened to be
+ * referring to that store object. In other words, this means this
+ * function breaks "referential transparency". It should therefore
+ * be used only with great care.
+ */
+ const StorePath & getBaseStorePath() const;
+
+ /**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
@@ -100,14 +249,43 @@ struct DerivedPath : _DerivedPathRaw {
std::string to_string_legacy(const Store & store) const;
/**
* Uses `^` as the separator
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
*/
- static DerivedPath parse(const Store & store, std::string_view);
+ static DerivedPath parse(
+ const Store & store,
+ std::string_view,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Uses `!` as the separator
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
*/
- static DerivedPath parseLegacy(const Store & store, std::string_view);
+ static DerivedPath parseLegacy(
+ const Store & store,
+ std::string_view,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
+
+ /**
+ * Convert a `SingleDerivedPath` to a `DerivedPath`.
+ */
+ static DerivedPath fromSingle(const SingleDerivedPath &);
+
+ nlohmann::json toJSON(Store & store) const;
};
typedef std::vector<DerivedPath> DerivedPaths;
+/**
+ * Used by various parser functions to require experimental features as
+ * needed.
+ *
+ * Somewhat unfortunate this cannot just be an implementation detail for
+ * this module.
+ *
+ * @param xpSettings Stop-gap to avoid globals during unit tests.
+ */
+void drvRequireExperiment(
+ const SingleDerivedPath & drv,
+ const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
}
diff --git a/src/libstore/downstream-placeholder.cc b/src/libstore/downstream-placeholder.cc
index d623c05e2..885b3604d 100644
--- a/src/libstore/downstream-placeholder.cc
+++ b/src/libstore/downstream-placeholder.cc
@@ -38,4 +38,19 @@ DownstreamPlaceholder DownstreamPlaceholder::unknownDerivation(
};
}
+DownstreamPlaceholder DownstreamPlaceholder::fromSingleDerivedPathBuilt(
+ const SingleDerivedPath::Built & b)
+{
+ return std::visit(overloaded {
+ [&](const SingleDerivedPath::Opaque & o) {
+ return DownstreamPlaceholder::unknownCaOutput(o.path, b.output);
+ },
+ [&](const SingleDerivedPath::Built & b2) {
+ return DownstreamPlaceholder::unknownDerivation(
+ DownstreamPlaceholder::fromSingleDerivedPathBuilt(b2),
+ b.output);
+ },
+ }, b.drvPath->raw());
+}
+
}
diff --git a/src/libstore/downstream-placeholder.hh b/src/libstore/downstream-placeholder.hh
index 97f77e6b8..9372dcd58 100644
--- a/src/libstore/downstream-placeholder.hh
+++ b/src/libstore/downstream-placeholder.hh
@@ -3,6 +3,7 @@
#include "hash.hh"
#include "path.hh"
+#include "derived-path.hh"
namespace nix {
@@ -73,6 +74,17 @@ public:
const DownstreamPlaceholder & drvPlaceholder,
std::string_view outputName,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
+
+ /**
+ * Convenience constructor that handles both cases (unknown
+ * content-addressed output and unknown derivation), delegating as
+ * needed to `unknownCaOutput` and `unknownDerivation`.
+ *
+ * Recursively builds up a placeholder from a
+ * `SingleDerivedPath::Built.drvPath` chain.
+ */
+ static DownstreamPlaceholder fromSingleDerivedPathBuilt(
+ const SingleDerivedPath::Built & built);
};
}
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index fa17d606d..78b05031a 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -358,6 +358,9 @@ public:
[&](const StorePath & drvPath) {
throw Error("wanted to fetch '%s' but the legacy ssh protocol doesn't support merely substituting drv files via the build paths command. It would build them instead. Try using ssh-ng://", printStorePath(drvPath));
},
+ [&](std::monostate) {
+ throw Error("wanted build derivation that is itself a build product, but the legacy ssh protocol doesn't support that. Try using ssh-ng://");
+ },
}, sOrDrvPath);
}
conn->to << ss;
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index 14160dc8b..1ece7a4c0 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -132,7 +132,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
}
for (auto & i : drv.inputDrvs)
- pool.enqueue(std::bind(doPath, DerivedPath::Built { i.first, i.second }));
+ pool.enqueue(std::bind(doPath, DerivedPath::Built { makeConstantStorePathRef(i.first), i.second }));
};
auto checkOutput = [&](
@@ -176,10 +176,18 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
std::visit(overloaded {
[&](const DerivedPath::Built & bfd) {
- if (!isValidPath(bfd.drvPath)) {
+ auto drvPathP = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath);
+ if (!drvPathP) {
+ // TODO make work in this case.
+ warn("Ignoring dynamic derivation %s while querying missing paths; not yet implemented", bfd.drvPath->to_string(*this));
+ return;
+ }
+ auto & drvPath = drvPathP->path;
+
+ if (!isValidPath(drvPath)) {
// FIXME: we could try to substitute the derivation.
auto state(state_.lock());
- state->unknown.insert(bfd.drvPath);
+ state->unknown.insert(drvPath);
return;
}
@@ -187,7 +195,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
/* true for regular derivations, and CA derivations for which we
have a trust mapping for all wanted outputs. */
auto knownOutputPaths = true;
- for (auto & [outputName, pathOpt] : queryPartialDerivationOutputMap(bfd.drvPath)) {
+ for (auto & [outputName, pathOpt] : queryPartialDerivationOutputMap(drvPath)) {
if (!pathOpt) {
knownOutputPaths = false;
break;
@@ -197,15 +205,15 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
}
if (knownOutputPaths && invalid.empty()) return;
- auto drv = make_ref<Derivation>(derivationFromPath(bfd.drvPath));
- ParsedDerivation parsedDrv(StorePath(bfd.drvPath), *drv);
+ auto drv = make_ref<Derivation>(derivationFromPath(drvPath));
+ ParsedDerivation parsedDrv(StorePath(drvPath), *drv);
if (knownOutputPaths && settings.useSubstitutes && parsedDrv.substitutesAllowed()) {
auto drvState = make_ref<Sync<DrvState>>(DrvState(invalid.size()));
for (auto & output : invalid)
- pool.enqueue(std::bind(checkOutput, bfd.drvPath, drv, output, drvState));
+ pool.enqueue(std::bind(checkOutput, drvPath, drv, output, drvState));
} else
- mustBuildDrv(bfd.drvPath, *drv);
+ mustBuildDrv(drvPath, *drv);
},
[&](const DerivedPath::Opaque & bo) {
@@ -310,7 +318,9 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
{
- auto outputsOpt_ = store.queryPartialDerivationOutputMap(bfd.drvPath, evalStore_);
+ auto drvPath = resolveDerivedPath(store, *bfd.drvPath, evalStore_);
+
+ auto outputsOpt_ = store.queryPartialDerivationOutputMap(drvPath, evalStore_);
auto outputsOpt = std::visit(overloaded {
[&](const OutputsSpec::All &) {
@@ -325,7 +335,7 @@ OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd,
if (!pOutputPathOpt)
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
- store.printStorePath(bfd.drvPath), output);
+ bfd.drvPath->to_string(store), output);
outputsOpt.insert_or_assign(output, std::move(*pOutputPathOpt));
}
return outputsOpt;
@@ -335,11 +345,64 @@ OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd,
OutputPathMap outputs;
for (auto & [outputName, outputPathOpt] : outputsOpt) {
if (!outputPathOpt)
- throw MissingRealisation(store.printStorePath(bfd.drvPath), outputName);
+ throw MissingRealisation(bfd.drvPath->to_string(store), outputName);
auto & outputPath = *outputPathOpt;
outputs.insert_or_assign(outputName, outputPath);
}
return outputs;
}
+
+StorePath resolveDerivedPath(Store & store, const SingleDerivedPath & req, Store * evalStore_)
+{
+ auto & evalStore = evalStore_ ? *evalStore_ : store;
+
+ return std::visit(overloaded {
+ [&](const SingleDerivedPath::Opaque & bo) {
+ return bo.path;
+ },
+ [&](const SingleDerivedPath::Built & bfd) {
+ auto drvPath = resolveDerivedPath(store, *bfd.drvPath, evalStore_);
+ auto outputPaths = evalStore.queryPartialDerivationOutputMap(drvPath, evalStore_);
+ if (outputPaths.count(bfd.output) == 0)
+ throw Error("derivation '%s' does not have an output named '%s'",
+ store.printStorePath(drvPath), bfd.output);
+ auto & optPath = outputPaths.at(bfd.output);
+ if (!optPath)
+ throw Error("'%s' does not yet map to a known concrete store path",
+ bfd.to_string(store));
+ return *optPath;
+ },
+ }, req.raw());
+}
+
+
+OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd)
+{
+ auto drvPath = resolveDerivedPath(store, *bfd.drvPath);
+ auto outputMap = store.queryDerivationOutputMap(drvPath);
+ auto outputsLeft = std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return StringSet {};
+ },
+ [&](const OutputsSpec::Names & names) {
+ return static_cast<StringSet>(names);
+ },
+ }, bfd.outputs.raw());
+ for (auto iter = outputMap.begin(); iter != outputMap.end();) {
+ auto & outputName = iter->first;
+ if (bfd.outputs.contains(outputName)) {
+ outputsLeft.erase(outputName);
+ ++iter;
+ } else {
+ iter = outputMap.erase(iter);
+ }
+ }
+ if (!outputsLeft.empty())
+ throw Error("derivation '%s' does not have an outputs %s",
+ store.printStorePath(drvPath),
+ concatStringsSep(", ", quoteStrings(std::get<OutputsSpec::Names>(bfd.outputs))));
+ return outputMap;
+}
+
}
diff --git a/src/libstore/path-with-outputs.cc b/src/libstore/path-with-outputs.cc
index 869b490ad..81f360f3a 100644
--- a/src/libstore/path-with-outputs.cc
+++ b/src/libstore/path-with-outputs.cc
@@ -16,10 +16,16 @@ std::string StorePathWithOutputs::to_string(const Store & store) const
DerivedPath StorePathWithOutputs::toDerivedPath() const
{
if (!outputs.empty()) {
- return DerivedPath::Built { path, OutputsSpec::Names { outputs } };
+ return DerivedPath::Built {
+ .drvPath = makeConstantStorePathRef(path),
+ .outputs = OutputsSpec::Names { outputs },
+ };
} else if (path.isDerivation()) {
assert(outputs.empty());
- return DerivedPath::Built { path, OutputsSpec::All { } };
+ return DerivedPath::Built {
+ .drvPath = makeConstantStorePathRef(path),
+ .outputs = OutputsSpec::All { },
+ };
} else {
return DerivedPath::Opaque { path };
}
@@ -34,29 +40,36 @@ std::vector<DerivedPath> toDerivedPaths(const std::vector<StorePathWithOutputs>
}
-std::variant<StorePathWithOutputs, StorePath> StorePathWithOutputs::tryFromDerivedPath(const DerivedPath & p)
+StorePathWithOutputs::ParseResult StorePathWithOutputs::tryFromDerivedPath(const DerivedPath & p)
{
return std::visit(overloaded {
- [&](const DerivedPath::Opaque & bo) -> std::variant<StorePathWithOutputs, StorePath> {
+ [&](const DerivedPath::Opaque & bo) -> StorePathWithOutputs::ParseResult {
if (bo.path.isDerivation()) {
// drv path gets interpreted as "build", not "get drv file itself"
return bo.path;
}
return StorePathWithOutputs { bo.path };
},
- [&](const DerivedPath::Built & bfd) -> std::variant<StorePathWithOutputs, StorePath> {
- return StorePathWithOutputs {
- .path = bfd.drvPath,
- // Use legacy encoding of wildcard as empty set
- .outputs = std::visit(overloaded {
- [&](const OutputsSpec::All &) -> StringSet {
- return {};
- },
- [&](const OutputsSpec::Names & outputs) {
- return static_cast<StringSet>(outputs);
- },
- }, bfd.outputs.raw()),
- };
+ [&](const DerivedPath::Built & bfd) -> StorePathWithOutputs::ParseResult {
+ return std::visit(overloaded {
+ [&](const SingleDerivedPath::Opaque & bo) -> StorePathWithOutputs::ParseResult {
+ return StorePathWithOutputs {
+ .path = bo.path,
+ // Use legacy encoding of wildcard as empty set
+ .outputs = std::visit(overloaded {
+ [&](const OutputsSpec::All &) -> StringSet {
+ return {};
+ },
+ [&](const OutputsSpec::Names & outputs) {
+ return static_cast<StringSet>(outputs);
+ },
+ }, bfd.outputs.raw()),
+ };
+ },
+ [&](const SingleDerivedPath::Built &) -> StorePathWithOutputs::ParseResult {
+ return std::monostate {};
+ },
+ }, bfd.drvPath->raw());
},
}, p.raw());
}
diff --git a/src/libstore/path-with-outputs.hh b/src/libstore/path-with-outputs.hh
index d75850868..57e03252d 100644
--- a/src/libstore/path-with-outputs.hh
+++ b/src/libstore/path-with-outputs.hh
@@ -23,7 +23,9 @@ struct StorePathWithOutputs
DerivedPath toDerivedPath() const;
- static std::variant<StorePathWithOutputs, StorePath> tryFromDerivedPath(const DerivedPath &);
+ typedef std::variant<StorePathWithOutputs, StorePath, std::monostate> ParseResult;
+
+ static StorePathWithOutputs::ParseResult tryFromDerivedPath(const DerivedPath &);
};
std::vector<DerivedPath> toDerivedPaths(const std::vector<StorePathWithOutputs>);
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 21258daec..58f72beb9 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -656,6 +656,9 @@ static void writeDerivedPaths(RemoteStore & store, RemoteStore::Connection & con
GET_PROTOCOL_MAJOR(conn.daemonVersion),
GET_PROTOCOL_MINOR(conn.daemonVersion));
},
+ [&](std::monostate) {
+ throw Error("wanted to build a derivation that is itself a build product, but the legacy 'ssh://' protocol doesn't support that. Try using 'ssh-ng://'");
+ },
}, sOrDrvPath);
}
conn.to << ss;
@@ -670,9 +673,16 @@ void RemoteStore::copyDrvsFromEvalStore(
/* The remote doesn't have a way to access evalStore, so copy
the .drvs. */
RealisedPath::Set drvPaths2;
- for (auto & i : paths)
- if (auto p = std::get_if<DerivedPath::Built>(&i))
- drvPaths2.insert(p->drvPath);
+ for (const auto & i : paths) {
+ std::visit(overloaded {
+ [&](const DerivedPath::Opaque & bp) {
+ // Do nothing, path is hopefully there already
+ },
+ [&](const DerivedPath::Built & bp) {
+ drvPaths2.insert(bp.drvPath->getBaseStorePath());
+ },
+ }, i.raw());
+ }
copyClosure(*evalStore, *this, drvPaths2);
}
}
@@ -742,7 +752,8 @@ std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
};
OutputPathMap outputs;
- auto drv = evalStore->readDerivation(bfd.drvPath);
+ auto drvPath = resolveDerivedPath(*evalStore, *bfd.drvPath);
+ auto drv = evalStore->readDerivation(drvPath);
const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
auto built = resolveDerivedPath(*this, bfd, &*evalStore);
for (auto & [output, outputPath] : built) {
@@ -750,7 +761,7 @@ std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
if (!outputHash)
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
- printStorePath(bfd.drvPath), output);
+ printStorePath(drvPath), output);
auto outputId = DrvOutput{ *outputHash, output };
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
auto realisation =
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index f9029ade1..da1a3eefb 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -945,6 +945,7 @@ void removeTempRoots();
* Resolve the derived path completely, failing if any derivation output
* is unknown.
*/
+StorePath resolveDerivedPath(Store &, const SingleDerivedPath &, Store * evalStore = nullptr);
OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * evalStore = nullptr);
diff --git a/src/libstore/tests/derived-path.cc b/src/libstore/tests/derived-path.cc
index 160443ec1..d6549f66f 100644
--- a/src/libstore/tests/derived-path.cc
+++ b/src/libstore/tests/derived-path.cc
@@ -17,14 +17,34 @@ Gen<DerivedPath::Opaque> Arbitrary<DerivedPath::Opaque>::arbitrary()
});
}
+Gen<SingleDerivedPath::Built> Arbitrary<SingleDerivedPath::Built>::arbitrary()
+{
+ return gen::just(SingleDerivedPath::Built {
+ .drvPath = make_ref<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath>()),
+ .output = (*gen::arbitrary<StorePathName>()).name,
+ });
+}
+
Gen<DerivedPath::Built> Arbitrary<DerivedPath::Built>::arbitrary()
{
return gen::just(DerivedPath::Built {
- .drvPath = *gen::arbitrary<StorePath>(),
+ .drvPath = make_ref<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath>()),
.outputs = *gen::arbitrary<OutputsSpec>(),
});
}
+Gen<SingleDerivedPath> Arbitrary<SingleDerivedPath>::arbitrary()
+{
+ switch (*gen::inRange<uint8_t>(0, std::variant_size_v<SingleDerivedPath::Raw>)) {
+ case 0:
+ return gen::just<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath::Opaque>());
+ case 1:
+ return gen::just<SingleDerivedPath>(*gen::arbitrary<SingleDerivedPath::Built>());
+ default:
+ assert(false);
+ }
+}
+
Gen<DerivedPath> Arbitrary<DerivedPath>::arbitrary()
{
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<DerivedPath::Raw>)) {
@@ -45,12 +65,69 @@ class DerivedPathTest : public LibStoreTest
{
};
-// FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is
-// no a real fixture.
-//
-// See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args
-TEST_F(DerivedPathTest, force_init)
-{
+/**
+ * Round trip (string <-> data structure) test for
+ * `DerivedPath::Opaque`.
+ */
+TEST_F(DerivedPathTest, opaque) {
+ std::string_view opaque = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
+ auto elem = DerivedPath::parse(*store, opaque);
+ auto * p = std::get_if<DerivedPath::Opaque>(&elem);
+ ASSERT_TRUE(p);
+ ASSERT_EQ(p->path, store->parseStorePath(opaque));
+ ASSERT_EQ(elem.to_string(*store), opaque);
+}
+
+/**
+ * Round trip (string <-> data structure) test for a simpler
+ * `DerivedPath::Built`.
+ */
+TEST_F(DerivedPathTest, built_opaque) {
+ std::string_view built = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv^bar,foo";
+ auto elem = DerivedPath::parse(*store, built);
+ auto * p = std::get_if<DerivedPath::Built>(&elem);
+ ASSERT_TRUE(p);
+ ASSERT_EQ(p->outputs, ((OutputsSpec) OutputsSpec::Names { "foo", "bar" }));
+ ASSERT_EQ(*p->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
+ .path = store->parseStorePath(built.substr(0, 49)),
+ }));
+ ASSERT_EQ(elem.to_string(*store), built);
+}
+
+/**
+ * Round trip (string <-> data structure) test for a more complex,
+ * inductive `DerivedPath::Built`.
+ */
+TEST_F(DerivedPathTest, built_built) {
+ /**
+ * We set these in tests rather than the regular globals so we don't have
+ * to worry about race conditions if the tests run concurrently.
+ */
+ ExperimentalFeatureSettings mockXpSettings;
+ mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
+
+ std::string_view built = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv^foo^bar,baz";
+ auto elem = DerivedPath::parse(*store, built, mockXpSettings);
+ auto * p = std::get_if<DerivedPath::Built>(&elem);
+ ASSERT_TRUE(p);
+ ASSERT_EQ(p->outputs, ((OutputsSpec) OutputsSpec::Names { "bar", "baz" }));
+ auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath);
+ ASSERT_TRUE(drvPath);
+ ASSERT_EQ(drvPath->output, "foo");
+ ASSERT_EQ(*drvPath->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
+ .path = store->parseStorePath(built.substr(0, 49)),
+ }));
+ ASSERT_EQ(elem.to_string(*store), built);
+}
+
+/**
+ * Without the right experimental features enabled, we cannot parse a
+ * complex inductive derived path.
+ */
+TEST_F(DerivedPathTest, built_built_xp) {
+ ASSERT_THROW(
+ DerivedPath::parse(*store, "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv^foo^bar,baz"),
+ MissingExperimentalFeature);
}
RC_GTEST_FIXTURE_PROP(
diff --git a/src/libstore/tests/derived-path.hh b/src/libstore/tests/derived-path.hh
index 506f3ccb1..98d61f228 100644
--- a/src/libstore/tests/derived-path.hh
+++ b/src/libstore/tests/derived-path.hh
@@ -12,8 +12,18 @@ namespace rc {
using namespace nix;
template<>
-struct Arbitrary<DerivedPath::Opaque> {
- static Gen<DerivedPath::Opaque> arbitrary();
+struct Arbitrary<SingleDerivedPath::Opaque> {
+ static Gen<SingleDerivedPath::Opaque> arbitrary();
+};
+
+template<>
+struct Arbitrary<SingleDerivedPath::Built> {
+ static Gen<SingleDerivedPath::Built> arbitrary();
+};
+
+template<>
+struct Arbitrary<SingleDerivedPath> {
+ static Gen<SingleDerivedPath> arbitrary();
};
template<>