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:
parent
8af4f886e2
commit
2a2df85fbd
1 changed files with 125 additions and 22 deletions
|
|
@ -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,16 +107,105 @@ 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
|
||||||
|
{
|
||||||
|
if (inputThread.joinable()) {
|
||||||
|
assert(inputPipe.writeSide);
|
||||||
|
writeFull(inputPipe.writeSide.get(), "x", false);
|
||||||
|
inputThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
if (!state->active) return;
|
if (!state->active) return;
|
||||||
|
|
@ -116,8 +215,12 @@ public:
|
||||||
quitCV.notify_one();
|
quitCV.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isVerbose() override {
|
updateThread.join();
|
||||||
return printBuildLogs;
|
}
|
||||||
|
|
||||||
|
bool isVerbose() override
|
||||||
|
{
|
||||||
|
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,21 +364,21 @@ 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);
|
state->activities.erase(i->second);
|
||||||
info.lastLine = lastLine;
|
info.lastLine = lastLine;
|
||||||
state->activities.emplace_back(info);
|
state->activities.emplace_back(info);
|
||||||
i->second = std::prev(state->activities.end());
|
i->second = std::prev(state->activities.end());
|
||||||
|
if (state->printBuildLogs)
|
||||||
update(*state);
|
update(*state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
else if (type == resUntrustedPath) {
|
else if (type == resUntrustedPath) {
|
||||||
state->untrustedPaths++;
|
state->untrustedPaths++;
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue