Skip to content

Commit 182ae39

Browse files
lovesegfaultMa27
authored andcommitted
refactor(libutil/topo-sort): return variant instead of throwing
The variant has on the left-hand side the topologically sorted vector and the right-hand side is a pair showing the path and its parent that represent a cycle in the graph making the sort impossible. This change prepares for enhanced cycle error messages that can provide more context about the cycle. The variant approach allows callers to handle cycles more flexibly, enabling better error reporting that shows the full cycle path and which files are involved. Adapted from Lix commit f7871fcb5. Change-Id: I70a987f470437df8beb3b1cc203ff88701d0aa1b Co-Authored-By: Maximilian Bosch <[email protected]>
1 parent 7a60f14 commit 182ae39

File tree

6 files changed

+427
-77
lines changed

6 files changed

+427
-77
lines changed

src/libstore/local-store.cc

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -989,19 +989,22 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
989989
error if a cycle is detected and roll back the
990990
transaction. Cycles can only occur when a derivation
991991
has multiple outputs. */
992-
topoSort(
993-
paths,
994-
{[&](const StorePath & path) {
995-
auto i = infos.find(path);
996-
return i == infos.end() ? StorePathSet() : i->second.references;
997-
}},
998-
{[&](const StorePath & path, const StorePath & parent) {
999-
return BuildError(
1000-
BuildResult::Failure::OutputRejected,
1001-
"cycle detected in the references of '%s' from '%s'",
1002-
printStorePath(path),
1003-
printStorePath(parent));
1004-
}});
992+
auto topoSortResult = topoSort(paths, {[&](const StorePath & path) {
993+
auto i = infos.find(path);
994+
return i == infos.end() ? StorePathSet() : i->second.references;
995+
}});
996+
997+
std::visit(
998+
overloaded{
999+
[&](const Cycle<StorePath> & cycle) {
1000+
throw BuildError(
1001+
BuildResult::Failure::OutputRejected,
1002+
"cycle detected in the references of '%s' from '%s'",
1003+
printStorePath(cycle.path),
1004+
printStorePath(cycle.parent));
1005+
},
1006+
[](auto &) { /* Success, continue */ }},
1007+
topoSortResult);
10051008

10061009
txn.commit();
10071010
});

src/libstore/misc.cc

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -311,22 +311,25 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
311311

312312
StorePaths Store::topoSortPaths(const StorePathSet & paths)
313313
{
314-
return topoSort(
315-
paths,
316-
{[&](const StorePath & path) {
317-
try {
318-
return queryPathInfo(path)->references;
319-
} catch (InvalidPath &) {
320-
return StorePathSet();
321-
}
322-
}},
323-
{[&](const StorePath & path, const StorePath & parent) {
324-
return BuildError(
325-
BuildResult::Failure::OutputRejected,
326-
"cycle detected in the references of '%s' from '%s'",
327-
printStorePath(path),
328-
printStorePath(parent));
329-
}});
314+
auto result = topoSort(paths, {[&](const StorePath & path) {
315+
try {
316+
return queryPathInfo(path)->references;
317+
} catch (InvalidPath &) {
318+
return StorePathSet();
319+
}
320+
}});
321+
322+
return std::visit(
323+
overloaded{
324+
[&](const Cycle<StorePath> & cycle) -> StorePaths {
325+
throw BuildError(
326+
BuildResult::Failure::OutputRejected,
327+
"cycle detected in the references of '%s' from '%s'",
328+
printStorePath(cycle.path),
329+
printStorePath(cycle.parent));
330+
},
331+
[](const auto & sorted) { return sorted; }},
332+
result);
330333
}
331334

332335
std::map<DrvOutput, StorePath>

src/libstore/unix/build/derivation-builder.cc

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,43 +1473,46 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
14731473
outputStats.insert_or_assign(outputName, std::move(st));
14741474
}
14751475

1476-
auto sortedOutputNames = topoSort(
1477-
outputsToSort,
1478-
{[&](const std::string & name) {
1479-
auto orifu = get(outputReferencesIfUnregistered, name);
1480-
if (!orifu)
1476+
auto topoSortResult = topoSort(outputsToSort, {[&](const std::string & name) {
1477+
auto orifu = get(outputReferencesIfUnregistered, name);
1478+
if (!orifu)
1479+
throw BuildError(
1480+
BuildResult::Failure::OutputRejected,
1481+
"no output reference for '%s' in build of '%s'",
1482+
name,
1483+
store.printStorePath(drvPath));
1484+
return std::visit(
1485+
overloaded{
1486+
/* Since we'll use the already installed versions of these, we
1487+
can treat them as leaves and ignore any references they
1488+
have. */
1489+
[&](const AlreadyRegistered &) { return StringSet{}; },
1490+
[&](const PerhapsNeedToRegister & refs) {
1491+
StringSet referencedOutputs;
1492+
/* FIXME build inverted map up front so no quadratic waste here */
1493+
for (auto & r : refs.refs)
1494+
for (auto & [o, p] : scratchOutputs)
1495+
if (r == p)
1496+
referencedOutputs.insert(o);
1497+
return referencedOutputs;
1498+
},
1499+
},
1500+
*orifu);
1501+
}});
1502+
1503+
auto sortedOutputNames = std::visit(
1504+
overloaded{
1505+
[&](Cycle<std::string> & cycle) -> std::vector<std::string> {
1506+
// TODO with more -vvvv also show the temporary paths for manual inspection.
14811507
throw BuildError(
14821508
BuildResult::Failure::OutputRejected,
1483-
"no output reference for '%s' in build of '%s'",
1484-
name,
1485-
store.printStorePath(drvPath));
1486-
return std::visit(
1487-
overloaded{
1488-
/* Since we'll use the already installed versions of these, we
1489-
can treat them as leaves and ignore any references they
1490-
have. */
1491-
[&](const AlreadyRegistered &) { return StringSet{}; },
1492-
[&](const PerhapsNeedToRegister & refs) {
1493-
StringSet referencedOutputs;
1494-
/* FIXME build inverted map up front so no quadratic waste here */
1495-
for (auto & r : refs.refs)
1496-
for (auto & [o, p] : scratchOutputs)
1497-
if (r == p)
1498-
referencedOutputs.insert(o);
1499-
return referencedOutputs;
1500-
},
1501-
},
1502-
*orifu);
1503-
}},
1504-
{[&](const std::string & path, const std::string & parent) {
1505-
// TODO with more -vvvv also show the temporary paths for manual inspection.
1506-
return BuildError(
1507-
BuildResult::Failure::OutputRejected,
1508-
"cycle detected in build of '%s' in the references of output '%s' from output '%s'",
1509-
store.printStorePath(drvPath),
1510-
path,
1511-
parent);
1512-
}});
1509+
"cycle detected in build of '%s' in the references of output '%s' from output '%s'",
1510+
store.printStorePath(drvPath),
1511+
cycle.path,
1512+
cycle.parent);
1513+
},
1514+
[](auto & sorted) { return sorted; }},
1515+
topoSortResult);
15131516

15141517
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());
15151518

src/libutil-tests/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ sources = files(
7474
'strings.cc',
7575
'suggestions.cc',
7676
'terminal.cc',
77+
'topo-sort.cc',
7778
'url.cc',
7879
'util.cc',
7980
'xml-writer.cc',

0 commit comments

Comments
 (0)