-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathprocess.cppm
More file actions
205 lines (175 loc) · 6.53 KB
/
process.cppm
File metadata and controls
205 lines (175 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// mcpp.platform.process — platform-aware process runner.
//
// Centralises all popen/system usage so callers do not scatter #if _WIN32
// guards or duplicate the popen-read loop. All functions automatically
// seal stdin (redirect from /dev/null on POSIX, from NUL on Windows) to
// prevent interactive prompts from child processes:
// - POSIX: fixes macOS first-run hangs where xcrun / xcode-select would
// block waiting for user input.
// - Windows: fixes first-run hangs where xlings / xim / curl / git child
// processes would block on terminal stdin, forcing the user to press
// Enter repeatedly to advance bootstrap / toolchain install.
//
// Entry points:
// capture — run a command, capture stdout
// run_silent — run a command, discard output
// run_streaming — run a command, stream stdout line by line
//
// NOTE: These functions run commands through the platform shell (sh/cmd.exe).
// Callers are responsible for shell-quoting arguments (see platform.shell).
module;
#include <cstdio>
#include <cstdlib>
#if defined(_WIN32)
#include <stdlib.h> // _putenv_s
#define popen _popen
#define pclose _pclose
#endif
export module mcpp.platform.process;
import std;
export namespace mcpp::platform::process {
struct RunResult {
int exit_code = 0;
std::string output;
};
// Run `command` via the platform shell, capture stdout.
// On POSIX, stdin is automatically redirected from /dev/null.
RunResult capture(std::string_view command);
// Run `command` with extra environment variables (additive).
// Windows: _putenv_s (mutates calling process env).
// POSIX: prefixes command with VAR=val tokens (no mutation).
RunResult capture_with_env(
std::string_view command,
const std::vector<std::pair<std::string, std::string>>& env);
// Run `command` silently (discard stdout/stderr).
// On POSIX, stdin is automatically redirected from /dev/null.
int run_silent(std::string_view command);
// Run `command`, stream stdout line-by-line via callback.
// On POSIX, stdin is automatically redirected from /dev/null.
int run_streaming(std::string_view command,
std::function<void(std::string_view line)> on_line);
// Run `command`, passing stdout/stderr through to the terminal.
// Optionally captures stdout into `output` if non-null.
// Returns a platform-normalized exit code (WEXITSTATUS on POSIX).
int run_passthrough(std::string_view command,
std::string* output = nullptr);
// Extract a platform-normalized exit code from a raw system()/pclose()
// return value. Windows returns the exit code directly; POSIX returns
// a wait-status word requiring WIFEXITED/WEXITSTATUS unwrapping.
int extract_exit_code(int raw_status);
} // namespace mcpp::platform::process
// ─── Implementation ──────────────────────────────────────────────────────
namespace mcpp::platform::process {
namespace {
// Append a non-interactive stdin redirect to prevent child processes from
// blocking on terminal input.
// - POSIX: "< /dev/null" — fixes macOS xcrun / xcode-select hangs.
// - Windows: "<NUL" — fixes xlings / xim / curl / git hangs on
// first-run toolchain install (user otherwise
// had to press Enter repeatedly to advance).
// `cmd.exe` accepts `<NUL` as a redirect for an immediately-EOF stdin.
std::string seal_stdin(std::string_view cmd) {
#if defined(_WIN32)
return std::string(cmd) + " <NUL";
#else
return std::string(cmd) + " </dev/null";
#endif
}
int normalize_exit_code(int rc) {
#if defined(_WIN32)
return rc;
#else
if (WIFEXITED(rc))
return WEXITSTATUS(rc);
return rc;
#endif
}
} // namespace
int extract_exit_code(int raw_status) {
return normalize_exit_code(raw_status);
}
RunResult capture(std::string_view command) {
auto cmd = seal_stdin(command);
RunResult result;
std::FILE* fp = ::popen(cmd.c_str(), "r");
if (!fp) {
result.exit_code = -1;
return result;
}
std::array<char, 4096> buf{};
while (std::fgets(buf.data(), static_cast<int>(buf.size()), fp) != nullptr)
result.output += buf.data();
result.exit_code = normalize_exit_code(::pclose(fp));
return result;
}
RunResult capture_with_env(
std::string_view command,
const std::vector<std::pair<std::string, std::string>>& env)
{
#if defined(_WIN32)
for (auto& [k, v] : env)
_putenv_s(k.c_str(), v.c_str());
return capture(command);
#else
std::string prefixed;
for (auto& [k, v] : env) {
prefixed += k;
prefixed += '=';
// Simple quoting for env values
prefixed += '\'';
for (char c : v) {
if (c == '\'') prefixed += "'\\''";
else prefixed += c;
}
prefixed += '\'';
prefixed += ' ';
}
prefixed += command;
return capture(prefixed);
#endif
}
int run_silent(std::string_view command) {
auto cmd = seal_stdin(command);
return normalize_exit_code(std::system(cmd.c_str()));
}
int run_streaming(std::string_view command,
std::function<void(std::string_view line)> on_line)
{
auto cmd = seal_stdin(command);
std::FILE* fp = ::popen(cmd.c_str(), "r");
if (!fp) return -1;
std::array<char, 16384> buf{};
std::string acc;
while (std::fgets(buf.data(), static_cast<int>(buf.size()), fp) != nullptr) {
acc += buf.data();
std::size_t pos;
while ((pos = acc.find('\n')) != std::string::npos) {
if (on_line) {
auto line = std::string_view{acc}.substr(0, pos);
while (!line.empty() && line.back() == '\r')
line.remove_suffix(1);
on_line(line);
}
acc.erase(0, pos + 1);
}
}
if (!acc.empty() && on_line) {
std::string_view line{acc};
while (!line.empty() && line.back() == '\r')
line.remove_suffix(1);
if (!line.empty()) on_line(line);
}
return normalize_exit_code(::pclose(fp));
}
int run_passthrough(std::string_view command, std::string* output) {
auto cmd = seal_stdin(command);
std::FILE* fp = ::popen(cmd.c_str(), "r");
if (!fp) return -1;
std::array<char, 8192> buf{};
while (std::fgets(buf.data(), static_cast<int>(buf.size()), fp) != nullptr) {
if (output) *output += buf.data();
std::fputs(buf.data(), stdout);
}
return normalize_exit_code(::pclose(fp));
}
} // namespace mcpp::platform::process