Compare commits

...

48 Commits

Author SHA1 Message Date
Eelco Dolstra
4074d01d26 Merge remote-tracking branch 'origin/master' into progress-bar 2024-12-11 15:12:29 +01:00
Eelco Dolstra
a41fdfc5aa Merge commit '1af94bf47' into progress-bar 2024-12-11 14:59:44 +01:00
John Ericson
540704e0aa Fix build 2023-03-11 17:29:06 -05:00
John Ericson
69a6e650bf Merge commit '73fde9eed06dfdef5d37b3d798cfc98a542a4d73' into progress-bar 2023-03-11 17:12:46 -05:00
John Ericson
28c6225110 Merge commit '280543933507839201547f831280faac614d0514' into progress-bar 2023-03-11 17:12:16 -05:00
John Ericson
bd85d3666d Merge commit '470e27ce8008ba952225b9f9f7f61a9627376f33' into progress-bar 2023-03-11 17:12:08 -05:00
John Ericson
37e74bb69b Merge commit '734019ce561951caff31365ee928603afdef450e' into progress-bar 2023-03-11 17:11:20 -05:00
John Ericson
835ffa02e1 Merge commit '8ad485ea893862029e02cb560a15fd276753b04f' into progress-bar 2023-03-11 17:10:43 -05:00
John Ericson
d3b5b49ece Merge commit '1c1a7074dae04414268d47c5b94e8d78afee8770' into progress-bar 2023-03-11 17:09:17 -05:00
John Ericson
57145cf9b4 Merge commit 'a2ace54fe45fe0ba0730433098cc85923c41461f' into progress-bar 2023-03-11 17:05:41 -05:00
John Ericson
b2ca890195 Merge commit 'b09baf690bb00125805a02e0feae9636b2114599' into progress-bar 2023-03-11 17:05:33 -05:00
John Ericson
5109b5e467 Merge commit '6636202356b94ca4128462493770e7fedf997b0e' into progress-bar 2023-03-11 17:04:22 -05:00
John Ericson
38949e6be4 Merge commit 'df552ff53e68dff8ca360adbdbea214ece1d08ee' into progress-bar 2023-03-11 17:03:54 -05:00
John Ericson
a314196904 Merge commit 'df11e75d0e5dd3783339a0e7a5683895d7bc7d61' into progress-bar 2023-03-11 17:02:27 -05:00
John Ericson
2f5a4df00c Merge commit '46d86e06ba54dc708fa8fd7d0109845fa2ac402e' into progress-bar 2023-03-11 17:02:16 -05:00
John Ericson
c70a6c81bb Merge commit '971382cab0c8ee057706e3dd4a124252d6b3547d' into progress-bar 2023-03-11 17:01:56 -05:00
John Ericson
fece09cad9 Merge commit '5fcf7f04a91c5cd0d49f833fe21991da89776a22' into progress-bar 2023-03-11 17:01:09 -05:00
John Ericson
e73dcf2cdd Merge commit '8388d2c7c662e37470240cfde798956fe8e36a6f' into progress-bar 2023-03-11 16:59:40 -05:00
John Ericson
68e32b7728 Merge commit 'f4c869977c391b31eb4f20486f7da03b026e2401' into progress-bar 2023-03-11 16:58:03 -05:00
John Ericson
f34aa7522b Merge commit '96670ed2163d3d1a296c9b053833362ec8c06985' into progress-bar 2023-03-11 16:57:47 -05:00
Eelco Dolstra
f8a1b81a79 Fix writeToStdout() 2021-11-03 22:08:27 +01:00
Eelco Dolstra
c4f0508ef5 Merge remote-tracking branch 'origin/master' into progress-bar 2021-11-03 14:01:55 +01:00
Eelco Dolstra
1af0a165d4 nix build: Add outro message 2021-01-05 12:00:23 +01:00
Eelco Dolstra
491ba8d1c4 Log fast builds/substitutions with a lower priority 2021-01-05 12:00:23 +01:00
Eelco Dolstra
101b15663b Log build/substitution finishes 2021-01-05 12:00:23 +01:00
Eelco Dolstra
846c028609 Fix prompting 2021-01-05 12:00:23 +01:00
Eelco Dolstra
07ba1eb67e Progress bar: Handle verify 2021-01-05 12:00:23 +01:00
Eelco Dolstra
2f512dd29f Move actEvaluate so it doesn't include actLockFlake 2021-01-05 12:00:23 +01:00
Eelco Dolstra
e6ca275e23 Show queryMissing() in the progress bar 2021-01-05 12:00:23 +01:00
Eelco Dolstra
562a6d2361 Spinner 2021-01-05 12:00:23 +01:00
Eelco Dolstra
966256c507 Show flake lock file updating in the progress bar 2021-01-05 12:00:23 +01:00
Eelco Dolstra
ed80589a07 Progress bar: Add a key to show what paths remain to be built/substituted 2021-01-05 12:00:23 +01:00
Eelco Dolstra
2392688a2d Move method 2021-01-05 12:00:23 +01:00
Eelco Dolstra
4979bd468a Replace LogFormat::barWithLogs with a setting
This will make it easier to add more settings to the progress bar.
2021-01-05 12:00:23 +01:00
Eelco Dolstra
99bb7aaf80 Fix resetting the terminal with '-L'
Using '-L' caused another call to setLogFormat(), which caused another
ProgressBar to be created. But the ProgressBar should be a singleton.

To do: remove LogFormat::barWithLogs. '-L' should be a setting of the
ProgressBar, not a different log format.
2021-01-05 12:00:23 +01:00
Eelco Dolstra
29ada5105b Disable the progress bar if stdout is redirected 2021-01-05 12:00:23 +01:00
Eelco Dolstra
4b711bf3ce Fix crash, tweaks 2021-01-05 12:00:23 +01:00
Eelco Dolstra
f90b12098d Show downloads 2021-01-05 12:00:23 +01:00
Eelco Dolstra
208425bd12 Show duration of running builds 2021-01-05 12:00:23 +01:00
Eelco Dolstra
256d6427fa Put builds/substitutes under the right progress bar 2021-01-05 12:00:23 +01:00
Eelco Dolstra
83f47e7fb1 Show failure / evaluation 2021-01-05 12:00:23 +01:00
Eelco Dolstra
dc0bac99dd Add activity for evaluation 2021-01-05 12:00:23 +01:00
Eelco Dolstra
8f92b7f0a1 Style change 2021-01-05 12:00:23 +01:00
Eelco Dolstra
55d3bdd8f0 Cleanup 2021-01-05 12:00:23 +01:00
Eelco Dolstra
e314119d14 Doh 2021-01-05 12:00:23 +01:00
Eelco Dolstra
82bbb3a66e Add separate progress bars for substituting and building 2021-01-05 12:00:23 +01:00
Eelco Dolstra
304715d5f3 Support multi-line status 2021-01-05 12:00:23 +01:00
Eelco Dolstra
2a2df85fbd Interactive progress bar
During a build you can hit 'L' to enable/disable printing of build
logs, 'v' or '+' to increase verbosity, and '-' to decrease verbosity.
2021-01-05 12:00:22 +01:00
22 changed files with 667 additions and 237 deletions

View File

@@ -369,7 +369,11 @@ void MixEnvironment::setEnviron()
return;
}
void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store)
void createOutLinks(
const std::filesystem::path & outLink,
const BuiltPaths & buildables,
LocalFSStore & store,
PathSet & symlinks)
{
for (const auto & [_i, buildable] : enumerate(buildables)) {
auto i = _i;
@@ -380,6 +384,7 @@ void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & bu
if (i)
symlink += fmt("-%d", i);
store.addPermRoot(bo.path, absPath(symlink.string()));
symlinks.insert(symlink);
},
[&](const BuiltPath::Built & bfd) {
for (auto & output : bfd.outputs) {
@@ -389,6 +394,7 @@ void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & bu
if (output.first != "out")
symlink += fmt("-%s", output.first);
store.addPermRoot(output.second, absPath(symlink.string()));
symlinks.insert(symlink);
}
},
},

View File

@@ -372,6 +372,10 @@ void printClosureDiff(
* Create symlinks prefixed by `outLink` to the store paths in
* `buildables`.
*/
void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store);
void createOutLinks(
const std::filesystem::path & outLink,
const BuiltPaths & buildables,
LocalFSStore & store,
PathSet & symlinks);
}

View File

@@ -75,7 +75,7 @@ InstallableFlake::InstallableFlake(
DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
Activity act(*logger, lvlTalkative, actEvaluate, fmt("evaluating derivation '%s'", what()));
auto attr = getCursor(*state);

View File

@@ -356,6 +356,8 @@ LockedFlake lockFlake(
{
experimentalFeatureSettings.require(Xp::Flakes);
Activity act(*logger, lvlTalkative, actLockFlake);
FlakeCache flakeCache;
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);

View File

@@ -15,8 +15,6 @@ LogFormat parseLogFormat(const std::string & logFormatStr) {
return LogFormat::internalJSON;
else if (logFormatStr == "bar")
return LogFormat::bar;
else if (logFormatStr == "bar-with-logs")
return LogFormat::barWithLogs;
throw Error("option 'log-format' has an invalid value '%s'", logFormatStr);
}
@@ -30,11 +28,6 @@ Logger * makeDefaultLogger() {
return makeJSONLogger(*makeSimpleLogger(true));
case LogFormat::bar:
return makeProgressBar();
case LogFormat::barWithLogs: {
auto logger = makeProgressBar();
logger->setPrintBuildLogs(true);
return logger;
}
default:
unreachable();
}

View File

@@ -10,7 +10,6 @@ enum class LogFormat {
rawWithLogs,
internalJSON,
bar,
barWithLogs,
};
void setLogFormat(const std::string & logFormatStr);

View File

@@ -3,6 +3,8 @@
#include "sync.hh"
#include "store-api.hh"
#include "names.hh"
#include "config-global.hh"
#include "signals.hh"
#include <atomic>
#include <map>
@@ -10,9 +12,17 @@
#include <sstream>
#include <iostream>
#include <chrono>
#include <future>
#include <termios.h>
#include <poll.h>
namespace nix {
ProgressBarSettings progressBarSettings;
static GlobalConfig::Register rProgressBarSettings(&progressBarSettings);
static std::string_view getS(const std::vector<Logger::Field> & fields, size_t n)
{
assert(n < fields.size());
@@ -34,13 +44,24 @@ static std::string_view storePathToName(std::string_view path)
return i == std::string::npos ? base.substr(0, 0) : base.substr(i + 1);
}
std::string repeat(std::string_view s, size_t n)
{
std::string res;
for (size_t i = 0; i < n; ++i)
res += s;
return res;
}
auto MiB = 1024.0 * 1024.0;
class ProgressBar : public Logger
{
private:
struct ActInfo
{
std::string s, lastLine, phase;
std::string s, lastLine;
std::optional<std::string> phase;
ActivityType type = actUnknown;
uint64_t done = 0;
uint64_t expected = 0;
@@ -48,9 +69,11 @@ private:
uint64_t failed = 0;
std::map<ActivityType, uint64_t> expectedByType;
bool visible = true;
bool ignored = false;
ActivityId parent;
std::optional<std::string> name;
std::chrono::time_point<std::chrono::steady_clock> startTime;
PathSet buildsRemaining, substitutionsRemaining;
};
struct ActivitiesByType
@@ -61,6 +84,56 @@ private:
uint64_t failed = 0;
};
struct ActivityStats
{
uint64_t done = 0;
uint64_t expected = 0;
uint64_t running = 0;
uint64_t failed = 0;
uint64_t left = 0;
uint64_t active = 0;
};
ActivityStats getActivityStats(ActivitiesByType & act)
{
ActivityStats stats {
.done = act.done,
.expected = act.done,
.running = 0,
.failed = act.failed
};
for (auto & j : act.its) {
if (j.second->ignored) continue;
stats.done += j.second->done;
stats.expected += j.second->expected;
stats.running += j.second->running;
stats.failed += j.second->failed;
stats.left += j.second->expected > j.second->done ? j.second->expected - j.second->done : 0;
stats.active++;
}
stats.expected = std::max(stats.expected, act.expected);
return stats;
}
enum StatusLineGroup {
idHelp,
idLockFlake,
idDownload,
idEvaluate,
idQueryMissing,
idCopyPaths,
idBuilds,
idVerifyPaths,
idStatus,
idQuit,
idPrompt,
};
typedef std::pair<StatusLineGroup, uint16_t> LineId;
struct State
{
std::list<ActInfo> activities;
@@ -75,36 +148,188 @@ private:
bool active = true;
bool paused = false;
bool haveUpdate = true;
std::map<LineId, std::string> statusLines;
/* How many lines need to be erased when redrawing. */
size_t prevStatusLines = 0;
bool helpShown = false;
std::optional<std::promise<std::optional<char>>> prompt;
};
/** Helps avoid unnecessary redraws, see `redraw()` */
const bool isTTY;
/** Helps avoid unnecessary redraws, see `redraw()`. */
Sync<std::string> lastOutput_;
Sync<State> state_;
std::thread updateThread;
std::thread inputThread;
std::condition_variable quitCV, updateCV;
bool printBuildLogs = false;
bool isTTY;
std::optional<struct termios> savedTermAttrs;
Pipe inputPipe;
const std::chrono::time_point<std::chrono::steady_clock> startTime = std::chrono::steady_clock::now();
public:
ProgressBar(bool isTTY)
: isTTY(isTTY)
, state_({ .active = isTTY })
{
state_.lock()->active = isTTY;
updateThread = std::thread([&]() {
auto state(state_.lock());
auto nextWakeup = std::chrono::milliseconds::max();
while (state->active) {
#if 0
if (!state->haveUpdate)
state.wait_for(updateCV, nextWakeup);
#endif
updateStatusLine(*state);
nextWakeup = draw(*state);
state.wait_for(quitCV, std::chrono::milliseconds(50));
}
});
if (isTTY) {
struct termios term;
if (tcgetattr(STDIN_FILENO, &term))
throw SysError("getting terminal attributes");
savedTermAttrs = term;
cfmakeraw(&term);
if (tcsetattr(STDIN_FILENO, TCSANOW, &term))
throw SysError("putting terminal into raw mode");
inputPipe.create();
inputThread = std::thread([this]() {
// FIXME: exceptions
struct pollfd fds[2];
fds[0] = { .fd = STDIN_FILENO, .events = POLLIN, .revents = 0 };
fds[1] = { .fd = inputPipe.readSide.get(), .events = POLLIN, .revents = 0 };
while (true) {
if (poll(fds, 2, -1) != 1) {
if (errno == EINTR) continue;
assert(false);
}
if (fds[1].revents & POLLIN) break;
assert(fds[0].revents & POLLIN);
char c;
auto n = read(STDIN_FILENO, &c, 1);
if (n == 0) break;
if (n == -1) {
if (errno == EINTR) continue;
break;
}
c = std::tolower(c);
if (c == 3 || c == 4 || c == 'q') {
auto state(state_.lock());
state->statusLines.insert_or_assign({idQuit, 0}, ANSI_RED "Exiting...");
draw(*state);
unix::triggerInterrupt();
}
{
auto state(state_.lock());
if (state->prompt) {
state->prompt->set_value(c != '\n' ? c : std::optional<char>());
state->prompt.reset();
continue;
}
}
if (c == 'l') {
auto state(state_.lock());
progressBarSettings.printBuildLogs = !progressBarSettings.printBuildLogs;
updateStatusLine(*state);
draw(*state,
progressBarSettings.printBuildLogs
? ANSI_BOLD "Enabling build logs."
: ANSI_BOLD "Disabling build logs.");
}
if (c == '+' || c == '=' || c == 'v') {
auto state(state_.lock());
verbosity = (Verbosity) (verbosity + 1);;
log(*state, lvlError, ANSI_BOLD "Increasing verbosity...");
}
if (c == '-') {
auto state(state_.lock());
verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError;
log(*state, lvlError, ANSI_BOLD "Decreasing verbosity...");
}
if (c == 'h' || c == '?') {
auto state(state_.lock());
if (state->helpShown) {
state->helpShown = false;
resetHelp(*state);
} else {
state->helpShown = true;
size_t n = 0;
state->statusLines.insert_or_assign({idHelp, n++}, "");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD "The following keys are available:");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD " 'v' to increase verbosity.");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD " '-' to decrease verbosity.");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD " 'l' to show build log output.");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD " 'r' to show what paths remain to be built/substituted.");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD " 'h' to hide this help message.");
state->statusLines.insert_or_assign({idHelp, n++}, ANSI_BOLD " 'q' to quit.");
state->statusLines.insert_or_assign({idHelp, n++}, "");
}
draw(*state);
}
if (c == 'r') {
auto state(state_.lock());
PathSet buildsRemaining, substitutionsRemaining;
for (auto & act : state->activities) {
for (auto & path : act.buildsRemaining) buildsRemaining.insert(path);
for (auto & path : act.substitutionsRemaining) substitutionsRemaining.insert(path);
}
std::string msg;
// FIXME: sort by name?
if (!buildsRemaining.empty()) {
msg += fmt("\n" ANSI_BOLD "%d derivations remaining to be built:\n" ANSI_NORMAL, buildsRemaining.size());
for (auto & path : buildsRemaining)
msg += fmt(" • %s\n", path);
}
if (!substitutionsRemaining.empty()) {
msg += fmt("\n" ANSI_BOLD "%d paths remaining to be substituted:\n" ANSI_NORMAL, substitutionsRemaining.size());
for (auto & path : substitutionsRemaining)
msg += fmt(" • %s\n", path);
}
if (buildsRemaining.empty() && substitutionsRemaining.empty())
msg = "\n" ANSI_BOLD "Nothing left to be built or substituted.";
draw(*state, chomp(msg));
}
}
});
resetHelp(*state_.lock());
}
}
~ProgressBar()
@@ -115,14 +340,27 @@ public:
/* Called by destructor, can't be overridden */
void stop() override final
{
if (inputThread.joinable()) {
assert(inputPipe.writeSide);
writeFull(inputPipe.writeSide.get(), "x", false);
inputThread.join();
}
{
auto state(state_.lock());
if (!state->active) return;
state->statusLines.clear();
draw(*state);
state->active = false;
writeToStderr("\r\e[K");
updateCV.notify_one();
quitCV.notify_one();
if (savedTermAttrs) {
tcsetattr(STDIN_FILENO, TCSANOW, &*savedTermAttrs);
savedTermAttrs.reset();
}
}
updateThread.join();
}
@@ -144,7 +382,7 @@ public:
bool isVerbose() override
{
return printBuildLogs;
return progressBarSettings.printBuildLogs;
}
void log(Verbosity lvl, std::string_view s) override
@@ -167,13 +405,27 @@ public:
void log(State & state, Verbosity lvl, std::string_view s)
{
if (state.active) {
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n");
draw(state);
draw(state, filterANSIEscapes(s, !isTTY));
} else {
writeToStderr(filterANSIEscapes(s, !isTTY) + "\n");
}
}
void removeStatusLines(State & state, StatusLineGroup id)
{
for (auto i = state.statusLines.lower_bound({id, 0});
i != state.statusLines.end() && i->first.first == id; )
i = state.statusLines.erase(i);
}
void resetHelp(State & state)
{
removeStatusLines(state, idHelp);
state.statusLines.insert_or_assign({idHelp, 0}, "");
state.statusLines.insert_or_assign({idHelp, 1}, ANSI_BOLD "Type 'h' for help.");
state.statusLines.insert_or_assign({idHelp, 2}, "");
}
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
const std::string & s, const Fields & fields, ActivityId parent) override
{
@@ -196,7 +448,7 @@ public:
std::string name(storePathToName(getS(fields, 0)));
if (hasSuffix(name, ".drv"))
name = name.substr(0, name.size() - 4);
i->s = fmt("building " ANSI_BOLD "%s" ANSI_NORMAL, name);
i->s = fmt(ANSI_BOLD "%s" ANSI_NORMAL, name);
auto machineName = getS(fields, 1);
if (machineName != "")
i->s += fmt(" on " ANSI_BOLD "%s" ANSI_NORMAL, machineName);
@@ -214,8 +466,8 @@ public:
auto sub = getS(fields, 1);
i->s = fmt(
hasPrefix(sub, "local")
? "copying " ANSI_BOLD "%s" ANSI_NORMAL " from %s"
: "fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s",
? ANSI_BOLD "%s" ANSI_NORMAL " from %s"
: ANSI_BOLD "%s" ANSI_NORMAL " from %s",
name, sub);
}
@@ -232,11 +484,28 @@ public:
i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
}
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
if (type == actFileTransfer) {
i->s = getS(fields, 0);
if (hasAncestor(*state, actCopyPath, parent)
|| hasAncestor(*state, actQueryPathInfo, parent))
i->ignored = true;
}
if (type == actVerifyPath)
i->s = getS(fields, 0);
if (type == actFileTransfer
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)) // FIXME?
|| type == actBuild
|| type == actSubstitute
|| type == actLockFlake
|| type == actQueryMissing
|| type == actVerifyPath)
i->visible = false;
if (type == actBuild)
i->startTime = std::chrono::steady_clock::now();
update(*state);
}
@@ -261,11 +530,14 @@ public:
if (i != state->its.end()) {
auto & actByType = state->activitiesByType[i->second->type];
actByType.done += i->second->done;
actByType.failed += i->second->failed;
for (auto & j : i->second->expectedByType)
state->activitiesByType[j.first].expected -= j.second;
if (!i->second->ignored) {
actByType.done += i->second->done;
actByType.failed += i->second->failed;
for (auto & j : i->second->expectedByType)
state->activitiesByType[j.first].expected -= j.second;
}
actByType.its.erase(act);
state->activities.erase(i->second);
@@ -279,6 +551,10 @@ public:
{
auto state(state_.lock());
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
if (type == resFileLinked) {
state->filesLinked++;
state->bytesLinked += getI(fields, 0);
@@ -288,22 +564,15 @@ public:
else if (type == resBuildLogLine || type == resPostBuildLogLine) {
auto lastLine = chomp(getS(fields, 0));
if (!lastLine.empty()) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo info = *i->second;
if (printBuildLogs) {
i->second->lastLine = lastLine;
if (progressBarSettings.printBuildLogs) {
auto suffix = "> ";
if (type == resPostBuildLogLine) {
suffix = " (post)> ";
}
log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
} else {
state->activities.erase(i->second);
info.lastLine = lastLine;
state->activities.emplace_back(info);
i->second = std::prev(state->activities.end());
log(*state, lvlInfo, ANSI_FAINT + i->second->name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
} else
update(*state);
}
}
}
@@ -318,33 +587,29 @@ public:
}
else if (type == resSetPhase) {
auto i = state->its.find(act);
assert(i != state->its.end());
i->second->phase = getS(fields, 0);
update(*state);
}
else if (type == resProgress) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
actInfo.done = getI(fields, 0);
actInfo.expected = getI(fields, 1);
actInfo.running = getI(fields, 2);
actInfo.failed = getI(fields, 3);
update(*state);
if (!actInfo.ignored) {
actInfo.done = getI(fields, 0);
actInfo.expected = getI(fields, 1);
actInfo.running = getI(fields, 2);
actInfo.failed = getI(fields, 3);
update(*state);
}
}
else if (type == resSetExpected) {
auto i = state->its.find(act);
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
auto type = (ActivityType) getI(fields, 0);
auto & j = actInfo.expectedByType[type];
state->activitiesByType[type].expected -= j;
j = getI(fields, 1);
state->activitiesByType[type].expected += j;
update(*state);
if (!actInfo.ignored) {
auto type = (ActivityType) getI(fields, 0);
auto & j = actInfo.expectedByType[type];
state->activitiesByType[type].expected -= j;
j = getI(fields, 1);
state->activitiesByType[type].expected += j;
update(*state);
}
}
else if (type == resFetchStatus) {
@@ -354,6 +619,18 @@ public:
actInfo.lastLine = getS(fields, 0);
update(*state);
}
else if (type == resExpectBuild)
actInfo.buildsRemaining.insert(std::string { getS(fields, 0) });
else if (type == resUnexpectBuild)
actInfo.buildsRemaining.erase(std::string { getS(fields, 0) });
else if (type == resExpectSubstitution)
actInfo.substitutionsRemaining.insert(std::string { getS(fields, 0) });
else if (type == resUnexpectSubstitution)
actInfo.substitutionsRemaining.erase(std::string { getS(fields, 0) });
}
void update(State & state)
@@ -362,42 +639,13 @@ public:
updateCV.notify_one();
}
/**
* Redraw, if the output has changed.
*
* Excessive redrawing is noticable on slow terminals, and it interferes
* with text selection in some terminals, including libvte-based terminal
* emulators.
*/
void redraw(std::string newOutput)
void updateStatusLine(State & state)
{
auto lastOutput(lastOutput_.lock());
if (newOutput != *lastOutput) {
writeToStderr(newOutput);
*lastOutput = std::move(newOutput);
}
}
std::chrono::milliseconds draw(State & state)
{
auto nextWakeup = std::chrono::milliseconds::max();
state.haveUpdate = false;
if (state.paused || !state.active) return nextWakeup;
std::string line;
std::string status = getStatus(state);
if (!status.empty()) {
line += '[';
line += status;
line += "]";
}
auto now = std::chrono::steady_clock::now();
if (!state.activities.empty()) {
if (!status.empty()) line += " ";
auto i = state.activities.rbegin();
while (i != state.activities.rend()) {
@@ -408,150 +656,266 @@ public:
auto delay = std::chrono::milliseconds(10);
if (i->startTime + delay < now)
break;
#if 0
else
nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime)));
#endif
}
++i;
}
if (i != state.activities.rend()) {
if (i != state.activities.rend())
line += i->s;
if (!i->phase.empty()) {
line += " (";
line += i->phase;
line += ")";
}
if (!i->lastLine.empty()) {
if (!i->s.empty()) line += ": ";
line += i->lastLine;
}
}
}
removeStatusLines(state, idStatus);
if (line != "")
state.statusLines.insert_or_assign({idStatus, 0}, line);
std::vector<std::string> spinner =
//{"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"};
//{"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"};
{"", "", "", "", "", "", "", ""};
auto busyMark = ANSI_BOLD + spinner[(std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count() / 200) % spinner.size()];
auto doneMark = ANSI_GREEN "";
auto failMark = ANSI_RED "";
if (state.activitiesByType.count(actEvaluate)) {
state.statusLines.insert_or_assign({idEvaluate, 0},
fmt("%s Evaluate",
state.activitiesByType[actEvaluate].its.empty()
? doneMark : busyMark));
state.statusLines.insert_or_assign({idEvaluate, 1}, "");
}
if (state.activitiesByType.count(actLockFlake)) {
state.statusLines.insert_or_assign({idLockFlake, 0},
fmt("%s Lock flake inputs",
state.activitiesByType[actLockFlake].its.empty()
? doneMark : busyMark));
state.statusLines.insert_or_assign({idLockFlake, 1}, "");
}
if (state.activitiesByType.count(actQueryMissing)) {
state.statusLines.insert_or_assign({idQueryMissing, 0},
fmt("%s Query missing paths",
state.activitiesByType[actQueryMissing].its.empty()
? doneMark : busyMark));
state.statusLines.insert_or_assign({idQueryMissing, 1}, "");
}
auto renderBar = [](uint64_t done, uint64_t failed, uint64_t running, uint64_t expected)
{
expected = std::max(expected, (uint64_t) 1);
auto pct1 = std::min((double) failed / expected, 1.0);
auto pct2 = std::min((double) (failed + done) / expected, 1.0);
auto pct3 = std::min((double) (failed + done + running) / expected, 1.0);
auto barLength = 70;
size_t chars1 = barLength * pct1;
size_t chars2 = barLength * pct2;
size_t chars3 = barLength * pct3;
assert(chars1 <= chars2);
assert(chars2 <= chars3);
return
ANSI_RED + repeat("", chars1) +
ANSI_GREEN + repeat("", chars2 - chars1) +
ANSI_WARNING + repeat("", chars3 - chars2) +
ANSI_NORMAL + repeat("", barLength - chars3);
};
auto fileTransfer = getActivityStats(state.activitiesByType[actFileTransfer]);
if (fileTransfer.done || fileTransfer.expected) {
removeStatusLines(state, idDownload);
size_t n = 0;
state.statusLines.insert_or_assign({idDownload, n++},
fmt("%s Download %.1f / %.1f MiB",
fileTransfer.active || fileTransfer.done < fileTransfer.expected
? busyMark
: doneMark,
fileTransfer.done / MiB, fileTransfer.expected / MiB));
state.statusLines.insert_or_assign({idDownload, n++},
fmt(" %s", renderBar(fileTransfer.done, 0, fileTransfer.left, fileTransfer.expected)));
for (auto & build : state.activitiesByType[actFileTransfer].its) {
if (build.second->ignored) continue;
state.statusLines.insert_or_assign({idDownload, n++},
fmt(ANSI_BOLD " ‣ %s", build.second->s));
}
state.statusLines.insert_or_assign({idDownload, n++}, "");
}
auto copyPath = getActivityStats(state.activitiesByType[actCopyPath]);
auto copyPaths = getActivityStats(state.activitiesByType[actCopyPaths]);
if (copyPath.done || copyPath.expected) {
// FIXME: handle failures
removeStatusLines(state, idCopyPaths);
size_t n = 0;
state.statusLines.insert_or_assign({idCopyPaths, n++},
fmt("%s Fetch %d / %d store paths, %.1f / %.1f MiB",
copyPaths.running || copyPaths.done < copyPaths.expected
? busyMark
: doneMark,
copyPaths.done, copyPaths.expected,
copyPath.done / MiB, copyPath.expected / MiB));
state.statusLines.insert_or_assign({idCopyPaths, n++},
fmt(" %s", renderBar(copyPath.done, 0, copyPath.left, copyPath.expected)));
for (auto & build : state.activitiesByType[actSubstitute].its) {
state.statusLines.insert_or_assign({idCopyPaths, n++},
fmt(ANSI_BOLD " ‣ %s", build.second->s));
}
state.statusLines.insert_or_assign({idCopyPaths, n++}, "");
}
auto builds = getActivityStats(state.activitiesByType[actBuilds]);
if (builds.done || builds.expected) {
removeStatusLines(state, idBuilds);
size_t n = 0;
state.statusLines.insert_or_assign(
{idBuilds, n++},
fmt("%s Build %d / %d derivations",
builds.failed
? failMark
: builds.running || builds.done < builds.expected
? busyMark
: doneMark,
builds.done, builds.expected)
+ (builds.running ? fmt(", %d running", builds.running) : "")
+ (builds.failed ? fmt(", %d failed", builds.failed) : ""));
state.statusLines.insert_or_assign({idBuilds, n++},
fmt(" %s",
renderBar(builds.done, builds.failed, builds.running, builds.expected)));
for (auto & build : state.activitiesByType[actBuild].its) {
state.statusLines.insert_or_assign({idBuilds, n++},
fmt(ANSI_BOLD " ‣ %s (%d s)%s: %s",
build.second->s,
std::chrono::duration_cast<std::chrono::seconds>(now - build.second->startTime).count(),
build.second->phase ? fmt(" (%s)", *build.second->phase) : "",
build.second->lastLine));
}
state.statusLines.insert_or_assign({idBuilds, n++}, "");
}
auto verify = getActivityStats(state.activitiesByType[actVerifyPaths]);
if (verify.done || verify.expected) {
removeStatusLines(state, idVerifyPaths);
auto bad = state.corruptedPaths + state.untrustedPaths + verify.failed;
size_t n = 0;
state.statusLines.insert_or_assign(
{idVerifyPaths, n++},
fmt("%s Verify %d / %d paths",
verify.done < verify.expected
? busyMark
: bad
? failMark
: doneMark,
verify.done, verify.expected)
+ (state.corruptedPaths ? fmt(", %d corrupted", state.corruptedPaths) : "")
+ (state.untrustedPaths ? fmt(", %d untrusted", state.untrustedPaths) : "")
+ (verify.failed ? fmt(", %d failed", verify.failed) : "")
);
state.statusLines.insert_or_assign({idVerifyPaths, n++},
fmt(" %s", renderBar(verify.done - bad, bad, verify.running, verify.expected)));
for (auto & build : state.activitiesByType[actVerifyPath].its) {
state.statusLines.insert_or_assign({idVerifyPaths, n++},
fmt(ANSI_BOLD " ‣ %s", build.second->s));
}
state.statusLines.insert_or_assign({idVerifyPaths, n++}, "");
}
}
std::chrono::milliseconds draw(State & state, std::optional<std::string_view> msg = {})
{
auto nextWakeup = std::chrono::milliseconds::max();
state.haveUpdate = false;
if (state.paused || !state.active) return nextWakeup;
auto width = getWindowSize().second;
if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
redraw("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
std::string s;
for (size_t i = 1; i < state.prevStatusLines; ++i)
s += "\r\e[K\e[A";
s += "\r\e[K";
if (msg) {
s += replaceStrings(std::string { *msg }, "\n", "\r\n");
s += ANSI_NORMAL "\e[K\n\r";
}
for (const auto & [n, i] : enumerate(state.statusLines)) {
s += filterANSIEscapes(i.second, false, width) + ANSI_NORMAL + "\e[K";
if (n + 1 < state.statusLines.size()) s += "\r\n";
}
writeToStderr(s);
state.prevStatusLines = state.statusLines.size();
return nextWakeup;
}
std::string getStatus(State & state)
{
auto MiB = 1024.0 * 1024.0;
std::string res;
auto renderActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
auto & act = state.activitiesByType[type];
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
for (auto & j : act.its) {
done += j.second->done;
expected += j.second->expected;
running += j.second->running;
failed += j.second->failed;
}
expected = std::max(expected, act.expected);
std::string s;
if (running || done || expected || failed) {
if (running)
if (expected != 0)
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
running / unit, done / unit, expected / unit);
else
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL,
running / unit, done / unit);
else if (expected != done)
if (expected != 0)
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
done / unit, expected / unit);
else
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL, done / unit);
else
s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit);
s = fmt(itemFmt, s);
if (failed)
s += fmt(" (" ANSI_RED "%d failed" ANSI_NORMAL ")", failed / unit);
}
return s;
};
auto showActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
auto s = renderActivity(type, itemFmt, numberFmt, unit);
if (s.empty()) return;
if (!res.empty()) res += ", ";
res += s;
};
showActivity(actBuilds, "%s built");
auto s1 = renderActivity(actCopyPaths, "%s copied");
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
if (!s1.empty() || !s2.empty()) {
if (!res.empty()) res += ", ";
if (s1.empty()) res += "0 copied"; else res += s1;
if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
}
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
{
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
if (s != "") {
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
if (!res.empty()) res += ", ";
res += s;
}
}
// FIXME: don't show "done" paths in green.
showActivity(actVerifyPaths, "%s paths verified");
if (state.corruptedPaths) {
if (!res.empty()) res += ", ";
res += fmt(ANSI_RED "%d corrupted" ANSI_NORMAL, state.corruptedPaths);
}
if (state.untrustedPaths) {
if (!res.empty()) res += ", ";
res += fmt(ANSI_RED "%d untrusted" ANSI_NORMAL, state.untrustedPaths);
}
return res;
}
void writeToStdout(std::string_view s) override
{
auto state(state_.lock());
if (state->active) {
std::cerr << "\r\e[K";
if (state->active)
// Note: this assumes that stdout == stderr == a terminal
draw(*state, s);
else
Logger::writeToStdout(s);
draw(*state);
} else {
Logger::writeToStdout(s);
}
}
std::optional<char> ask(std::string_view msg) override
{
auto state(state_.lock());
if (!state->active) return {};
std::cerr << fmt("\r\e[K%s ", msg);
auto s = trim(readLine(getStandardInput(), true));
if (s.size() != 1) return {};
draw(*state);
return s[0];
}
checkInterrupt();
void setPrintBuildLogs(bool printBuildLogs) override
{
this->printBuildLogs = printBuildLogs;
std::future<std::optional<char>> fut;
{
auto state(state_.lock());
if (!inputThread.joinable()) return {};
state->statusLines.insert_or_assign({idPrompt, 0}, fmt(ANSI_BOLD "%s " ANSI_NORMAL, msg));
draw(*state);
state->prompt = std::promise<std::optional<char>>();
fut = state->prompt->get_future();
}
auto res = fut.get();
{
auto state(state_.lock());
removeStatusLines(*state, idPrompt);
draw(*state);
}
return res;
}
};
@@ -560,11 +924,6 @@ Logger * makeProgressBar()
return new ProgressBar(isTTY());
}
void startProgressBar()
{
logger = makeProgressBar();
}
void stopProgressBar()
{
auto progressBar = dynamic_cast<ProgressBar *>(logger);

View File

@@ -7,8 +7,14 @@ namespace nix {
Logger * makeProgressBar();
void startProgressBar();
void stopProgressBar();
struct ProgressBarSettings : Config
{
Setting<bool> printBuildLogs{this, false, "print-build-logs",
"Whether the progress bar should print full build logs or just the most recent line."};
};
extern ProgressBarSettings progressBarSettings;
}

View File

@@ -727,6 +727,7 @@ Goal::Co DerivationGoal::tryToBuild()
EOF from the hook. */
actLock.reset();
buildResult.startTime = time(0); // inexact
startTime = std::chrono::steady_clock::now();
started();
co_await Suspend{};
co_return buildDone();
@@ -1055,6 +1056,16 @@ Goal::Co DerivationGoal::buildDone()
co_return done(st, {}, std::move(e));
}
auto stopTime = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() / 1000.0;
// FIXME: associate with activity 'act'.
printMsg(duration > 0.2 ? lvlNotice : lvlInfo,
ANSI_BOLD ANSI_GREEN "Built" ANSI_NORMAL " '%s' in %.1f s.",
worker.store.printStorePath(drvPath),
duration);
}
Goal::Co DerivationGoal::resolvedFinished()

View File

@@ -196,6 +196,11 @@ struct DerivationGoal : public Goal
BuildMode buildMode;
/* Time the build started. 'result' also has a 'startTime' field,
but that's wall clock time, so we can't use it to compute the
build duration... */
std::chrono::time_point<std::chrono::steady_clock> startTime;
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
std::unique_ptr<Activity> act;

View File

@@ -197,6 +197,8 @@ Goal::Co PathSubstitutionGoal::tryToRun(StorePath subPath, nix::ref<Store> sub,
outPipe.createAsyncPipe(worker.ioport.get());
#endif
startTime = std::chrono::steady_clock::now();
auto promise = std::promise<void>();
thr = std::thread([this, &promise, &subPath, &sub]() {
@@ -273,6 +275,16 @@ Goal::Co PathSubstitutionGoal::tryToRun(StorePath subPath, nix::ref<Store> sub,
worker.updateProgress();
auto stopTime = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() / 1000.0;
// FIXME: associate with activity 'act'.
printMsg(duration > 0.2 ? lvlNotice : lvlInfo,
ANSI_BOLD ANSI_GREEN "Substituted" ANSI_NORMAL " '%s' in %.1f s.",
worker.store.printStorePath(storePath),
duration);
co_return done(ecSuccess, BuildResult::Substituted);
}

View File

@@ -41,6 +41,9 @@ struct PathSubstitutionGoal : public Goal
*/
std::optional<ContentAddress> ca;
/* Time substitution started. */
std::chrono::time_point<std::chrono::steady_clock> startTime;
Done done(
ExitCode result,
BuildResult::Status status,

View File

@@ -149,11 +149,14 @@ static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> &
void Worker::removeGoal(GoalPtr goal)
{
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal)) {
act.result(resUnexpectBuild, store.printStorePath(drvGoal->drvPath));
nix::removeGoal(drvGoal, derivationGoals);
else
if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
}
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal)) {
act.result(resUnexpectSubstitution, store.printStorePath(subGoal->storePath));
nix::removeGoal(subGoal, substitutionGoals);
}
else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal))
nix::removeGoal(subGoal, drvOutputSubstitutionGoals);
else
@@ -306,6 +309,12 @@ void Worker::run(const Goals & _topGoals)
uint64_t downloadSize, narSize;
store.queryMissing(topPaths, willBuild, willSubstitute, unknown, downloadSize, narSize);
for (auto & path : willBuild)
act.result(resExpectBuild, store.printStorePath(path));
for (auto & path : willSubstitute)
act.result(resExpectSubstitution, store.printStorePath(path));
debug("entered goal loop");
while (1) {
@@ -552,4 +561,11 @@ GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal)
return subGoal;
}
void Worker::updateProgress()
{
actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
}
}

View File

@@ -318,13 +318,7 @@ public:
void markContentsGood(const StorePath & path);
void updateProgress()
{
actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
}
void updateProgress();
};
}

View File

@@ -101,7 +101,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
StorePathSet & willBuild_, StorePathSet & willSubstitute_, StorePathSet & unknown_,
uint64_t & downloadSize_, uint64_t & narSize_)
{
Activity act(*logger, lvlDebug, actUnknown, "querying info about missing paths");
Activity act(*logger, lvlDebug, actQueryMissing, "querying info about missing paths");
downloadSize_ = narSize_ = 0;

View File

@@ -932,6 +932,7 @@ void LocalDerivationGoal::startBuilder()
};
buildResult.startTime = time(0);
startTime = std::chrono::steady_clock::now();
/* Fork a child to build the package. */

View File

@@ -23,6 +23,10 @@ typedef enum {
actPostBuildHook = 110,
actBuildWaiting = 111,
actFetchTree = 112,
actEvaluate = 113,
actLockFlake = 114,
actQueryMissing = 115,
actVerifyPath = 116,
} ActivityType;
typedef enum {
@@ -35,6 +39,10 @@ typedef enum {
resSetExpected = 106,
resPostBuildLogLine = 107,
resFetchStatus = 108,
resExpectBuild = 109,
resUnexpectBuild = 110,
resExpectSubstitution = 111,
resUnexpectSubstitution = 112,
} ResultType;
typedef uint64_t ActivityId;
@@ -114,9 +122,6 @@ public:
virtual std::optional<char> ask(std::string_view s)
{ return {}; }
virtual void setPrintBuildLogs(bool printBuildLogs)
{ }
};
/**

View File

@@ -115,9 +115,11 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
if (json) logger->cout("%s", builtPathsWithResultToJSON(buildables, *store).dump());
PathSet symlinks;
if (outLink != "")
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
createOutLinks(outLink, toBuiltPaths(buildables), *store2);
createOutLinks(outLink, toBuiltPaths(buildables), *store2, symlinks);
if (printOutputPaths) {
stopProgressBar();
@@ -139,6 +141,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
for (auto & b : buildables)
buildables2.push_back(b.path);
updateProfile(buildables2);
if (!json)
notice(
ANSI_GREEN "Build succeeded." ANSI_NORMAL
" The result is available through the symlink " ANSI_BOLD "%s" ANSI_NORMAL ".",
showPaths(symlinks));
}
};

View File

@@ -71,9 +71,10 @@ struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand, MixProfile
updateProfile(rootPaths);
if (outLink) {
if (auto store2 = dstStore.dynamic_pointer_cast<LocalFSStore>())
createOutLinks(*outLink, rootPaths, *store2);
else
if (auto store2 = dstStore.dynamic_pointer_cast<LocalFSStore>()) {
PathSet symlinks;
createOutLinks(*outLink, rootPaths, *store2, symlinks);
} else
throw Error("'--out-link' is not supported for this Nix store");
}
}

View File

@@ -20,6 +20,7 @@
#include "flake/flake.hh"
#include "self-exe.hh"
#include "json-utils.hh"
#include "progress-bar.hh"
#include <sys/types.h>
#include <regex>
@@ -122,7 +123,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
.shortName = 'L',
.description = "Print full build logs on standard error.",
.category = loggingCategory,
.handler = {[&]() { logger->setPrintBuildLogs(true); }},
.handler = {[&]() { progressBarSettings.printBuildLogs = true; }},
.experimentalFeature = Xp::NixCommand,
});
@@ -408,7 +409,6 @@ void mainWrapped(int argc, char * * argv)
evalSettings.pureEval = true;
setLogFormat("bar");
settings.verboseBuild = false;
// If on a terminal, progress will be displayed via progress bars etc. (thus verbosity=notice)
@@ -510,6 +510,8 @@ void mainWrapped(int argc, char * * argv)
return;
}
setLogFormat(LogFormat::bar);
if (!args.command)
throw UsageError("no subcommand specified");

View File

@@ -12,6 +12,7 @@
#include "posix-source-accessor.hh"
#include "misc-store-flags.hh"
#include "terminal.hh"
#include "loggers.hh"
#include <nlohmann/json.hpp>
@@ -192,7 +193,7 @@ static int main_nix_prefetch_url(int argc, char * * argv)
Finally f([]() { stopProgressBar(); });
if (isTTY())
startProgressBar();
setLogFormat(LogFormat::bar);
auto store = openStore();
auto state = std::make_unique<EvalState>(myArgs.lookupPath, store, fetchSettings, evalSettings);

View File

@@ -95,7 +95,9 @@ struct CmdVerify : StorePathsCommand
// Note: info->path can be different from storePath
// for binary cache stores when using --all (since we
// can't enumerate names efficiently).
Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", store->printStorePath(info->path)));
Activity act2(*logger, lvlInfo, actVerifyPath,
fmt("checking '%s'", store->printStorePath(info->path)),
{store->printStorePath(info->path)});
if (!noContents) {