* Don't throw an exception when a build fails. Just terminate the

goal and allow the problem to be handled elsewhere (e.g., at
  top-level).
This commit is contained in:
Eelco Dolstra 2004-06-25 10:21:44 +00:00
parent 795d9f8b08
commit e4883211f9
1 changed files with 215 additions and 59 deletions

View File

@ -48,11 +48,17 @@ protected:
/* Number of goals we are waiting for. */ /* Number of goals we are waiting for. */
unsigned int nrWaitees; unsigned int nrWaitees;
/* Number of goals we were waiting for that have failed. */
unsigned int nrFailed;
/* Whether amDone() has been called. */
bool done;
Goal(Worker & _worker) : worker(_worker) Goal(Worker & _worker) : worker(_worker)
{ {
nrWaitees = 0; done = false;
} }
virtual ~Goal() virtual ~Goal()
@ -60,6 +66,12 @@ protected:
printMsg(lvlVomit, "goal destroyed"); printMsg(lvlVomit, "goal destroyed");
} }
void resetWaitees(int nrWaitees)
{
this->nrWaitees = nrWaitees;
nrFailed = 0;
}
public: public:
virtual void work() = 0; virtual void work() = 0;
@ -70,9 +82,14 @@ public:
void addWaiter(GoalPtr waiter); void addWaiter(GoalPtr waiter);
void waiteeDone(); virtual void waiteeDone(bool success);
void amDone(); protected:
virtual void waiteeFailed()
{
}
void amDone(bool success = true);
}; };
@ -160,6 +177,13 @@ public:
}; };
class BuildError : public Error
{
public:
BuildError(const format & f) : Error(f) { };
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -170,18 +194,25 @@ void Goal::addWaiter(GoalPtr waiter)
} }
void Goal::waiteeDone() void Goal::waiteeDone(bool success)
{ {
assert(nrWaitees > 0); assert(nrWaitees > 0);
/* Note: waiteeFailed should never call amDone()! */
if (!success) {
++nrFailed;
waiteeFailed();
}
if (!--nrWaitees) worker.wakeUp(shared_from_this()); if (!--nrWaitees) worker.wakeUp(shared_from_this());
} }
void Goal::amDone() void Goal::amDone(bool success)
{ {
printMsg(lvlVomit, "done"); printMsg(lvlVomit, "done");
assert(!done);
done = true;
for (Goals::iterator i = waiters.begin(); i != waiters.end(); ++i) for (Goals::iterator i = waiters.begin(); i != waiters.end(); ++i)
(*i)->waiteeDone(); (*i)->waiteeDone(success);
worker.removeGoal(shared_from_this()); worker.removeGoal(shared_from_this());
} }
@ -375,7 +406,7 @@ void NormalisationGoal::init()
/* The first thing to do is to make sure that the store expression /* The first thing to do is to make sure that the store expression
exists. If it doesn't, it may be created through a exists. If it doesn't, it may be created through a
substitute. */ substitute. */
nrWaitees = 1; resetWaitees(1);
worker.addSubstitutionGoal(nePath, shared_from_this()); worker.addSubstitutionGoal(nePath, shared_from_this());
state = &NormalisationGoal::haveStoreExpr; state = &NormalisationGoal::haveStoreExpr;
@ -386,6 +417,14 @@ void NormalisationGoal::haveStoreExpr()
{ {
trace("loading store expression"); trace("loading store expression");
if (nrFailed != 0) {
printMsg(lvlError,
format("cannot normalise missing store expression `%1%'")
% nePath);
amDone(false);
return;
}
assert(isValidPath(nePath)); assert(isValidPath(nePath));
/* Get the store expression. */ /* Get the store expression. */
@ -403,7 +442,7 @@ void NormalisationGoal::haveStoreExpr()
i != expr.derivation.inputs.end(); ++i) i != expr.derivation.inputs.end(); ++i)
worker.addNormalisationGoal(*i, shared_from_this()); worker.addNormalisationGoal(*i, shared_from_this());
nrWaitees = expr.derivation.inputs.size(); resetWaitees(expr.derivation.inputs.size());
state = &NormalisationGoal::inputNormalised; state = &NormalisationGoal::inputNormalised;
} }
@ -413,6 +452,15 @@ void NormalisationGoal::inputNormalised()
{ {
trace("all inputs normalised"); trace("all inputs normalised");
if (nrFailed != 0) {
printMsg(lvlError,
format("cannot normalise derivation `%1%': "
"%2% closure element(s) could not be normalised")
% nePath % nrFailed);
amDone(false);
return;
}
/* Inputs must also be realised before we can build this goal. */ /* Inputs must also be realised before we can build this goal. */
for (PathSet::iterator i = expr.derivation.inputs.begin(); for (PathSet::iterator i = expr.derivation.inputs.begin();
i != expr.derivation.inputs.end(); ++i) i != expr.derivation.inputs.end(); ++i)
@ -424,7 +472,7 @@ void NormalisationGoal::inputNormalised()
worker.addRealisationGoal(neInput, shared_from_this()); worker.addRealisationGoal(neInput, shared_from_this());
} }
nrWaitees = expr.derivation.inputs.size(); resetWaitees(expr.derivation.inputs.size());
state = &NormalisationGoal::inputRealised; state = &NormalisationGoal::inputRealised;
} }
@ -434,6 +482,15 @@ void NormalisationGoal::inputRealised()
{ {
trace("all inputs realised"); trace("all inputs realised");
if (nrFailed != 0) {
printMsg(lvlError,
format("cannot normalise derivation `%1%': "
"%2% closure element(s) could not be realised")
% nePath % nrFailed);
amDone(false);
return;
}
/* Okay, try to build. Note that here we don't wait for a build /* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a slot to become available, since we don't need one if there is a
build hook. */ build hook. */
@ -446,42 +503,50 @@ void NormalisationGoal::tryToBuild()
{ {
trace("trying to build"); trace("trying to build");
/* Is the build hook willing to accept this job? */ try {
switch (tryBuildHook()) {
case rpAccept: /* Is the build hook willing to accept this job? */
/* Yes, it has started doing so. Wait until we get switch (tryBuildHook()) {
EOF from the hook. */ case rpAccept:
state = &NormalisationGoal::buildDone; /* Yes, it has started doing so. Wait until we get
return; EOF from the hook. */
case rpPostpone: state = &NormalisationGoal::buildDone;
/* Not now; wait until at least one child finishes. */ return;
case rpPostpone:
/* Not now; wait until at least one child finishes. */
worker.waitForBuildSlot(shared_from_this());
return;
case rpDecline:
/* We should do it ourselves. */
break;
case rpDone:
/* Somebody else did it (there is a successor now). */
amDone();
return;
}
/* Make sure that we are allowed to start a build. */
if (!worker.canBuildMore()) {
worker.waitForBuildSlot(shared_from_this()); worker.waitForBuildSlot(shared_from_this());
return; return;
case rpDecline: }
/* We should do it ourselves. */
break; /* Acquire locks and such. If we then see that there now is a
case rpDone: successor, we're done. */
/* Somebody else did it (there is a successor now). */ if (!prepareBuild()) {
amDone(); amDone();
return; return;
} }
/* Make sure that we are allowed to start a build. */ /* Okay, we have to build. */
if (!worker.canBuildMore()) { startBuilder();
worker.waitForBuildSlot(shared_from_this());
} catch (BuildError & e) {
printMsg(lvlError, e.msg());
amDone(false);
return; return;
} }
/* Acquire locks and such. If we then see that there now is a
successor, we're done. */
if (!prepareBuild()) {
amDone();
return;
}
/* Okay, we have to build. */
startBuilder();
/* This state will be reached when we get EOF on the child's /* This state will be reached when we get EOF on the child's
log pipe. */ log pipe. */
state = &NormalisationGoal::buildDone; state = &NormalisationGoal::buildDone;
@ -514,15 +579,23 @@ void NormalisationGoal::buildDone()
/* Check the exit status. */ /* Check the exit status. */
if (!statusOk(status)) { if (!statusOk(status)) {
deleteTmpDir(false); deleteTmpDir(false);
throw Error(format("builder for `%1%' %2%") printMsg(lvlError, format("builder for `%1%' %2%")
% nePath % statusToString(status)); % nePath % statusToString(status));
amDone(false);
return;
} }
deleteTmpDir(true); deleteTmpDir(true);
/* Compute a closure store expression, and register it as our /* Compute a closure store expression, and register it as our
successor. */ successor. */
createClosure(); try {
createClosure();
} catch (BuildError & e) {
printMsg(lvlError, e.msg());
amDone(false);
return;
}
amDone(); amDone();
} }
@ -791,8 +864,9 @@ void NormalisationGoal::startBuilder()
/* Right platform? */ /* Right platform? */
if (expr.derivation.platform != thisSystem) if (expr.derivation.platform != thisSystem)
throw Error(format("a `%1%' is required, but I am a `%2%'") throw BuildError(
% expr.derivation.platform % thisSystem); format("a `%1%' is required to build `%3%', but I am a `%2%'")
% expr.derivation.platform % thisSystem % nePath);
/* If any of the outputs already exist but are not registered, /* If any of the outputs already exist but are not registered,
delete them. */ delete them. */
@ -923,8 +997,11 @@ void NormalisationGoal::createClosure()
i != expr.derivation.outputs.end(); ++i) i != expr.derivation.outputs.end(); ++i)
{ {
Path path = *i; Path path = *i;
if (!pathExists(path)) if (!pathExists(path)) {
throw Error(format("output path `%1%' does not exist") % path); throw BuildError(
format("builder for `%1%' failed to produce output path `%1%'")
% nePath % path);
}
nf.closure.roots.insert(path); nf.closure.roots.insert(path);
makePathReadOnly(path); makePathReadOnly(path);
@ -1038,18 +1115,18 @@ void NormalisationGoal::initChild()
commonChildInit(logPipe); commonChildInit(logPipe);
if (chdir(tmpDir.c_str()) == -1) if (chdir(tmpDir.c_str()) == -1)
throw SysError(format("changing into to `%1%'") % tmpDir); throw SysError(format("changing into `%1%'") % tmpDir);
/* When running a hook, dup the communication pipes. */ /* When running a hook, dup the communication pipes. */
bool inHook = fromHook.writeSide.isOpen(); bool inHook = fromHook.writeSide.isOpen();
if (inHook) { if (inHook) {
fromHook.readSide.close(); fromHook.readSide.close();
if (dup2(fromHook.writeSide, 3) == -1) if (dup2(fromHook.writeSide, 3) == -1)
throw SysError("dup1"); throw SysError("dupping from-hook write side");
toHook.writeSide.close(); toHook.writeSide.close();
if (dup2(toHook.readSide, 4) == -1) if (dup2(toHook.readSide, 4) == -1)
throw SysError("dup2"); throw SysError("dupping to-hook read side");
} }
/* Close all other file descriptors. */ /* Close all other file descriptors. */
@ -1139,7 +1216,7 @@ void RealisationGoal::init()
/* The first thing to do is to make sure that the store expression /* The first thing to do is to make sure that the store expression
exists. If it doesn't, it may be created through a exists. If it doesn't, it may be created through a
substitute. */ substitute. */
nrWaitees = 1; resetWaitees(1);
worker.addSubstitutionGoal(nePath, shared_from_this()); worker.addSubstitutionGoal(nePath, shared_from_this());
state = &RealisationGoal::haveStoreExpr; state = &RealisationGoal::haveStoreExpr;
@ -1150,6 +1227,14 @@ void RealisationGoal::haveStoreExpr()
{ {
trace("loading store expression"); trace("loading store expression");
if (nrFailed != 0) {
printMsg(lvlError,
format("cannot realise missing store expression `%1%'")
% nePath);
amDone(false);
return;
}
assert(isValidPath(nePath)); assert(isValidPath(nePath));
/* Get the store expression. */ /* Get the store expression. */
@ -1165,7 +1250,7 @@ void RealisationGoal::haveStoreExpr()
i != expr.closure.elems.end(); ++i) i != expr.closure.elems.end(); ++i)
worker.addSubstitutionGoal(i->first, shared_from_this()); worker.addSubstitutionGoal(i->first, shared_from_this());
nrWaitees = expr.closure.elems.size(); resetWaitees(expr.closure.elems.size());
state = &RealisationGoal::elemFinished; state = &RealisationGoal::elemFinished;
} }
@ -1175,6 +1260,16 @@ void RealisationGoal::elemFinished()
{ {
trace("all closure elements present"); trace("all closure elements present");
if (nrFailed != 0) {
printMsg(lvlError,
format("cannot realise closure `%1%': "
"%2% closure element(s) are not present "
"and could not be substituted")
% nePath % nrFailed);
amDone(false);
return;
}
amDone(); amDone();
} }
@ -1269,15 +1364,21 @@ void SubstitutionGoal::tryNext()
{ {
trace("trying next substitute"); trace("trying next substitute");
if (subs.size() == 0) throw Error( if (subs.size() == 0) {
format("path `%1%' is required, but it has no (remaining) substitutes") /* None left. Terminate this goal and let someone else deal
with it. */
printMsg(lvlError,
format("path `%1%' is required, but it has no (remaining) substitutes")
% storePath); % storePath);
amDone(false);
return;
}
sub = subs.front(); sub = subs.front();
subs.pop_front(); subs.pop_front();
/* Normalise the substitute store expression. */ /* Normalise the substitute store expression. */
worker.addNormalisationGoal(sub.storeExpr, shared_from_this()); worker.addNormalisationGoal(sub.storeExpr, shared_from_this());
nrWaitees = 1; resetWaitees(1);
state = &SubstitutionGoal::exprNormalised; state = &SubstitutionGoal::exprNormalised;
} }
@ -1287,11 +1388,17 @@ void SubstitutionGoal::exprNormalised()
{ {
trace("substitute store expression normalised"); trace("substitute store expression normalised");
if (nrFailed != 0) {
tryNext();
return;
}
/* Realise the substitute store expression. */ /* Realise the substitute store expression. */
if (!querySuccessor(sub.storeExpr, nfSub)) if (!querySuccessor(sub.storeExpr, nfSub))
nfSub = sub.storeExpr; nfSub = sub.storeExpr;
worker.addRealisationGoal(nfSub, shared_from_this()); worker.addRealisationGoal(nfSub, shared_from_this());
nrWaitees = 1;
resetWaitees(1);
state = &SubstitutionGoal::exprRealised; state = &SubstitutionGoal::exprRealised;
} }
@ -1301,6 +1408,11 @@ void SubstitutionGoal::exprRealised()
{ {
trace("substitute store expression realised"); trace("substitute store expression realised");
if (nrFailed != 0) {
tryNext();
return;
}
state = &SubstitutionGoal::tryToRun; state = &SubstitutionGoal::tryToRun;
worker.waitForBuildSlot(shared_from_this()); worker.waitForBuildSlot(shared_from_this());
} }
@ -1452,6 +1564,40 @@ void SubstitutionGoal::trace(const format & f)
//////////////////////////////////////////////////////////////////////
/* A fake goal used to receive notification of success or failure of
other goals. */
class PseudoGoal : public Goal
{
private:
bool success;
public:
PseudoGoal(Worker & _worker) : Goal(_worker)
{
success = true;
}
void work()
{
abort();
}
void waiteeDone(bool success)
{
if (!success) this->success = false;
}
bool isOkay()
{
return success;
}
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -1687,13 +1833,15 @@ Path normaliseStoreExpr(const Path & nePath)
startNest(nest, lvlDebug, format("normalising `%1%'") % nePath); startNest(nest, lvlDebug, format("normalising `%1%'") % nePath);
Worker worker; Worker worker;
worker.addNormalisationGoal(nePath, GoalPtr()); shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker));
worker.addNormalisationGoal(nePath, pseudo);
worker.run(); worker.run();
if (!pseudo->isOkay())
throw Error(format("normalisation of store expression `%1%' failed") % nePath);
Path nfPath; Path nfPath;
if (!querySuccessor(nePath, nfPath)) if (!querySuccessor(nePath, nfPath)) abort();
throw Error("there should be a successor");
return nfPath; return nfPath;
} }
@ -1703,8 +1851,12 @@ void realiseClosure(const Path & nePath)
startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath); startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath);
Worker worker; Worker worker;
worker.addRealisationGoal(nePath, GoalPtr()); shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker));
worker.addRealisationGoal(nePath, pseudo);
worker.run(); worker.run();
if (!pseudo->isOkay())
throw Error(format("realisation of closure `%1%' failed") % nePath);
} }
@ -1714,6 +1866,10 @@ void ensurePath(const Path & path)
if (isValidPath(path)) return; if (isValidPath(path)) return;
Worker worker; Worker worker;
worker.addSubstitutionGoal(path, GoalPtr()); shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker));
worker.addSubstitutionGoal(path, pseudo);
worker.run(); worker.run();
if (!pseudo->isOkay())
throw Error(format("path `%1%' does not exist and cannot be created") % path);
} }