libmain: add progress bar with multiple status lines

Add the log-formats `multiline` and `multiline-with-logs` which offer
multiple current active building status lines.

Change-Id: Idd8afe62f8591b5d8b70e258c5cefa09be4cab03
This commit is contained in:
kloenk 2024-06-01 16:06:26 +02:00
parent 8a3d063a49
commit b289df3ab7
4 changed files with 100 additions and 29 deletions

View file

@ -17,6 +17,12 @@ LogFormat parseLogFormat(const std::string & logFormatStr) {
return LogFormat::bar; return LogFormat::bar;
else if (logFormatStr == "bar-with-logs") else if (logFormatStr == "bar-with-logs")
return LogFormat::barWithLogs; return LogFormat::barWithLogs;
#if !defined(WIN32) || !defined(_WIN32) || !defined(__WIN32__) || !defined(__NT__)
else if (logFormatStr == "multiline")
return LogFormat::multiline;
else if (logFormatStr == "multiline-with-logs")
return LogFormat::multilineWithLogs;
#endif
throw Error("option 'log-format' has an invalid value '%s'", logFormatStr); throw Error("option 'log-format' has an invalid value '%s'", logFormatStr);
} }
@ -35,6 +41,19 @@ Logger * makeDefaultLogger() {
logger->setPrintBuildLogs(true); logger->setPrintBuildLogs(true);
return logger; return logger;
} }
#if !defined(WIN32) || !defined(_WIN32) || !defined(__WIN32__) || !defined(__NT__)
case LogFormat::multiline: {
auto logger = makeProgressBar();
logger->setPrintMultiline(true);
return logger;
}
case LogFormat::multilineWithLogs: {
auto logger = makeProgressBar();
logger->setPrintMultiline(true);
logger->setPrintBuildLogs(true);
return logger;
}
#endif
default: default:
abort(); abort();
} }

View file

@ -11,6 +11,10 @@ enum class LogFormat {
internalJSON, internalJSON,
bar, bar,
barWithLogs, barWithLogs,
#if !defined(WIN32) || !defined(_WIN32) || !defined(__WIN32__) || !defined(__NT__)
multiline,
multilineWithLogs,
#endif
}; };
void setLogFormat(const std::string & logFormatStr); void setLogFormat(const std::string & logFormatStr);

View file

@ -73,6 +73,8 @@ private:
std::map<ActivityType, ActivitiesByType> activitiesByType; std::map<ActivityType, ActivitiesByType> activitiesByType;
int lastLines = 0;
uint64_t filesLinked = 0, bytesLinked = 0; uint64_t filesLinked = 0, bytesLinked = 0;
uint64_t corruptedPaths = 0, untrustedPaths = 0; uint64_t corruptedPaths = 0, untrustedPaths = 0;
@ -89,6 +91,7 @@ private:
std::condition_variable quitCV, updateCV; std::condition_variable quitCV, updateCV;
bool printBuildLogs = false; bool printBuildLogs = false;
bool printMultiline = false;
bool isTTY; bool isTTY;
public: public:
@ -103,7 +106,7 @@ public:
while (state->active) { while (state->active) {
if (!state->haveUpdate) if (!state->haveUpdate)
state.wait_for(updateCV, nextWakeup); state.wait_for(updateCV, nextWakeup);
nextWakeup = draw(*state); nextWakeup = draw(*state, {});
state.wait_for(quitCV, std::chrono::milliseconds(50)); state.wait_for(quitCV, std::chrono::milliseconds(50));
} }
}); });
@ -165,8 +168,7 @@ public:
void log(State & state, Verbosity lvl, std::string_view s) void log(State & state, Verbosity lvl, std::string_view s)
{ {
if (state.active) { if (state.active) {
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n"); draw(state, s);
draw(state);
} else { } else {
auto s2 = s + ANSI_NORMAL "\n"; auto s2 = s + ANSI_NORMAL "\n";
if (!isTTY) s2 = filterANSIEscapes(s2, true); if (!isTTY) s2 = filterANSIEscapes(s2, true);
@ -354,60 +356,99 @@ public:
updateCV.notify_one(); updateCV.notify_one();
} }
std::chrono::milliseconds draw(State & state) std::chrono::milliseconds draw(State & state, const std::optional<std::string_view> & s)
{ {
auto nextWakeup = A_LONG_TIME; auto nextWakeup = A_LONG_TIME;
state.haveUpdate = false; state.haveUpdate = false;
if (state.paused || !state.active) return nextWakeup; if (state.paused || !state.active) return nextWakeup;
std::string line; auto windowSize = getWindowSize();
auto width = windowSize.second;
if (width <= 0) {
width = std::numeric_limits<decltype(width)>::max();
}
if (printMultiline && (state.lastLines >= 1)) {
writeToStderr(fmt("\e[G\e[%dF\e[J", state.lastLines));
}
state.lastLines = 0;
if (s != std::nullopt)
writeToStderr("\r\e[K" + filterANSIEscapes(s.value(), !isTTY) + ANSI_NORMAL "\n");
std::string line;
std::string status = getStatus(state); std::string status = getStatus(state);
if (!status.empty()) { if (!status.empty()) {
line += '['; line += '[';
line += status; line += status;
line += "]"; line += "]";
} }
if (printMultiline && !line.empty()) {
writeToStderr(filterANSIEscapes(line, false, width) + "\n");
state.lastLines++;
}
auto height = windowSize.first > 0 ? windowSize.first : 25;
auto moreBuilds = 0;
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
if (!state.activities.empty()) { if (!state.activities.empty()) {
if (!status.empty()) line += " "; for (auto i = state.activities.begin(); i != state.activities.end(); ++i) {
auto i = state.activities.rbegin(); if (!(i->visible && (!i->s.empty() || !i->lastLine.empty()))) {
continue;
while (i != state.activities.rend()) { }
if (i->visible && (!i->s.empty() || !i->lastLine.empty())) {
/* Don't show activities until some time has /* Don't show activities until some time has
passed, to avoid displaying very short passed, to avoid displaying very short
activities. */ activities. */
auto delay = std::chrono::milliseconds(10); auto delay = std::chrono::milliseconds(10);
if (i->startTime + delay < now) if (i->startTime + delay >= now) {
break; nextWakeup = std::min(
else nextWakeup,
nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime))); std::chrono::duration_cast<std::chrono::milliseconds>(
delay - (now - i->startTime)
)
);
}
if (printMultiline) {
line = i->s;
} else {
line += " ";
line += i->s;
} }
++i;
}
if (i != state.activities.rend()) {
line += i->s;
if (!i->phase.empty()) { if (!i->phase.empty()) {
line += " ("; line += " (";
line += i->phase; line += i->phase;
line += ")"; line += ")";
} }
if (!i->lastLine.empty()) { if (!i->lastLine.empty()) {
if (!i->s.empty()) line += ": "; if (!i->s.empty()) {
line += ": ";
}
line += i->lastLine; line += i->lastLine;
} }
if (printMultiline) {
if (state.lastLines < (height - 1)) {
writeToStderr(filterANSIEscapes(line, false, width) + "\n");
state.lastLines++;
} else {
moreBuilds++;
}
}
} }
} }
auto width = getWindowSize().second; if (printMultiline && moreBuilds) {
if (width <= 0) width = std::numeric_limits<decltype(width)>::max(); writeToStderr(fmt("And %d more...", moreBuilds));
}
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K"); if (!printMultiline) {
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
}
return nextWakeup; return nextWakeup;
} }
@ -506,9 +547,8 @@ public:
{ {
auto state(state_.lock()); auto state(state_.lock());
if (state->active) { if (state->active) {
std::cerr << "\r\e[K";
Logger::writeToStdout(s); Logger::writeToStdout(s);
draw(*state); draw(*state, {});
} else { } else {
Logger::writeToStdout(s); Logger::writeToStdout(s);
} }
@ -521,7 +561,7 @@ public:
std::cerr << fmt("\r\e[K%s ", msg); std::cerr << fmt("\r\e[K%s ", msg);
auto s = trim(readLine(STDIN_FILENO)); auto s = trim(readLine(STDIN_FILENO));
if (s.size() != 1) return {}; if (s.size() != 1) return {};
draw(*state); draw(*state, {});
return s[0]; return s[0];
} }
@ -529,6 +569,11 @@ public:
{ {
this->printBuildLogs = printBuildLogs; this->printBuildLogs = printBuildLogs;
} }
void setPrintMultiline(bool printMultiline) override
{
this->printMultiline = printMultiline;
}
}; };
Logger * makeProgressBar() Logger * makeProgressBar()

View file

@ -114,6 +114,9 @@ public:
virtual void setPrintBuildLogs(bool printBuildLogs) virtual void setPrintBuildLogs(bool printBuildLogs)
{ } { }
virtual void setPrintMultiline(bool printMultiline)
{ }
}; };
/** /**