Skip to content

Commit

Permalink
Merge pull request #4628 from obsidiansystems/dynamic-drvs
Browse files Browse the repository at this point in the history
Dynamic derivations RFC 92
  • Loading branch information
roberth authored Sep 7, 2023
2 parents 37d6fff + 80d7994 commit e34493a
Show file tree
Hide file tree
Showing 23 changed files with 792 additions and 193 deletions.
18 changes: 14 additions & 4 deletions doc/manual/src/protocols/derivation-aterm.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

For historical reasons, [derivations](@docroot@/glossary.md#gloss-store-derivation) are stored on-disk in [ATerm](https://homepages.cwi.nl/~daybuild/daily-books/technology/aterm-guide/aterm-guide.html) format.

Derivations are serialised in the following format:
Derivations are serialised in one of the following formats:

```
Derive(...)
```
- ```
Derive(...)
```

For all stable derivations.

- ```
DrvWithVersion(<version-string>, ...)
```

The only `version-string`s that are in use today are for [experimental features](@docroot@/contributing/experimental-features.md):

- `"xp-dyn-drv"` for the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature.
2 changes: 1 addition & 1 deletion perl/lib/Nix/Store.xs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ SV * derivationFromPath(char * drvPath)
hv_stores(hash, "outputs", newRV((SV *) outputs));

AV * inputDrvs = newAV();
for (auto & i : drv.inputDrvs)
for (auto & i : drv.inputDrvs.map)
av_push(inputDrvs, newSVpv(store()->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second
hv_stores(hash, "inputDrvs", newRV((SV *) inputDrvs));

Expand Down
2 changes: 1 addition & 1 deletion src/build-remote/build-remote.cc
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ static int main_build_remote(int argc, char * * argv)
//
// 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations
if (!drv.inputDrvs.empty())
if (!drv.inputDrvs.map.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv);
auto & result = *optResult;
Expand Down
22 changes: 22 additions & 0 deletions src/libcmd/built-path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@ StorePathSet BuiltPath::outPaths() const
);
}

SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
{
return SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
.output = output.first,
};
}

SingleDerivedPath SingleBuiltPath::discardOutputPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath {
return p;
},
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath {
return b.discardOutputPath();
},
}, raw()
);
}

nlohmann::json BuiltPath::Built::toJSON(const Store & store) const
{
nlohmann::json res;
Expand Down
4 changes: 4 additions & 0 deletions src/libcmd/built-path.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ struct SingleBuiltPathBuilt {
ref<SingleBuiltPath> drvPath;
std::pair<std::string, StorePath> output;

SingleDerivedPathBuilt discardOutputPath() const;

std::string to_string(const Store & store) const;
static SingleBuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
nlohmann::json toJSON(const Store & store) const;
Expand All @@ -34,6 +36,8 @@ struct SingleBuiltPath : _SingleBuiltPathRaw {

StorePath outPath() const;

SingleDerivedPath discardOutputPath() const;

static SingleBuiltPath parse(const Store & store, std::string_view);
nlohmann::json toJSON(const Store & store) const;
};
Expand Down
10 changes: 4 additions & 6 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1252,15 +1252,13 @@ drvName, Bindings * attrs, Value & v)
state.store->computeFSClosure(d.drvPath, refs);
for (auto & j : refs) {
drv.inputSrcs.insert(j);
if (j.isDerivation())
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
if (j.isDerivation()) {
drv.inputDrvs.map[j].value = state.store->readDerivation(j).outputNames();
}
}
},
[&](const NixStringContextElem::Built & b) {
if (auto * p = std::get_if<DerivedPath::Opaque>(&*b.drvPath))
drv.inputDrvs[p->path].insert(b.output);
else
throw UnimplementedError("Dependencies on the outputs of dynamic derivations are not yet supported");
drv.inputDrvs.ensureSlot(*b.drvPath).value.insert(b.output);
},
[&](const NixStringContextElem::Opaque & o) {
drv.inputSrcs.insert(o.path);
Expand Down
83 changes: 54 additions & 29 deletions src/libstore/build/derivation-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -350,25 +350,37 @@ void DerivationGoal::gaveUpOnSubstitution()

/* The inputs must be built before we can build this goal. */
inputDrvOutputs.clear();
if (useDerivation)
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
if (useDerivation) {
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;

addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
addWaitee(worker.makeGoal(
DerivedPath::Built {
.drvPath = inputDrv,
.outputs = inputNode.value,
},
buildMode == bmRepair ? bmRepair : bmNormal));
for (const auto & [outputName, childNode] : inputNode.childMap)
addWaiteeDerivedPath(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};

for (const auto & [inputDrvPath, inputNode] : dynamic_cast<Derivation *>(drv.get())->inputDrvs.map) {
/* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = worker.evalStore.readDerivation(i.first);
auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'",
worker.store.printStorePath(drvPath),
worker.store.printStorePath(i.first));
worker.store.printStorePath(inputDrvPath));
}

addWaitee(worker.makeGoal(
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(i.first),
.outputs = i.second,
},
buildMode == bmRepair ? bmRepair : bmNormal));
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
}
}

/* Copy the input sources from the eval store to the build
store. */
Expand Down Expand Up @@ -501,7 +513,7 @@ void DerivationGoal::inputsRealised()
return ia.deferred;
},
[&](const DerivationType::ContentAddressed & ca) {
return !fullDrv.inputDrvs.empty() && (
return !fullDrv.inputDrvs.map.empty() && (
ca.fixed
/* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */
Expand All @@ -515,7 +527,7 @@ void DerivationGoal::inputsRealised()
}
}, drvType.raw);

if (resolveDrv && !fullDrv.inputDrvs.empty()) {
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
experimentalFeatureSettings.require(Xp::CaDerivations);

/* We are be able to resolve this derivation based on the
Expand Down Expand Up @@ -552,11 +564,13 @@ void DerivationGoal::inputsRealised()
return;
}

for (auto & [depDrvPath, wantedDepOutputs] : fullDrv.inputDrvs) {
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;

accumInputPaths = [&](const StorePath & depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
/* Add the relevant output closures of the input derivation
`i' as input paths. Only add the closures of output paths
that are specified as inputs. */
for (auto & j : wantedDepOutputs) {
auto getOutput = [&](const std::string & outputName) {
/* TODO (impure derivations-induced tech debt):
Tracking input derivation outputs statefully through the
goals is error prone and has led to bugs.
Expand All @@ -568,21 +582,30 @@ void DerivationGoal::inputsRealised()
a representation in the store, which is a usability problem
in itself. When implementing this logic entirely with lookups
make sure that they're cached. */
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
worker.store.computeFSClosure(*outPath, inputPaths);
if (auto outPath = get(inputDrvOutputs, { depDrvPath, outputName })) {
return *outPath;
}
else {
auto outMap = worker.evalStore.queryDerivationOutputMap(depDrvPath);
auto outMapPath = outMap.find(j);
auto outMapPath = outMap.find(outputName);
if (outMapPath == outMap.end()) {
throw Error(
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
worker.store.printStorePath(drvPath), outputName, worker.store.printStorePath(depDrvPath));
}
worker.store.computeFSClosure(outMapPath->second, inputPaths);
return outMapPath->second;
}
}
}
};

for (auto & outputName : inputNode.value)
worker.store.computeFSClosure(getOutput(outputName), inputPaths);

for (auto & [outputName, childNode] : inputNode.childMap)
accumInputPaths(getOutput(outputName), childNode);
};

for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
accumInputPaths(depDrvPath, depNode);
}

/* Second, the input sources. */
Expand Down Expand Up @@ -1475,22 +1498,24 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
if (!useDerivation) return;
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());

auto * dg = tryGetConcreteDrvGoal(waitee);
if (!dg) return;
std::optional info = tryGetConcreteDrvGoal(waitee);
if (!info) return;
const auto & [dg, drvReq] = *info;

auto outputs = fullDrv.inputDrvs.find(dg->drvPath);
if (outputs == fullDrv.inputDrvs.end()) return;
auto * nodeP = fullDrv.inputDrvs.findSlot(drvReq.get());
if (!nodeP) return;
auto & outputs = nodeP->value;

for (auto & outputName : outputs->second) {
auto buildResult = dg->getBuildResult(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(dg->drvPath),
for (auto & outputName : outputs) {
auto buildResult = dg.get().getBuildResult(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(dg.get().drvPath),
.outputs = OutputsSpec::Names { outputName },
});
if (buildResult.success()) {
auto i = buildResult.builtOutputs.find(outputName);
if (i != buildResult.builtOutputs.end())
inputDrvOutputs.insert_or_assign(
{ dg->drvPath, outputName },
{ dg.get().drvPath, outputName },
i->second.outPath);
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/libstore/build/worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -594,11 +594,14 @@ GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal)
return subGoal;
}

const DerivationGoal * tryGetConcreteDrvGoal(GoalPtr waitee)
std::optional<std::pair<std::reference_wrapper<const DerivationGoal>, std::reference_wrapper<const SingleDerivedPath>>> tryGetConcreteDrvGoal(GoalPtr waitee)
{
auto * odg = dynamic_cast<CreateDerivationAndRealiseGoal *>(&*waitee);
if (!odg) return nullptr;
return &*odg->concreteDrvGoal;
if (!odg) return std::nullopt;
return {{
std::cref(*odg->concreteDrvGoal),
std::cref(*odg->drvReq),
}};
}

}
3 changes: 2 additions & 1 deletion src/libstore/build/worker.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
* we have made the function, written in `worker.cc` where all the goal
* types are visible, and use it instead.
*/
const DerivationGoal * tryGetConcreteDrvGoal(GoalPtr waitee);

std::optional<std::pair<std::reference_wrapper<const DerivationGoal>, std::reference_wrapper<const SingleDerivedPath>>> tryGetConcreteDrvGoal(GoalPtr waitee);

/**
* A mapping used to remember for each child process to what goal it
Expand Down
Loading

0 comments on commit e34493a

Please sign in to comment.