From 5761827d5bd90efda9ba2183ac30ad73d51de6bf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 25 Apr 2016 16:47:46 +0200 Subject: [PATCH] Show the log tail when a build fails If --no-build-output is given (which will become the default for the "nix" command at least), show the last 10 lines of the build output if the build fails. --- src/libmain/shared.cc | 2 +- src/libstore/build.cc | 56 +++++++++++++++++++++++++++++------- src/libstore/globals.cc | 1 - src/libstore/globals.hh | 8 ++++-- src/libstore/remote-store.cc | 2 +- src/nix-daemon/nix-daemon.cc | 2 +- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 0d9f9af73..0b6311516 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -171,7 +171,7 @@ struct LegacyArgs : public MixCommonArgs : MixCommonArgs(programName), parseArg(parseArg) { mkFlag('Q', "no-build-output", "do not show build output", - &settings.buildVerbosity, lvlVomit); + &settings.verboseBuild, false); mkFlag('K', "keep-failed", "keep temporary directories of failed builds", &settings.keepFailed); diff --git a/src/libstore/build.cc b/src/libstore/build.cc index f680b3737..3830d7a67 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -747,6 +747,11 @@ private: /* Number of bytes received from the builder's stdout/stderr. */ unsigned long logSize; + /* The most recent log lines. */ + std::list logTail; + + std::string currentLogLine; + /* Pipe for the builder's standard output/error. */ Pipe builderOut; @@ -872,6 +877,7 @@ private: /* Callback used by the worker to write to the log. */ void handleChildOutput(int fd, const string & data) override; void handleEOF(int fd) override; + void flushLine(); /* Return the set of (in)valid paths. */ PathSet checkPathValidity(bool returnValid, bool checkHash); @@ -1462,11 +1468,19 @@ void DerivationGoal::buildDone() if (pathExists(chrootRootDir + i)) rename((chrootRootDir + i).c_str(), i.c_str()); - if (diskFull) - printMsg(lvlError, "note: build failure may have been caused by lack of free disk space"); + std::string msg = (format("builder for ‘%1%’ %2%") + % drvPath % statusToString(status)).str(); - throw BuildError(format("builder for ‘%1%’ %2%") - % drvPath % statusToString(status)); + if (!settings.verboseBuild && !logTail.empty()) { + msg += (format("; last %d log lines:") % logTail.size()).str(); + for (auto & line : logTail) + msg += "\n " + line; + } + + if (diskFull) + msg += "\nnote: build failure may have been caused by lack of free disk space"; + + throw BuildError(msg); } /* Compute the FS closure of the outputs and register them as @@ -2920,8 +2934,20 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) done(BuildResult::LogLimitExceeded); return; } - if (verbosity >= settings.buildVerbosity) - printMsg(lvlError, filterANSIEscapes(data, true)); // FIXME + + for (size_t pos = 0; true; ) { + auto newline = data.find('\n', pos); + + if (newline == std::string::npos) { + currentLogLine.append(data, pos, std::string::npos); + break; + } + + currentLogLine.append(data, pos, newline - pos); + flushLine(); + pos = newline + 1; + } + if (bzLogFile) { int err; BZ2_bzWrite(&err, bzLogFile, (unsigned char *) data.data(), data.size()); @@ -2937,10 +2963,23 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) void DerivationGoal::handleEOF(int fd) { + flushLine(); worker.wakeUp(shared_from_this()); } +void DerivationGoal::flushLine() +{ + if (settings.verboseBuild) + printMsg(lvlInfo, filterANSIEscapes(currentLogLine, true)); + else { + logTail.push_back(currentLogLine); + if (logTail.size() > settings.logLines) logTail.pop_front(); + } + currentLogLine = ""; +} + + PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash) { PathSet result; @@ -3341,10 +3380,7 @@ void SubstitutionGoal::finished() void SubstitutionGoal::handleChildOutput(int fd, const string & data) { assert(fd == logPipe.readSide); - if (verbosity >= settings.buildVerbosity) - printMsg(lvlError, data); // FIXME - /* Don't write substitution output to a log file for now. We - probably should, though. */ + printMsg(lvlError, data); // FIXME } diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index f4c41d715..90539ea85 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -28,7 +28,6 @@ Settings::Settings() keepFailed = false; keepGoing = false; tryFallback = false; - buildVerbosity = lvlError; maxBuildJobs = 1; buildCores = 1; #ifdef _SC_NPROCESSORS_ONLN diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 8fa49e2dc..60251ef42 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -78,8 +78,12 @@ struct Settings { instead. */ bool tryFallback; - /* Verbosity level for build output. */ - Verbosity buildVerbosity; + /* Whether to show build log output in real time. */ + bool verboseBuild = true; + + /* If verboseBuild is false, the number of lines of the tail of + the log to show if a build fails. */ + size_t logLines = 10; /* Maximum number of parallel build jobs. 0 means unlimited. */ unsigned int maxBuildJobs; diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 831f4a83e..4663291b9 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -120,7 +120,7 @@ void RemoteStore::setOptions(ref conn) if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 2) conn->to << settings.useBuildHook; if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 4) - conn->to << settings.buildVerbosity + conn->to << (settings.verboseBuild ? lvlError : lvlVomit) << 0 // obsolete log type << 0 /* obsolete print build trace */; if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 6) diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc index fbac852e0..3c2e05210 100644 --- a/src/nix-daemon/nix-daemon.cc +++ b/src/nix-daemon/nix-daemon.cc @@ -443,7 +443,7 @@ static void performOp(ref store, bool trusted, unsigned int clientVe if (GET_PROTOCOL_MINOR(clientVersion) >= 2) settings.useBuildHook = readInt(from) != 0; if (GET_PROTOCOL_MINOR(clientVersion) >= 4) { - settings.buildVerbosity = (Verbosity) readInt(from); + settings.verboseBuild = lvlError == (Verbosity) readInt(from); readInt(from); // obsolete logType readInt(from); // obsolete printBuildTrace }