1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00
This commit is contained in:
Bernardo Meurer 2025-11-07 16:33:09 -05:00 committed by GitHub
commit 0bb05c685a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 108 additions and 77 deletions

View file

@ -989,19 +989,22 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
error if a cycle is detected and roll back the error if a cycle is detected and roll back the
transaction. Cycles can only occur when a derivation transaction. Cycles can only occur when a derivation
has multiple outputs. */ has multiple outputs. */
topoSort( auto topoSortResult = topoSort(paths, {[&](const StorePath & path) {
paths, auto i = infos.find(path);
{[&](const StorePath & path) { return i == infos.end() ? StorePathSet() : i->second.references;
auto i = infos.find(path); }});
return i == infos.end() ? StorePathSet() : i->second.references;
}}, std::visit(
{[&](const StorePath & path, const StorePath & parent) { overloaded{
return BuildError( [&](Cycle<StorePath> & cycle) {
BuildResult::Failure::OutputRejected, throw BuildError(
"cycle detected in the references of '%s' from '%s'", BuildResult::Failure::OutputRejected,
printStorePath(path), "cycle detected in the references of '%s' from '%s'",
printStorePath(parent)); printStorePath(cycle.path),
}}); printStorePath(cycle.parent));
},
[](auto &) { /* Success, continue */ }},
topoSortResult);
txn.commit(); txn.commit();
}); });

View file

@ -311,22 +311,25 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
StorePaths Store::topoSortPaths(const StorePathSet & paths) StorePaths Store::topoSortPaths(const StorePathSet & paths)
{ {
return topoSort( auto result = topoSort(paths, {[&](const StorePath & path) {
paths, try {
{[&](const StorePath & path) { return queryPathInfo(path)->references;
try { } catch (InvalidPath &) {
return queryPathInfo(path)->references; return StorePathSet();
} catch (InvalidPath &) { }
return StorePathSet(); }});
}
}}, return std::visit(
{[&](const StorePath & path, const StorePath & parent) { overloaded{
return BuildError( [&](Cycle<StorePath> & cycle) -> StorePaths {
BuildResult::Failure::OutputRejected, throw BuildError(
"cycle detected in the references of '%s' from '%s'", BuildResult::Failure::OutputRejected,
printStorePath(path), "cycle detected in the references of '%s' from '%s'",
printStorePath(parent)); printStorePath(cycle.path),
}}); printStorePath(cycle.parent));
},
[](auto & sorted) { return sorted; }},
result);
} }
std::map<DrvOutput, StorePath> std::map<DrvOutput, StorePath>

View file

@ -1473,43 +1473,46 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
outputStats.insert_or_assign(outputName, std::move(st)); outputStats.insert_or_assign(outputName, std::move(st));
} }
auto sortedOutputNames = topoSort( auto topoSortResult = topoSort(outputsToSort, {[&](const std::string & name) {
outputsToSort, auto orifu = get(outputReferencesIfUnregistered, name);
{[&](const std::string & name) { if (!orifu)
auto orifu = get(outputReferencesIfUnregistered, name); throw BuildError(
if (!orifu) BuildResult::Failure::OutputRejected,
"no output reference for '%s' in build of '%s'",
name,
store.printStorePath(drvPath));
return std::visit(
overloaded{
/* Since we'll use the already installed versions of these, we
can treat them as leaves and ignore any references they
have. */
[&](const AlreadyRegistered &) { return StringSet{}; },
[&](const PerhapsNeedToRegister & refs) {
StringSet referencedOutputs;
/* FIXME build inverted map up front so no quadratic waste here */
for (auto & r : refs.refs)
for (auto & [o, p] : scratchOutputs)
if (r == p)
referencedOutputs.insert(o);
return referencedOutputs;
},
},
*orifu);
}});
auto sortedOutputNames = std::visit(
overloaded{
[&](Cycle<std::string> & cycle) -> std::vector<std::string> {
// TODO with more -vvvv also show the temporary paths for manual inspection.
throw BuildError( throw BuildError(
BuildResult::Failure::OutputRejected, BuildResult::Failure::OutputRejected,
"no output reference for '%s' in build of '%s'", "cycle detected in build of '%s' in the references of output '%s' from output '%s'",
name, store.printStorePath(drvPath),
store.printStorePath(drvPath)); cycle.path,
return std::visit( cycle.parent);
overloaded{ },
/* Since we'll use the already installed versions of these, we [](auto & sorted) { return sorted; }},
can treat them as leaves and ignore any references they topoSortResult);
have. */
[&](const AlreadyRegistered &) { return StringSet{}; },
[&](const PerhapsNeedToRegister & refs) {
StringSet referencedOutputs;
/* FIXME build inverted map up front so no quadratic waste here */
for (auto & r : refs.refs)
for (auto & [o, p] : scratchOutputs)
if (r == p)
referencedOutputs.insert(o);
return referencedOutputs;
},
},
*orifu);
}},
{[&](const std::string & path, const std::string & parent) {
// TODO with more -vvvv also show the temporary paths for manual inspection.
return BuildError(
BuildResult::Failure::OutputRejected,
"cycle detected in build of '%s' in the references of output '%s' from output '%s'",
store.printStorePath(drvPath),
path,
parent);
}});
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end()); std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());

View file

@ -2,39 +2,61 @@
///@file ///@file
#include "nix/util/error.hh" #include "nix/util/error.hh"
#include <variant>
namespace nix { namespace nix {
template<typename T>
struct Cycle
{
T path;
T parent;
};
template<typename T>
using TopoSortResult = std::variant<std::vector<T>, Cycle<T>>;
template<typename T, typename Compare> template<typename T, typename Compare>
std::vector<T> topoSort( TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T, Compare>(const T &)> getChildren)
std::set<T, Compare> items,
std::function<std::set<T, Compare>(const T &)> getChildren,
std::function<Error(const T &, const T &)> makeCycleError)
{ {
std::vector<T> sorted; std::vector<T> sorted;
decltype(items) visited, parents; decltype(items) visited, parents;
auto dfsVisit = [&](this auto & dfsVisit, const T & path, const T * parent) { std::function<std::optional<Cycle<T>>(const T & path, const T * parent)> dfsVisit;
if (parents.count(path))
throw makeCycleError(path, *parent);
if (!visited.insert(path).second) dfsVisit = [&](const T & path, const T * parent) -> std::optional<Cycle<T>> {
return; if (parents.count(path)) {
return Cycle{path, *parent};
}
if (!visited.insert(path).second) {
return std::nullopt;
}
parents.insert(path); parents.insert(path);
auto references = getChildren(path); auto references = getChildren(path);
for (auto & i : references) for (auto & i : references)
/* Don't traverse into items that don't exist in our starting set. */ /* Don't traverse into items that don't exist in our starting set. */
if (i != path && items.count(i)) if (i != path && items.count(i)) {
dfsVisit(i, &path); auto result = dfsVisit(i, &path);
if (result.has_value()) {
return result;
}
}
sorted.push_back(path); sorted.push_back(path);
parents.erase(path); parents.erase(path);
return std::nullopt;
}; };
for (auto & i : items) for (auto & i : items) {
dfsVisit(i, nullptr); auto cycle = dfsVisit(i, nullptr);
if (cycle.has_value()) {
return *cycle;
}
}
std::reverse(sorted.begin(), sorted.end()); std::reverse(sorted.begin(), sorted.end());