Skip to content

Commit f04abab

Browse files
committed
fix(run): show quiet configure heartbeat
2 parents d8b433c + bdec179 commit f04abab

3 files changed

Lines changed: 286 additions & 39 deletions

File tree

src/build/BuildStyle.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ namespace vix::cli::build
114114
print_label(out, label);
115115
out << " " << value << "\n\n";
116116
}
117+
118+
static void print_compact_optional_line(
119+
std::ostream &out,
120+
const std::string &label,
121+
const std::string &value)
122+
{
123+
if (value.empty())
124+
return;
125+
126+
out << " "
127+
<< colorize(label_color(label).c_str(), label)
128+
<< " "
129+
<< value
130+
<< "\n";
131+
}
117132
} // namespace
118133

119134
void print_build_header_full(
@@ -378,23 +393,20 @@ namespace vix::cli::build
378393
<< ""
379394
<< title
380395
<< style::RESET
381-
<< "\n\n";
396+
<< "\n";
382397

383-
if (!diagnostic.message.empty())
384-
{
385-
print_label(out, "message:");
386-
out << " " << diagnostic.message << "\n\n";
387-
}
398+
print_compact_optional_line(out, "message:", diagnostic.message);
388399

389400
if (diagnostic.has_location())
390401
{
391-
print_label(out, "location:");
392-
out << " "
402+
out << " "
403+
<< colorize(label_color("location:").c_str(), "location:")
404+
<< " "
393405
<< format_build_location(diagnostic.location)
394-
<< "\n\n";
406+
<< "\n";
395407
}
396408

397-
print_optional_line(out, "error:", diagnostic.error);
409+
print_compact_optional_line(out, "error:", diagnostic.error);
398410

399411
if (diagnostic.has_code_frame())
400412
{
@@ -432,7 +444,7 @@ namespace vix::cli::build
432444
out << "\n";
433445
}
434446

435-
print_optional_line(out, "hint:", diagnostic.hint);
447+
print_compact_optional_line(out, "hint:", diagnostic.hint);
436448
}
437449

438450
std::string format_build_location(

src/commands/run/RunProcess.cpp

Lines changed: 215 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
#include <cstring>
2525
#include <string>
2626
#include <string_view>
27+
#include <iomanip>
28+
#include <sstream>
2729

2830
#ifndef _WIN32
2931
#include <errno.h>
3032
#include <signal.h>
33+
#include <sys/ioctl.h>
3134
#include <sys/resource.h>
3235
#include <sys/select.h>
3336
#include <sys/types.h>
@@ -277,6 +280,42 @@ namespace vix::commands::RunCommand::detail
277280
}
278281
}
279282

283+
std::size_t terminal_width() noexcept
284+
{
285+
struct winsize ws{};
286+
287+
if (::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
288+
return static_cast<std::size_t>(ws.ws_col);
289+
290+
return 120;
291+
}
292+
293+
std::string format_seconds(long long milliseconds)
294+
{
295+
const double seconds = static_cast<double>(milliseconds) / 1000.0;
296+
297+
std::ostringstream out;
298+
out.setf(std::ios::fixed);
299+
out.precision(seconds >= 10.0 ? 1 : 2);
300+
out << seconds << "s";
301+
302+
return out.str();
303+
}
304+
305+
std::string truncate_terminal_line(const std::string &line, std::size_t width)
306+
{
307+
if (width == 0)
308+
return "";
309+
310+
if (line.size() <= width)
311+
return line;
312+
313+
if (width <= 3)
314+
return std::string(width, '.');
315+
316+
return line.substr(0, width - 3) + "...";
317+
}
318+
280319
std::size_t utf8_safe_prefix_len(const std::string &s, std::size_t want)
281320
{
282321
if (want >= s.size())
@@ -385,13 +424,37 @@ namespace vix::commands::RunCommand::detail
385424

386425
bool is_cmake_configure_cmd(const std::string &cmd) noexcept
387426
{
388-
const bool isCmake = (cmd.find("cmake") != std::string::npos);
389-
const bool isBuild = (cmd.find("--build") != std::string::npos);
390-
const bool isPreset = (cmd.find("--preset") != std::string::npos);
391-
const bool isDotDot = (cmd.find("cmake ..") != std::string::npos) ||
392-
(cmd.find("cmake ..") != std::string::npos);
427+
const bool hasCmake =
428+
cmd.find("cmake") != std::string::npos;
429+
430+
if (!hasCmake)
431+
return false;
432+
433+
if (cmd.find("--build") != std::string::npos)
434+
return false;
435+
436+
if (cmd.find("--install") != std::string::npos)
437+
return false;
438+
439+
if (cmd.find("--preset") != std::string::npos)
440+
return true;
441+
442+
if (cmd.find("cmake ..") != std::string::npos ||
443+
cmd.find("cmake ..") != std::string::npos)
444+
return true;
445+
446+
const bool hasSource =
447+
cmd.find(" -S ") != std::string::npos ||
448+
cmd.find(" -S.") != std::string::npos ||
449+
cmd.find(" -S\"") != std::string::npos ||
450+
cmd.find(" -S'") != std::string::npos;
393451

394-
return isCmake && !isBuild && (isPreset || isDotDot);
452+
const bool hasBuild =
453+
cmd.find(" -B ") != std::string::npos ||
454+
cmd.find(" -B\"") != std::string::npos ||
455+
cmd.find(" -B'") != std::string::npos;
456+
457+
return hasSource && hasBuild;
395458
}
396459

397460
bool looks_like_error_or_warning(std::string_view line) noexcept
@@ -683,6 +746,7 @@ namespace vix::commands::RunCommand::detail
683746
while (true)
684747
{
685748
const std::size_t nl = data.find('\n', start);
749+
686750
if (nl == std::string::npos)
687751
{
688752
carry = data.substr(start);
@@ -692,22 +756,12 @@ namespace vix::commands::RunCommand::detail
692756
std::string_view line(&data[start], (nl - start) + 1);
693757
start = nl + 1;
694758

759+
/*
760+
* During CMake configure, Vix must never dump normal CMake traces.
761+
* Only real errors and warnings are allowed to reach the terminal.
762+
*/
695763
if (looks_like_error_or_warning(line))
696-
{
697764
out.append(line.data(), line.size());
698-
continue;
699-
}
700-
701-
if (line.rfind("-- ", 0) == 0)
702-
continue;
703-
704-
if (line.find("Preset CMake variables:") != std::string_view::npos)
705-
continue;
706-
707-
if (line.rfind(" CMAKE_", 0) == 0)
708-
continue;
709-
710-
out.append(line.data(), line.size());
711765
}
712766

713767
return out;
@@ -1134,8 +1188,23 @@ namespace vix::commands::RunCommand::detail
11341188
std::string printable = chunk;
11351189

11361190
if (cmakeConfigure)
1191+
{
11371192
printable = cmakeNoise.filter(printable);
11381193

1194+
if (printable.empty())
1195+
return;
1196+
1197+
if (captureOnly || useSan)
1198+
return;
1199+
1200+
write_all(STDOUT_FILENO, printable.data(), printable.size());
1201+
printedSomething = true;
1202+
printedRealOutput = true;
1203+
result.printed_live = true;
1204+
lastPrintedChar = printable.back();
1205+
return;
1206+
}
1207+
11391208
if (printable.empty())
11401209
return;
11411210

@@ -1263,6 +1332,87 @@ namespace vix::commands::RunCommand::detail
12631332
const auto startTime = std::chrono::steady_clock::now();
12641333
bool didTimeout = false;
12651334

1335+
auto lastOutputTime = startTime;
1336+
auto lastHeartbeatTime = startTime;
1337+
bool heartbeatVisible = false;
1338+
1339+
auto heartbeat_env_enabled = [&]() -> bool
1340+
{
1341+
if (captureOnly)
1342+
return false;
1343+
1344+
if (passthroughRuntime)
1345+
return false;
1346+
1347+
if (!cmakeConfigure)
1348+
return false;
1349+
1350+
const char *runValue = vix::utils::vix_getenv("VIX_RUN_HEARTBEAT");
1351+
const char *buildValue = vix::utils::vix_getenv("VIX_BUILD_HEARTBEAT");
1352+
1353+
const char *value = runValue && *runValue ? runValue : buildValue;
1354+
1355+
if (!value || !*value)
1356+
return true;
1357+
1358+
std::string s(value);
1359+
for (char &c : s)
1360+
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
1361+
1362+
if (s == "0" || s == "false" || s == "no" || s == "off")
1363+
return false;
1364+
1365+
return true;
1366+
}();
1367+
1368+
auto clear_heartbeat_line = [&]() -> void
1369+
{
1370+
if (!heartbeatVisible)
1371+
return;
1372+
1373+
const std::size_t width = terminal_width();
1374+
1375+
std::string clear;
1376+
clear += "\r";
1377+
clear.append(width, ' ');
1378+
clear += "\r";
1379+
1380+
write_all(STDOUT_FILENO, clear.data(), clear.size());
1381+
heartbeatVisible = false;
1382+
};
1383+
1384+
auto render_heartbeat_line = [&](long long elapsedMs) -> void
1385+
{
1386+
const std::size_t width = terminal_width();
1387+
1388+
std::string line;
1389+
line += " ";
1390+
line += CYAN;
1391+
line += "configure";
1392+
line += RESET;
1393+
line += " still running... ";
1394+
line += GRAY;
1395+
line += "(" + format_seconds(elapsedMs) + ", checking/downloading dependencies)";
1396+
line += RESET;
1397+
1398+
std::string out;
1399+
out += "\r";
1400+
out.append(width, ' ');
1401+
out += "\r";
1402+
out += truncate_terminal_line(line, width);
1403+
1404+
write_all(STDOUT_FILENO, out.data(), out.size());
1405+
heartbeatVisible = true;
1406+
};
1407+
1408+
auto finish_heartbeat_line = [&]() -> void
1409+
{
1410+
if (!heartbeatVisible)
1411+
return;
1412+
1413+
clear_heartbeat_line();
1414+
};
1415+
12661416
bool sentInt = false;
12671417
bool sentTerm = false;
12681418
bool sentKill = false;
@@ -1362,7 +1512,7 @@ namespace vix::commands::RunCommand::detail
13621512
struct timeval tv;
13631513
struct timeval *tv_ptr = nullptr;
13641514

1365-
if (spinnerActive || enableTimeout)
1515+
if (spinnerActive || enableTimeout || heartbeat_env_enabled)
13661516
{
13671517
tv.tv_sec = 0;
13681518
tv.tv_usec = 100000;
@@ -1413,6 +1563,17 @@ namespace vix::commands::RunCommand::detail
14131563
{
14141564
if (!chunk.empty())
14151565
{
1566+
const bool outputMayBePrinted =
1567+
!cmakeConfigure || looks_like_error_or_warning(std::string_view(chunk));
1568+
1569+
if (outputMayBePrinted)
1570+
{
1571+
lastOutputTime = std::chrono::steady_clock::now();
1572+
1573+
if (heartbeatVisible && !captureOnly)
1574+
clear_heartbeat_line();
1575+
}
1576+
14161577
result.stdoutText += chunk;
14171578

14181579
if (replayCapture)
@@ -1447,6 +1608,33 @@ namespace vix::commands::RunCommand::detail
14471608
}
14481609
}
14491610

1611+
if (heartbeat_env_enabled)
1612+
{
1613+
const auto now = std::chrono::steady_clock::now();
1614+
1615+
const auto silenceMs =
1616+
std::chrono::duration_cast<std::chrono::milliseconds>(
1617+
now - lastOutputTime)
1618+
.count();
1619+
1620+
const auto heartbeatMs =
1621+
std::chrono::duration_cast<std::chrono::milliseconds>(
1622+
now - lastHeartbeatTime)
1623+
.count();
1624+
1625+
if (silenceMs >= 2000 && heartbeatMs >= 1000)
1626+
{
1627+
lastHeartbeatTime = now;
1628+
1629+
const auto elapsedMs =
1630+
std::chrono::duration_cast<std::chrono::milliseconds>(
1631+
now - startTime)
1632+
.count();
1633+
1634+
render_heartbeat_line(elapsedMs);
1635+
}
1636+
}
1637+
14501638
int status = 0;
14511639
const pid_t r = ::waitpid(pid, &status, WNOHANG);
14521640
if (r == pid)
@@ -1476,6 +1664,9 @@ namespace vix::commands::RunCommand::detail
14761664

14771665
if (didTimeout)
14781666
{
1667+
if (!captureOnly)
1668+
finish_heartbeat_line();
1669+
14791670
result.exitCode = 124;
14801671
return result;
14811672
}
@@ -1501,6 +1692,9 @@ namespace vix::commands::RunCommand::detail
15011692
write_all(STDOUT_FILENO, &nl, 1);
15021693
}
15031694

1695+
if (!captureOnly)
1696+
finish_heartbeat_line();
1697+
15041698
if (userInterrupted)
15051699
result.exitCode = 130;
15061700

0 commit comments

Comments
 (0)