* Reject a build if there is a cycle among the outputs. This is
necessary because existing code assumes that the references graph is acyclic.
This commit is contained in:
parent
254b3399ba
commit
b1004f40f7
4 changed files with 24 additions and 9 deletions
|
@ -278,10 +278,6 @@ public:
|
|||
};
|
||||
|
||||
|
||||
MakeError(SubstError, Error)
|
||||
MakeError(BuildError, Error) /* denotes a permanent build failure */
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
@ -1982,7 +1978,8 @@ void DerivationGoal::computeClosure()
|
|||
}
|
||||
|
||||
/* Register each output path as valid, and register the sets of
|
||||
paths referenced by each of them. */
|
||||
paths referenced by each of them. If there are cycles in the
|
||||
outputs, this will fail. */
|
||||
ValidPathInfos infos;
|
||||
foreach (DerivationOutputs::iterator, i, drv.outputs) {
|
||||
ValidPathInfo info;
|
||||
|
|
|
@ -372,8 +372,13 @@ static void addAdditionalRoots(StoreAPI & store, PathSet & roots)
|
|||
|
||||
|
||||
static void dfsVisit(StoreAPI & store, const PathSet & paths,
|
||||
const Path & path, PathSet & visited, Paths & sorted)
|
||||
const Path & path, PathSet & visited, Paths & sorted,
|
||||
PathSet & parents)
|
||||
{
|
||||
if (parents.find(path) != parents.end())
|
||||
throw BuildError(format("cycle detected in the references of `%1%'") % path);
|
||||
parents.insert(path);
|
||||
|
||||
if (visited.find(path) != visited.end()) return;
|
||||
visited.insert(path);
|
||||
|
||||
|
@ -385,18 +390,19 @@ static void dfsVisit(StoreAPI & store, const PathSet & paths,
|
|||
/* Don't traverse into paths that don't exist. That can
|
||||
happen due to substitutes for non-existent paths. */
|
||||
if (*i != path && paths.find(*i) != paths.end())
|
||||
dfsVisit(store, paths, *i, visited, sorted);
|
||||
dfsVisit(store, paths, *i, visited, sorted, parents);
|
||||
|
||||
sorted.push_front(path);
|
||||
parents.erase(path);
|
||||
}
|
||||
|
||||
|
||||
Paths topoSortPaths(StoreAPI & store, const PathSet & paths)
|
||||
{
|
||||
Paths sorted;
|
||||
PathSet visited;
|
||||
PathSet visited, parents;
|
||||
foreach (PathSet::const_iterator, i, paths)
|
||||
dfsVisit(store, paths, *i, visited, sorted);
|
||||
dfsVisit(store, paths, *i, visited, sorted, parents);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
|
|
@ -966,12 +966,14 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
|
|||
while (1) {
|
||||
try {
|
||||
SQLiteTxn txn(db);
|
||||
PathSet paths;
|
||||
|
||||
foreach (ValidPathInfos::const_iterator, i, infos) {
|
||||
assert(i->hash.type == htSHA256);
|
||||
/* !!! Maybe the registration info should be updated if the
|
||||
path is already valid. */
|
||||
if (!isValidPath(i->path)) addValidPath(*i);
|
||||
paths.insert(i->path);
|
||||
}
|
||||
|
||||
foreach (ValidPathInfos::const_iterator, i, infos) {
|
||||
|
@ -980,6 +982,12 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
|
|||
addReference(referrer, queryValidPathId(*j));
|
||||
}
|
||||
|
||||
/* Do a topological sort of the paths. This will throw an
|
||||
error if a cycle is detected and roll back the
|
||||
transaction. Cycles can only occur when a derivation
|
||||
has multiple outputs. */
|
||||
topoSortPaths(*this, paths);
|
||||
|
||||
txn.commit();
|
||||
break;
|
||||
} catch (SQLiteBusy & e) {
|
||||
|
|
|
@ -349,6 +349,10 @@ void exportPaths(StoreAPI & store, const Paths & paths,
|
|||
bool sign, Sink & sink);
|
||||
|
||||
|
||||
MakeError(SubstError, Error)
|
||||
MakeError(BuildError, Error) /* denotes a permanent build failure */
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue