Compare commits
48 Commits
nix-develo
...
progress-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4074d01d26 | ||
|
|
a41fdfc5aa | ||
|
|
540704e0aa | ||
|
|
69a6e650bf | ||
|
|
28c6225110 | ||
|
|
bd85d3666d | ||
|
|
37e74bb69b | ||
|
|
835ffa02e1 | ||
|
|
d3b5b49ece | ||
|
|
57145cf9b4 | ||
|
|
b2ca890195 | ||
|
|
5109b5e467 | ||
|
|
38949e6be4 | ||
|
|
a314196904 | ||
|
|
2f5a4df00c | ||
|
|
c70a6c81bb | ||
|
|
fece09cad9 | ||
|
|
e73dcf2cdd | ||
|
|
68e32b7728 | ||
|
|
f34aa7522b | ||
|
|
f8a1b81a79 | ||
|
|
c4f0508ef5 | ||
|
|
1af0a165d4 | ||
|
|
491ba8d1c4 | ||
|
|
101b15663b | ||
|
|
846c028609 | ||
|
|
07ba1eb67e | ||
|
|
2f512dd29f | ||
|
|
e6ca275e23 | ||
|
|
562a6d2361 | ||
|
|
966256c507 | ||
|
|
ed80589a07 | ||
|
|
2392688a2d | ||
|
|
4979bd468a | ||
|
|
99bb7aaf80 | ||
|
|
29ada5105b | ||
|
|
4b711bf3ce | ||
|
|
f90b12098d | ||
|
|
208425bd12 | ||
|
|
256d6427fa | ||
|
|
83f47e7fb1 | ||
|
|
dc0bac99dd | ||
|
|
8f92b7f0a1 | ||
|
|
55d3bdd8f0 | ||
|
|
e314119d14 | ||
|
|
82bbb3a66e | ||
|
|
304715d5f3 | ||
|
|
2a2df85fbd |
@@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ enum class LogFormat {
|
||||
rawWithLogs,
|
||||
internalJSON,
|
||||
bar,
|
||||
barWithLogs,
|
||||
};
|
||||
|
||||
void setLogFormat(const std::string & logFormatStr);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -932,6 +932,7 @@ void LocalDerivationGoal::startBuilder()
|
||||
};
|
||||
|
||||
buildResult.startTime = time(0);
|
||||
startTime = std::chrono::steady_clock::now();
|
||||
|
||||
/* Fork a child to build the package. */
|
||||
|
||||
|
||||
@@ -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)
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user