1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-21 09:49:36 +01:00

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.
This commit is contained in:
Eelco Dolstra 2020-11-10 11:25:19 +01:00
parent 8af4f886e2
commit 2a2df85fbd

View file

@ -9,6 +9,9 @@
#include <thread> #include <thread>
#include <iostream> #include <iostream>
#include <termios.h>
#include <poll.h>
namespace nix { namespace nix {
static std::string getS(const std::vector<Logger::Field> & fields, size_t n) static std::string getS(const std::vector<Logger::Field> & fields, size_t n)
@ -71,24 +74,31 @@ private:
bool active = true; bool active = true;
bool haveUpdate = true; bool haveUpdate = true;
bool printBuildLogs;
}; };
bool isTTY;
Sync<State> state_; Sync<State> state_;
std::thread updateThread; std::thread updateThread;
std::thread inputThread;
std::condition_variable quitCV, updateCV; std::condition_variable quitCV, updateCV;
bool printBuildLogs; std::optional<struct termios> savedTermAttrs;
bool isTTY;
Pipe inputPipe;
public: public:
ProgressBar(bool printBuildLogs, bool isTTY) ProgressBar(bool printBuildLogs, bool isTTY)
: printBuildLogs(printBuildLogs) : isTTY(isTTY)
, isTTY(isTTY) , state_({ .active = isTTY, .printBuildLogs = printBuildLogs })
{ {
state_.lock()->active = isTTY; state_.lock()->active = isTTY;
updateThread = std::thread([&]() { updateThread = std::thread([&]() {
auto state(state_.lock()); auto state(state_.lock());
while (state->active) { while (state->active) {
@ -97,27 +107,120 @@ public:
draw(*state); draw(*state);
state.wait_for(quitCV, std::chrono::milliseconds(50)); state.wait_for(quitCV, std::chrono::milliseconds(50));
} }
if (savedTermAttrs) {
tcsetattr(STDIN_FILENO, TCSANOW, &*savedTermAttrs);
savedTermAttrs.reset();
}
}); });
if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO)) {
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 == 'q') {
triggerInterrupt();
break;
}
if (c == 'l') {
auto state(state_.lock());
log(*state, lvlError,
state->printBuildLogs
? ANSI_BOLD "Disabling build logs."
: ANSI_BOLD "Enabling build logs.");
state->printBuildLogs = !state->printBuildLogs;
draw(*state);
}
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 == '?') {
printError(
ANSI_BOLD "The following keys are available:\n"
" 'v' to increase verbosity.\n"
" '-' to decrease verbosity.\n"
" 'l' to show build log output.\n"
" 'q' to quit." ANSI_NORMAL);
}
}
});
log(lvlError, "Type 'h' for help.");
}
} }
~ProgressBar() ~ProgressBar()
{ {
stop(); stop();
updateThread.join();
} }
void stop() override void stop() override
{ {
auto state(state_.lock()); if (inputThread.joinable()) {
if (!state->active) return; assert(inputPipe.writeSide);
state->active = false; writeFull(inputPipe.writeSide.get(), "x", false);
writeToStderr("\r\e[K"); inputThread.join();
updateCV.notify_one(); }
quitCV.notify_one();
{
auto state(state_.lock());
if (!state->active) return;
state->active = false;
writeToStderr("\r\e[K");
updateCV.notify_one();
quitCV.notify_one();
}
updateThread.join();
} }
bool isVerbose() override { bool isVerbose() override
return printBuildLogs; {
return state_.lock()->printBuildLogs;
} }
void log(Verbosity lvl, const FormatOrString & fs) override void log(Verbosity lvl, const FormatOrString & fs) override
@ -139,7 +242,7 @@ public:
void log(State & state, Verbosity lvl, const std::string & s) void log(State & state, Verbosity lvl, const std::string & s)
{ {
if (state.active) { if (state.active) {
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n"); writeToStderr("\r\e[K" + replaceStrings(filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n", "\n", "\r\n"));
draw(state); draw(state);
} else { } else {
auto s2 = s + ANSI_NORMAL "\n"; auto s2 = s + ANSI_NORMAL "\n";
@ -261,19 +364,19 @@ public:
auto i = state->its.find(act); auto i = state->its.find(act);
assert(i != state->its.end()); assert(i != state->its.end());
ActInfo info = *i->second; ActInfo info = *i->second;
if (printBuildLogs) { if (state->printBuildLogs) {
auto suffix = "> "; auto suffix = "> ";
if (type == resPostBuildLogLine) { if (type == resPostBuildLogLine) {
suffix = " (post)> "; suffix = " (post)> ";
} }
log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine); 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());
update(*state);
} }
state->activities.erase(i->second);
info.lastLine = lastLine;
state->activities.emplace_back(info);
i->second = std::prev(state->activities.end());
if (state->printBuildLogs)
update(*state);
} }
} }
@ -352,7 +455,7 @@ public:
line += i->phase; line += i->phase;
line += ")"; line += ")";
} }
if (!i->lastLine.empty()) { if (!state.printBuildLogs && !i->lastLine.empty()) {
if (!i->s.empty()) line += ": "; if (!i->s.empty()) line += ": ";
line += i->lastLine; line += i->lastLine;
} }