* Some refactoring.

* Throw more exceptions as BuildErrors instead of Errors.  This
  matters when --keep-going is turned on.  (A BuildError is caught
  and terminates the goal in question, an Error terminates the
  program.)
This commit is contained in:
Eelco Dolstra 2006-12-08 17:26:21 +00:00
parent 9dbfe242e3
commit 06c4929958
1 changed files with 118 additions and 95 deletions

View File

@ -99,7 +99,7 @@ public:
void addWaitee(GoalPtr waitee); void addWaitee(GoalPtr waitee);
virtual void waiteeDone(GoalPtr waitee, bool success); virtual void waiteeDone(GoalPtr waitee, ExitCode result);
virtual void handleChildOutput(int fd, const string & data) virtual void handleChildOutput(int fd, const string & data)
{ {
@ -123,8 +123,13 @@ public:
return exitCode; return exitCode;
} }
void cancel()
{
amDone(ecFailed);
}
protected: protected:
void amDone(bool success = true); void amDone(ExitCode result);
}; };
@ -189,13 +194,24 @@ public:
/* Can we start another child process? */ /* Can we start another child process? */
bool canBuildMore(); bool canBuildMore();
/* Registers / unregisters a running child process. */ /* Registers a running child process. `inBuildSlot' means that
the process counts towards the jobs limit. */
void childStarted(GoalPtr goal, pid_t pid, void childStarted(GoalPtr goal, pid_t pid,
const set<int> & fds, bool inBuildSlot); const set<int> & fds, bool inBuildSlot);
/* Unregisters a running child process. `wakeSleepers' should be
false if there is no sense in waking up goals that are sleeping
because they can't run yet (e.g., there is no free build slot,
or the hook would still say `postpone'). */
void childTerminated(pid_t pid, bool wakeSleepers = true); void childTerminated(pid_t pid, bool wakeSleepers = true);
/* Add a goal to the set of goals waiting for a build slot. */ /* Put `goal' to sleep until a build slot becomes available (which
void waitForBuildSlot(GoalPtr goal, bool reallyWait = false); might be right away). */
void waitForBuildSlot(GoalPtr goal);
/* Put `goal' to sleep until a child process terminates, i.e., a
call is made to childTerminate(..., true). */
void waitForChildTermination(GoalPtr goal);
/* Loop until the specified top-level goals have finished. */ /* Loop until the specified top-level goals have finished. */
void run(const Goals & topGoals); void run(const Goals & topGoals);
@ -205,19 +221,8 @@ public:
}; };
class SubstError : public Error MakeError(SubstError, Error)
{ MakeError(BuildError, Error)
public:
SubstError(const format & f) : Error(f) { };
};
class BuildError : public Error
{
public:
BuildError(const format & f) : Error(f) { };
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -230,7 +235,7 @@ void Goal::addWaitee(GoalPtr waitee)
} }
void Goal::waiteeDone(GoalPtr waitee, bool success) void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
{ {
assert(waitees.find(waitee) != waitees.end()); assert(waitees.find(waitee) != waitees.end());
waitees.erase(waitee); waitees.erase(waitee);
@ -238,9 +243,9 @@ void Goal::waiteeDone(GoalPtr waitee, bool success)
trace(format("waitee `%1%' done; %2% left") % trace(format("waitee `%1%' done; %2% left") %
waitee->name % waitees.size()); waitee->name % waitees.size());
if (!success) ++nrFailed; if (result == ecFailed) ++nrFailed;
if (waitees.empty() || (!success && !keepGoing)) { if (waitees.empty() || (result == ecFailed && !keepGoing)) {
/* If we failed and keepGoing is not set, we remove all /* If we failed and keepGoing is not set, we remove all
remaining waitees. */ remaining waitees. */
@ -260,14 +265,15 @@ void Goal::waiteeDone(GoalPtr waitee, bool success)
} }
void Goal::amDone(bool success) void Goal::amDone(ExitCode result)
{ {
trace("done"); trace("done");
assert(exitCode == ecBusy); assert(exitCode == ecBusy);
exitCode = success ? ecSuccess : ecFailed; assert(result == ecSuccess || result == ecFailed);
exitCode = result;
for (WeakGoals::iterator i = waiters.begin(); i != waiters.end(); ++i) { for (WeakGoals::iterator i = waiters.begin(); i != waiters.end(); ++i) {
GoalPtr goal = i->lock(); GoalPtr goal = i->lock();
if (goal) goal->waiteeDone(shared_from_this(), success); if (goal) goal->waiteeDone(shared_from_this(), result);
} }
waiters.clear(); waiters.clear();
worker.removeGoal(shared_from_this()); worker.removeGoal(shared_from_this());
@ -439,7 +445,7 @@ void UserLock::acquire()
} }
} }
throw Error(format("all build users are currently in use; " throw BuildError(format("all build users are currently in use; "
"consider creating additional users and adding them to the `%1%' group") "consider creating additional users and adding them to the `%1%' group")
% buildUsersGroup); % buildUsersGroup);
} }
@ -715,7 +721,7 @@ void DerivationGoal::haveDerivation()
printMsg(lvlError, printMsg(lvlError,
format("cannot build missing derivation `%1%'") format("cannot build missing derivation `%1%'")
% drvPath); % drvPath);
amDone(false); amDone(ecFailed);
return; return;
} }
@ -733,7 +739,7 @@ void DerivationGoal::haveDerivation()
/* If they are all valid, then we're done. */ /* If they are all valid, then we're done. */
if (invalidOutputs.size() == 0) { if (invalidOutputs.size() == 0) {
amDone(true); amDone(ecSuccess);
return; return;
} }
@ -764,7 +770,7 @@ void DerivationGoal::outputsSubstituted()
nrFailed = 0; nrFailed = 0;
if (checkPathValidity(false).size() == 0) { if (checkPathValidity(false).size() == 0) {
amDone(true); amDone(ecSuccess);
return; return;
} }
@ -794,7 +800,7 @@ void DerivationGoal::inputsRealised()
format("cannot build derivation `%1%': " format("cannot build derivation `%1%': "
"%2% inputs could not be realised") "%2% inputs could not be realised")
% drvPath % nrFailed); % drvPath % nrFailed);
amDone(false); amDone(ecFailed);
return; return;
} }
@ -821,14 +827,14 @@ void DerivationGoal::tryToBuild()
return; return;
case rpPostpone: case rpPostpone:
/* Not now; wait until at least one child finishes. */ /* Not now; wait until at least one child finishes. */
worker.waitForBuildSlot(shared_from_this(), true); worker.waitForChildTermination(shared_from_this());
return; return;
case rpDecline: case rpDecline:
/* We should do it ourselves. */ /* We should do it ourselves. */
break; break;
case rpDone: case rpDone:
/* Somebody else did it. */ /* Somebody else did it. */
amDone(); amDone(ecSuccess);
return; return;
} }
@ -841,7 +847,7 @@ void DerivationGoal::tryToBuild()
/* Acquire locks and such. If we then see that the build has /* Acquire locks and such. If we then see that the build has
been done by somebody else, we're done. */ been done by somebody else, we're done. */
if (!prepareBuild()) { if (!prepareBuild()) {
amDone(); amDone(ecSuccess);
return; return;
} }
@ -850,7 +856,7 @@ void DerivationGoal::tryToBuild()
} catch (BuildError & e) { } catch (BuildError & e) {
printMsg(lvlError, e.msg()); printMsg(lvlError, e.msg());
amDone(false); amDone(ecFailed);
return; return;
} }
@ -892,61 +898,62 @@ void DerivationGoal::buildDone()
if (buildUser.enabled()) if (buildUser.enabled())
buildUser.kill(); buildUser.kill();
/* Some cleanup per path. We do this here and not in try {
computeClosure() for convenience when the build has failed. */
for (DerivationOutputs::iterator i = drv.outputs.begin();
i != drv.outputs.end(); ++i)
{
Path path = i->second.path;
if (!pathExists(path)) continue;
struct stat st; /* Some cleanup per path. We do this here and not in
if (lstat(path.c_str(), &st)) computeClosure() for convenience when the build has
throw SysError(format("getting attributes of path `%1%'") % path); failed. */
for (DerivationOutputs::iterator i = drv.outputs.begin();
i != drv.outputs.end(); ++i)
{
Path path = i->second.path;
if (!pathExists(path)) continue;
struct stat st;
if (lstat(path.c_str(), &st) == -1)
throw SysError(format("getting attributes of path `%1%'") % path);
#ifndef __CYGWIN__ #ifndef __CYGWIN__
/* Check that the output is not group or world writable, as /* Check that the output is not group or world writable,
that means that someone else can have interfered with the as that means that someone else can have interfered
build. Also, the output should be owned by the build with the build. Also, the output should be owned by
user. */ the build user. */
if ((st.st_mode & (S_IWGRP | S_IWOTH)) || if ((st.st_mode & (S_IWGRP | S_IWOTH)) ||
(buildUser.enabled() && st.st_uid != buildUser.getUID())) (buildUser.enabled() && st.st_uid != buildUser.getUID()))
throw Error(format("suspicious ownership or permission on `%1%'; rejecting this build output") % path); throw BuildError(format("suspicious ownership or permission on `%1%'; rejecting this build output") % path);
#endif #endif
/* Gain ownership of the build result using the setuid wrapper /* Gain ownership of the build result using the setuid
if we're not root. If we *are* root, then wrapper if we're not root. If we *are* root, then
canonicalisePathMetaData() will take care of this later canonicalisePathMetaData() will take care of this later
on. */ on. */
if (buildUser.enabled() && !amPrivileged()) if (buildUser.enabled() && !amPrivileged())
getOwnership(path); getOwnership(path);
} }
/* Check the exit status. */ /* Check the exit status. */
if (!statusOk(status)) { if (!statusOk(status)) {
deleteTmpDir(false); deleteTmpDir(false);
printMsg(lvlError, format("builder for `%1%' %2%") throw BuildError(format("builder for `%1%' %2%")
% drvPath % statusToString(status)); % drvPath % statusToString(status));
amDone(false); }
return;
}
deleteTmpDir(true); deleteTmpDir(true);
/* Compute the FS closure of the outputs and register them as /* Compute the FS closure of the outputs and register them as
being valid. */ being valid. */
try {
computeClosure(); computeClosure();
} catch (BuildError & e) { } catch (BuildError & e) {
printMsg(lvlError, e.msg()); printMsg(lvlError, e.msg());
amDone(false); amDone(ecFailed);
return; return;
} }
/* Release the build user, if applicable. */ /* Release the build user, if applicable. */
buildUser.release(); buildUser.release();
amDone(); amDone(ecSuccess);
} }
@ -1184,6 +1191,8 @@ void DerivationGoal::terminateBuildHook()
debug("terminating build hook"); debug("terminating build hook");
pid_t savedPid = pid; pid_t savedPid = pid;
pid.wait(true); pid.wait(true);
/* `false' means don't wake up waiting goals, since we want to
keep this build slot ourselves (at least if the hook reply XXX. */
worker.childTerminated(savedPid, false); worker.childTerminated(savedPid, false);
fromHook.readSide.close(); fromHook.readSide.close();
toHook.writeSide.close(); toHook.writeSide.close();
@ -1218,7 +1227,7 @@ bool DerivationGoal::prepareBuild()
if (validPaths.size() > 0) { if (validPaths.size() > 0) {
/* !!! fix this; try to delete valid paths */ /* !!! fix this; try to delete valid paths */
throw Error( throw BuildError(
format("derivation `%1%' is blocked by its output paths") format("derivation `%1%' is blocked by its output paths")
% drvPath); % drvPath);
} }
@ -1250,7 +1259,7 @@ bool DerivationGoal::prepareBuild()
if (inDrv.outputs.find(*j) != inDrv.outputs.end()) if (inDrv.outputs.find(*j) != inDrv.outputs.end())
computeFSClosure(inDrv.outputs[*j].path, inputPaths); computeFSClosure(inDrv.outputs[*j].path, inputPaths);
else else
throw Error( throw BuildError(
format("derivation `%1%' requires non-existent output `%2%' from input derivation `%3%'") format("derivation `%1%' requires non-existent output `%2%' from input derivation `%3%'")
% drvPath % *j % i->first); % drvPath % *j % i->first);
} }
@ -1286,7 +1295,7 @@ void DerivationGoal::startBuilder()
{ {
Path path = i->second.path; Path path = i->second.path;
if (store->isValidPath(path)) if (store->isValidPath(path))
throw Error(format("obstructed build: path `%1%' exists") % path); throw BuildError(format("obstructed build: path `%1%' exists") % path);
if (pathExists(path)) { if (pathExists(path)) {
debug(format("removing unregistered path `%1%'") % path); debug(format("removing unregistered path `%1%'") % path);
deletePathWrapped(path); deletePathWrapped(path);
@ -1368,12 +1377,12 @@ void DerivationGoal::startBuilder()
string s = drv.env["exportReferencesGraph"]; string s = drv.env["exportReferencesGraph"];
Strings ss = tokenizeString(s); Strings ss = tokenizeString(s);
if (ss.size() % 2 != 0) if (ss.size() % 2 != 0)
throw Error(format("odd number of tokens in `exportReferencesGraph': `%1%'") % s); throw BuildError(format("odd number of tokens in `exportReferencesGraph': `%1%'") % s);
for (Strings::iterator i = ss.begin(); i != ss.end(); ) { for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
string fileName = *i++; string fileName = *i++;
Path storePath = *i++; Path storePath = *i++;
if (!store->isValidPath(storePath)) if (!store->isValidPath(storePath))
throw Error(format("`exportReferencesGraph' refers to an invalid path `%1%'") throw BuildError(format("`exportReferencesGraph' refers to an invalid path `%1%'")
% storePath); % storePath);
checkStoreName(fileName); /* !!! abuse of this function */ checkStoreName(fileName); /* !!! abuse of this function */
PathSet refs; PathSet refs;
@ -1534,7 +1543,7 @@ PathSet parseReferenceSpecifiers(const Derivation & drv, string attr)
result.insert(*i); result.insert(*i);
else if (drv.outputs.find(*i) != drv.outputs.end()) else if (drv.outputs.find(*i) != drv.outputs.end())
result.insert(drv.outputs.find(*i)->second.path); result.insert(drv.outputs.find(*i)->second.path);
else throw Error( else throw BuildError(
format("derivation contains an illegal reference specifier `%1%'") format("derivation contains an illegal reference specifier `%1%'")
% *i); % *i);
} }
@ -1561,7 +1570,7 @@ void DerivationGoal::computeClosure()
} }
struct stat st; struct stat st;
if (lstat(path.c_str(), &st)) if (lstat(path.c_str(), &st) == -1)
throw SysError(format("getting attributes of path `%1%'") % path); throw SysError(format("getting attributes of path `%1%'") % path);
startNest(nest, lvlTalkative, startNest(nest, lvlTalkative,
@ -1584,7 +1593,7 @@ void DerivationGoal::computeClosure()
/* The output path should be a regular file without /* The output path should be a regular file without
execute permission. */ execute permission. */
if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0) if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0)
throw Error( throw BuildError(
format("output path `%1% should be a non-executable regular file") format("output path `%1% should be a non-executable regular file")
% path); % path);
} }
@ -1592,11 +1601,11 @@ void DerivationGoal::computeClosure()
/* Check the hash. */ /* Check the hash. */
HashType ht = parseHashType(algo); HashType ht = parseHashType(algo);
if (ht == htUnknown) if (ht == htUnknown)
throw Error(format("unknown hash algorithm `%1%'") % algo); throw BuildError(format("unknown hash algorithm `%1%'") % algo);
Hash h = parseHash(ht, i->second.hash); Hash h = parseHash(ht, i->second.hash);
Hash h2 = recursive ? hashPath(ht, path) : hashFile(ht, path); Hash h2 = recursive ? hashPath(ht, path) : hashFile(ht, path);
if (h != h2) if (h != h2)
throw Error( throw BuildError(
format("output path `%1%' should have %2% hash `%3%', instead has `%4%'") format("output path `%1%' should have %2% hash `%3%', instead has `%4%'")
% path % algo % printHash(h) % printHash(h2)); % path % algo % printHash(h) % printHash(h2));
} }
@ -1630,7 +1639,7 @@ void DerivationGoal::computeClosure()
PathSet allowed = parseReferenceSpecifiers(drv, drv.env["allowedReferences"]); PathSet allowed = parseReferenceSpecifiers(drv, drv.env["allowedReferences"]);
for (PathSet::iterator i = references.begin(); i != references.end(); ++i) for (PathSet::iterator i = references.begin(); i != references.end(); ++i)
if (allowed.find(*i) == allowed.end()) if (allowed.find(*i) == allowed.end())
throw Error(format("output is not allowed to refer to path `%1%'") % *i); throw BuildError(format("output is not allowed to refer to path `%1%'") % *i);
} }
/* Hash the contents of the path. The hash is stored in the /* Hash the contents of the path. The hash is stored in the
@ -1849,7 +1858,7 @@ void SubstitutionGoal::init()
/* If the path already exists we're done. */ /* If the path already exists we're done. */
if (store->isValidPath(storePath)) { if (store->isValidPath(storePath)) {
amDone(); amDone(ecSuccess);
return; return;
} }
@ -1880,8 +1889,12 @@ void SubstitutionGoal::referencesValid()
{ {
trace("all referenced realised"); trace("all referenced realised");
if (nrFailed > 0) if (nrFailed > 0) {
throw Error(format("some references of path `%1%' could not be realised") % storePath); printMsg(lvlError,
format("some references of path `%1%' could not be realised") % storePath);
amDone(ecFailed);
return;
}
for (PathSet::iterator i = references.begin(); for (PathSet::iterator i = references.begin();
i != references.end(); ++i) i != references.end(); ++i)
@ -1902,7 +1915,7 @@ void SubstitutionGoal::tryNext()
printMsg(lvlError, printMsg(lvlError,
format("path `%1%' is required, but it has no (remaining) substitutes") format("path `%1%' is required, but it has no (remaining) substitutes")
% storePath); % storePath);
amDone(false); amDone(ecFailed);
return; return;
} }
sub = subs.front(); sub = subs.front();
@ -1933,7 +1946,7 @@ void SubstitutionGoal::tryToRun()
if (store->isValidPath(storePath)) { if (store->isValidPath(storePath)) {
debug(format("store path `%1%' has become valid") % storePath); debug(format("store path `%1%' has become valid") % storePath);
outputLock->setDeletion(true); outputLock->setDeletion(true);
amDone(); amDone(ecSuccess);
return; return;
} }
@ -2046,7 +2059,7 @@ void SubstitutionGoal::finished()
printMsg(lvlChatty, printMsg(lvlChatty,
format("substitution of path `%1%' succeeded") % storePath); format("substitution of path `%1%' succeeded") % storePath);
amDone(); amDone(ecSuccess);
} }
@ -2197,24 +2210,30 @@ void Worker::childTerminated(pid_t pid, bool wakeSleepers)
} }
wantingToBuild.clear(); wantingToBuild.clear();
} }
} }
void Worker::waitForBuildSlot(GoalPtr goal, bool reallyWait) void Worker::waitForBuildSlot(GoalPtr goal)
{ {
debug("wait for build slot"); debug("wait for build slot");
if (reallyWait && children.size() == 0) if (canBuildMore())
throw Error("waiting for a build slot, yet there are no children - "
"maybe the build hook gave an inappropriate `postpone' reply?");
if (!reallyWait && canBuildMore())
wakeUp(goal); /* we can do it right away */ wakeUp(goal); /* we can do it right away */
else else
wantingToBuild.insert(goal); wantingToBuild.insert(goal);
} }
void Worker::waitForChildTermination(GoalPtr goal)
{
debug("wait for child termination");
if (children.size() == 0)
throw Error("waiting for a build slot, yet there are no running children - "
"maybe the build hook gave an inappropriate `postpone' reply?");
wantingToBuild.insert(goal);
}
void Worker::run(const Goals & _topGoals) void Worker::run(const Goals & _topGoals)
{ {
for (Goals::iterator i = _topGoals.begin(); for (Goals::iterator i = _topGoals.begin();
@ -2342,8 +2361,12 @@ void Worker::waitForInput()
if (maxSilentTime != 0 && if (maxSilentTime != 0 &&
now - i->second.lastOutput >= (time_t) maxSilentTime) now - i->second.lastOutput >= (time_t) maxSilentTime)
throw Error(format("%1% timed out after %2% seconds of silence") {
printMsg(lvlError,
format("%1% timed out after %2% seconds of silence")
% goal->getName() % maxSilentTime); % goal->getName() % maxSilentTime);
goal->cancel();
}
} }
} }