Skip to content

Commit e6ebcb2

Browse files
committed
fix(run): use PTY for live runtime stdout
1 parent 056f7a1 commit e6ebcb2

File tree

1 file changed

+91
-56
lines changed

1 file changed

+91
-56
lines changed

src/commands/run/RunProcess.cpp

Lines changed: 91 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
#include <sys/wait.h>
3333
#include <sys/resource.h>
3434
#include <unistd.h>
35+
36+
#if defined(__APPLE__)
37+
#include <util.h>
38+
#else
39+
#include <pty.h>
40+
#endif
3541
#endif
3642

3743
#ifdef _WIN32
@@ -680,9 +686,10 @@ namespace vix::commands::RunCommand::detail
680686

681687
LiveRunResult result;
682688

683-
int outPipe[2] = {-1, -1};
689+
int masterFd = -1;
690+
int slaveFd = -1;
684691

685-
if (::pipe(outPipe) != 0)
692+
if (::openpty(&masterFd, &slaveFd, nullptr, nullptr, nullptr) != 0)
686693
{
687694
const int st = std::system(cmd.c_str());
688695
result.rawStatus = st;
@@ -693,8 +700,8 @@ namespace vix::commands::RunCommand::detail
693700
pid_t pid = ::fork();
694701
if (pid < 0)
695702
{
696-
close_safe(outPipe[0]);
697-
close_safe(outPipe[1]);
703+
close_safe(masterFd);
704+
close_safe(slaveFd);
698705

699706
const int st = std::system(cmd.c_str());
700707
result.rawStatus = st;
@@ -710,7 +717,9 @@ namespace vix::commands::RunCommand::detail
710717
saChild.sa_flags = 0;
711718
::sigaction(SIGINT, &saChild, nullptr);
712719

713-
::setpgid(0, 0);
720+
// Nouvelle session pour détacher le child du terminal parent
721+
// et permettre au PTY esclave de jouer le rôle de terminal.
722+
::setsid();
714723

715724
::setenv("ASAN_OPTIONS",
716725
"abort_on_error=1:"
@@ -727,12 +736,19 @@ namespace vix::commands::RunCommand::detail
727736
"halt_on_error=1:print_stacktrace=1:color=never",
728737
1);
729738

730-
::close(outPipe[0]);
739+
::close(masterFd);
740+
741+
::dup2(slaveFd, STDIN_FILENO);
742+
::dup2(slaveFd, STDOUT_FILENO);
743+
::dup2(slaveFd, STDERR_FILENO);
731744

732-
::dup2(outPipe[1], STDOUT_FILENO);
733-
::dup2(outPipe[1], STDERR_FILENO);
745+
if (slaveFd > STDERR_FILENO)
746+
::close(slaveFd);
734747

735-
::close(outPipe[1]);
748+
// Avec un PTY, beaucoup de programmes passent naturellement
749+
// en comportement terminal interactif. On garde quand même ceci.
750+
::setvbuf(stdout, nullptr, _IOLBF, 0);
751+
::setvbuf(stderr, nullptr, _IONBF, 0);
736752

737753
if (::getenv("VIX_MODE") == nullptr)
738754
::setenv("VIX_MODE", "run", 1);
@@ -748,7 +764,7 @@ namespace vix::commands::RunCommand::detail
748764
_exit(127);
749765
}
750766

751-
close_safe(outPipe[1]);
767+
close_safe(slaveFd);
752768
::setpgid(pid, pid);
753769

754770
const bool useSpinner = !spinnerLabel.empty();
@@ -922,7 +938,6 @@ namespace vix::commands::RunCommand::detail
922938
auto has = [&](std::string_view s) noexcept
923939
{ return line.find(s) != std::string_view::npos; };
924940

925-
// libstdc++ / libc++ terminate noise
926941
if (has("terminate called after throwing an instance of"))
927942
return true;
928943
if (has("terminating with uncaught exception"))
@@ -931,13 +946,8 @@ namespace vix::commands::RunCommand::detail
931946
return true;
932947
if (has("std::terminate"))
933948
return true;
934-
935-
// the missing line in your output
936-
// libstdc++ prints: " what(): Weird!"
937949
if (has("what():"))
938950
return true;
939-
940-
// common endings (optional)
941951
if (has("Aborted (core dumped)"))
942952
return true;
943953
if (has("core dumped"))
@@ -952,18 +962,15 @@ namespace vix::commands::RunCommand::detail
952962
{
953963
for (char c : s)
954964
{
955-
// ignore line endings
956965
if (c == '\n' || c == '\r')
957966
continue;
958967

959-
// any non-space/tab means it's not whitespace-only
960968
if (c != ' ' && c != '\t')
961969
return false;
962970
}
963971
return true;
964972
}
965973

966-
// Remove noise from what we PRINT, but keep it in result.stdoutText capture.
967974
std::string filter_for_print(const std::string &chunk)
968975
{
969976
std::string data = carry;
@@ -1002,6 +1009,32 @@ namespace vix::commands::RunCommand::detail
10021009
SanitizerSuppressor sanitizer;
10031010
UncaughtExceptionSuppressor uncaught;
10041011

1012+
auto read_from_pty = [](int fd, std::string &outChunk) -> bool
1013+
{
1014+
char buf[4096];
1015+
const ssize_t n = ::read(fd, buf, sizeof(buf));
1016+
if (n > 0)
1017+
{
1018+
outChunk.assign(buf, static_cast<std::size_t>(n));
1019+
return true;
1020+
}
1021+
1022+
outChunk.clear();
1023+
1024+
// Sur PTY, quand le slave se ferme, read() peut retourner
1025+
// 0 ou -1 avec errno = EIO. On traite les deux comme EOF.
1026+
if (n == 0)
1027+
return false;
1028+
1029+
if (n < 0 && errno == EINTR)
1030+
return true;
1031+
1032+
if (n < 0 && errno == EIO)
1033+
return false;
1034+
1035+
return false;
1036+
};
1037+
10051038
bool running = true;
10061039
bool printedSomething = false;
10071040
bool printedRealOutput = false;
@@ -1132,10 +1165,10 @@ namespace vix::commands::RunCommand::detail
11321165
FD_ZERO(&fds);
11331166

11341167
int maxfd = -1;
1135-
if (outPipe[0] >= 0)
1168+
if (masterFd >= 0)
11361169
{
1137-
FD_SET(outPipe[0], &fds);
1138-
maxfd = outPipe[0];
1170+
FD_SET(masterFd, &fds);
1171+
maxfd = masterFd;
11391172
}
11401173

11411174
if (maxfd < 0)
@@ -1183,58 +1216,61 @@ namespace vix::commands::RunCommand::detail
11831216
spinnerActive = false;
11841217
}
11851218

1186-
if (outPipe[0] >= 0 && FD_ISSET(outPipe[0], &fds))
1219+
if (masterFd >= 0 && FD_ISSET(masterFd, &fds))
11871220
{
11881221
std::string chunk;
1189-
if (read_into(outPipe[0], chunk))
1222+
if (read_from_pty(masterFd, chunk))
11901223
{
1191-
result.stdoutText += chunk;
1192-
1193-
if (!suppress_known_failure_output && is_known_runtime_port_in_use(chunk))
1194-
suppress_known_failure_output = true;
1224+
if (!chunk.empty())
1225+
{
1226+
result.stdoutText += chunk;
11951227

1196-
if (suppress_known_failure_output)
1197-
continue;
1228+
if (!suppress_known_failure_output && is_known_runtime_port_in_use(chunk))
1229+
suppress_known_failure_output = true;
11981230

1199-
if (!should_drop_chunk_default(chunk))
1200-
{
1201-
std::string printable = chunk;
1231+
if (suppress_known_failure_output)
1232+
continue;
12021233

1203-
if (cmakeConfigure)
1204-
printable = cmakeNoise.filter(printable);
1234+
if (!should_drop_chunk_default(chunk))
1235+
{
1236+
std::string printable = chunk;
12051237

1206-
if (!printable.empty())
1207-
printable = sanitizer.filter_for_print(printable);
1238+
if (cmakeConfigure)
1239+
printable = cmakeNoise.filter(printable);
12081240

1209-
if (!printable.empty())
1210-
printable = uncaught.filter_for_print(printable);
1241+
if (!printable.empty())
1242+
printable = sanitizer.filter_for_print(printable);
12111243

1212-
if (!printable.empty())
1213-
{
1214-
std::string filtered =
1215-
passthroughRuntime ? printable : runtimeFilter.process(printable);
1244+
if (!printable.empty())
1245+
printable = uncaught.filter_for_print(printable);
12161246

1217-
if (!filtered.empty())
1247+
if (!printable.empty())
12181248
{
1219-
std::string toPrint = drop_vix_error_tip_lines(filtered);
1220-
if (!toPrint.empty())
1221-
toPrint = drop_sanitizer_abort_banner_lines(toPrint);
1249+
std::string filtered =
1250+
passthroughRuntime ? printable : runtimeFilter.process(printable);
12221251

1223-
if (!toPrint.empty() && !captureOnly)
1252+
if (!filtered.empty())
12241253
{
1225-
write_all(STDOUT_FILENO, toPrint.data(), toPrint.size());
1226-
printedSomething = true;
1227-
printedRealOutput = true;
1228-
result.printed_live = true;
1229-
lastPrintedChar = toPrint.back();
1254+
std::string toPrint = drop_vix_error_tip_lines(filtered);
1255+
if (!toPrint.empty())
1256+
toPrint = drop_sanitizer_abort_banner_lines(toPrint);
1257+
1258+
if (!toPrint.empty() && !captureOnly)
1259+
{
1260+
write_all(STDOUT_FILENO, toPrint.data(), toPrint.size());
1261+
printedSomething = true;
1262+
printedRealOutput = true;
1263+
result.printed_live = true;
1264+
lastPrintedChar = toPrint.back();
1265+
}
12301266
}
12311267
}
12321268
}
12331269
}
12341270
}
12351271
else
12361272
{
1237-
close_safe(outPipe[0]);
1273+
close_safe(masterFd);
12381274
}
12391275
}
12401276
}
@@ -1252,7 +1288,7 @@ namespace vix::commands::RunCommand::detail
12521288
if (spinnerActive && !captureOnly)
12531289
spinner_clear(printedSomething, lastPrintedChar);
12541290

1255-
close_safe(outPipe[0]);
1291+
close_safe(masterFd);
12561292

12571293
if (!haveStatus)
12581294
{
@@ -1277,7 +1313,6 @@ namespace vix::commands::RunCommand::detail
12771313
const int sig = WTERMSIG(finalStatus);
12781314
result.terminatedBySignal = true;
12791315
result.termSignal = sig;
1280-
12811316
result.exitCode = 128 + sig;
12821317
}
12831318
else

0 commit comments

Comments
 (0)