Skip to content

Commit 902648e

Browse files
committed
Simplify API
1 parent 735971c commit 902648e

3 files changed

Lines changed: 79 additions & 86 deletions

File tree

lib/std/os/process.c3

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -41,38 +41,30 @@ constdef SpawnOptions
4141
ASYNC_OUTPUT = 1 << 2,
4242
NO_WINDOW = 1 << 3,
4343
NO_PATH_SEARCH = 1 << 4,
44-
INHERIT_STDIO @deprecated = 1 << 5
44+
INHERIT_STDIO = 1 << 5
4545
}
4646

47-
const INHERIT_STDIO_MASK @local = 1 << 5;
4847

4948
alias RawFd = env::WIN32 ? Win32_HANDLE : CInt;
5049

5150
enum ProcessStdioAction
5251
{
52+
DEFAULT,
5353
CAPTURE,
5454
INHERIT,
5555
BIND
5656
}
5757

5858
struct ProcessStdio
5959
{
60-
ProcessStdioAction kind;
60+
ProcessStdioAction action;
6161
RawFd fd;
6262
}
6363

64-
const ProcessStdio CAPTURE = { .kind = CAPTURE };
65-
const ProcessStdio INHERIT = { .kind = INHERIT };
66-
macro ProcessStdio bind(RawFd fd) => { .kind = BIND, .fd = fd };
67-
68-
struct ProcessStdioSet
69-
{
70-
ProcessStdio stdin;
71-
ProcessStdio stdout;
72-
ProcessStdio stderr;
73-
}
74-
75-
const ProcessStdioSet INHERIT_STDIO = { .stdin.kind = INHERIT, .stdout.kind = INHERIT, .stderr.kind = INHERIT };
64+
const ProcessStdio CAPTURE = { .action = CAPTURE };
65+
const ProcessStdio INHERIT = { .action = INHERIT };
66+
const ProcessStdio DEFAULT = { .action = DEFAULT };
67+
macro ProcessStdio bind(RawFd fd) => { .action = BIND, .fd = fd };
7668

7769
fn String? run_capture_stdout(char[] buffer, String[] command_line, SpawnOptions options = {}, String[] environment = {})
7870
{
@@ -84,18 +76,24 @@ fn String? run_capture_stdout(char[] buffer, String[] command_line, SpawnOptions
8476
return (String)buffer[:len - 1];
8577
}
8678

87-
fn int? run(String[] command_line, SpawnOptions options = {}, String[] environment = {}, ProcessStdioSet stdio = {})
79+
fn int? run(String[] command_line, SpawnOptions options = {}, String[] environment = {},
80+
ProcessStdio in = DEFAULT, ProcessStdio out = DEFAULT, ProcessStdio err = DEFAULT)
8881
{
89-
Process process = process::spawn(command_line, options, environment, stdio)!;
82+
Process process = process::spawn(command_line, options, environment, in, out, err)!;
9083
defer (void)process.destroy();
9184
return process.join()!;
9285
}
9386

87+
9488
<*
9589
@require !environment || !(options & INHERIT_ENV)
9690
*>
97-
fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] environment = {}, ProcessStdioSet stdio = {}) @if(env::WIN32)
91+
fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] environment = {},
92+
ProcessStdio in = DEFAULT, ProcessStdio out = DEFAULT, ProcessStdio err = DEFAULT) @if(env::WIN32 || env::POSIX)
9893
{
94+
update_default(&in, &out, &err, !!(options & INHERIT_STDIO));
95+
96+
$if env::WIN32:
9997
Win32_DWORD flags = win32::CREATE_UNICODE_ENVIRONMENT;
10098
Win32_PROCESS_INFORMATION process_info;
10199
Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES::size, null, 1 };
@@ -106,15 +104,15 @@ fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] env
106104

107105
if (options & NO_WINDOW) flags |= win32::CREATE_NO_WINDOW;
108106

109-
// Backwards compatibility
110-
if (options & INHERIT_STDIO_MASK) stdio = { INHERIT, INHERIT, INHERIT };
111107

112108
CFile stdin, stdout, stderr;
113109
void* rd, wr;
114110
void* event_output, event_error;
115111

116-
switch (stdio.stdin.kind)
112+
switch (in.action)
117113
{
114+
case DEFAULT:
115+
unreachable();
118116
case CAPTURE:
119117
if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE~;
120118
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE~;
@@ -127,13 +125,15 @@ fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] env
127125
}
128126
start_info.hStdInput = rd;
129127
case BIND:
130-
start_info.hStdInput = stdio.stdin.fd;
128+
start_info.hStdInput = in.fd;
131129
case INHERIT:
132130
start_info.hStdInput = win32::getStdHandle(win32::STD_INPUT_HANDLE);
133131
}
134132

135-
switch (stdio.stdout.kind)
133+
switch (out.action)
136134
{
135+
case DEFAULT:
136+
unreachable();
137137
case CAPTURE:
138138
if (options & ASYNC_OUTPUT)
139139
{
@@ -155,7 +155,7 @@ fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] env
155155

156156
start_info.hStdOutput = wr;
157157
case BIND:
158-
start_info.hStdOutput = stdio.stdout.fd;
158+
start_info.hStdOutput = out.fd;
159159
case INHERIT:
160160
start_info.hStdOutput = win32::getStdHandle(win32::STD_OUTPUT_HANDLE);
161161
}
@@ -167,8 +167,10 @@ fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] env
167167
}
168168
else
169169
{
170-
switch (stdio.stderr.kind)
170+
switch (err.action)
171171
{
172+
case DEFAULT:
173+
unreachable();
172174
case CAPTURE:
173175
if (options & ASYNC_OUTPUT)
174176
{
@@ -189,7 +191,7 @@ fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] env
189191
}
190192
start_info.hStdError = wr;
191193
case BIND:
192-
start_info.hStdError = stdio.stderr.fd;
194+
start_info.hStdError = err.fd;
193195
case INHERIT:
194196
start_info.hStdError = win32::getStdHandle(win32::STD_ERROR_HANDLE);
195197
}
@@ -230,69 +232,56 @@ fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] env
230232
// We don't need the handle of the primary thread in the called process.
231233
win32::closeHandle(process_info.hThread);
232234

233-
if (stdio.stdout.kind == CAPTURE) win32::closeHandle(start_info.hStdOutput);
234-
if (stdio.stderr.kind == CAPTURE && start_info.hStdOutput != start_info.hStdError)
235+
if (out.action == CAPTURE) win32::closeHandle(start_info.hStdOutput);
236+
if (err.action == CAPTURE && start_info.hStdOutput != start_info.hStdError)
235237
{
236238
win32::closeHandle(start_info.hStdError);
237239
}
238240

239241

240242
return {
241243
.hProcess = process_info.hProcess,
242-
.hStdInput = stdio.stdin.kind == CAPTURE ? start_info.hStdInput : null,
244+
.hStdInput = in.action == CAPTURE ? start_info.hStdInput : null,
243245
.hEventOutput = event_output,
244246
.hEventError = event_error,
245247
.stdin_file = stdin,
246248
.stdout_file = stdout,
247249
.stderr_file = stderr,
248250
.is_alive = true,
249251
};
250-
}
251-
252-
253-
254-
255-
<*
256-
@require !environment || !(options & INHERIT_ENV)
257-
*>
258-
fn Process? spawn(String[] command_line, SpawnOptions options = { }, String[] environment = {}, ProcessStdioSet stdio = {}) @if(env::POSIX)
259-
{
260-
CInt[2] stdinfd;
261-
CInt[2] stdoutfd;
262-
CInt[2] stderrfd;
263-
CFile stdin = null;
264-
CFile stdout = null;
265-
CFile stderr = null;
266-
252+
$else
267253
Posix_spawn_file_actions_t actions;
268254
if (posix::spawn_file_actions_init(&actions)) return FAILED_TO_INITIALIZE_ACTIONS~;
269255
defer posix::spawn_file_actions_destroy(&actions);
270256

271-
// Backwards compatibility
272-
if (options & INHERIT_STDIO_MASK) stdio = { INHERIT, INHERIT, INHERIT };
273-
274-
275257
// We should set FD_CLOEXEC to prevent leaking pipe handles between concurrent spawns
276258
const int F_SETFD = 2;
277259
const int FD_CLOEXEC = 1;
278260

279-
switch (stdio.stdin.kind)
261+
CInt[2] stdinfd, stdoutfd, stderrfd;
262+
CFile stdin, stdout, stderr;
263+
264+
switch (in.action)
280265
{
281-
case CAPTURE:
266+
case DEFAULT:
267+
unreachable();
268+
case CAPTURE:
282269
if (posix::pipe(&stdinfd)) return FAILED_TO_OPEN_STDIN~;
283270
os::fcntl((NativeSocket)stdinfd[0], F_SETFD, FD_CLOEXEC);
284271
os::fcntl((NativeSocket)stdinfd[1], F_SETFD, FD_CLOEXEC);
285272
if (posix::spawn_file_actions_adddup2(&actions, stdinfd[0], libc::STDIN_FD)) return FAILED_TO_OPEN_STDIN~;
286273
case BIND:
287-
if (posix::spawn_file_actions_adddup2(&actions, stdio.stdin.fd, libc::STDIN_FD)) return FAILED_TO_BIND_STDIN~;
274+
if (posix::spawn_file_actions_adddup2(&actions, in.fd, libc::STDIN_FD)) return FAILED_TO_BIND_STDIN~;
288275
case INHERIT:
289276
break;
290277
}
291278

292-
switch (stdio.stdout.kind)
279+
switch (out.action)
293280
{
281+
case DEFAULT:
282+
unreachable();
294283
case CAPTURE:
295-
if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT~;
284+
if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT~;
296285
os::fcntl((NativeSocket)stdoutfd[0], F_SETFD, FD_CLOEXEC);
297286
os::fcntl((NativeSocket)stdoutfd[1], F_SETFD, FD_CLOEXEC);
298287

@@ -303,7 +292,7 @@ fn Process? spawn(String[] command_line, SpawnOptions options = { }, String[] en
303292
}
304293
if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return FAILED_TO_OPEN_STDOUT~;
305294
case BIND:
306-
if (posix::spawn_file_actions_adddup2(&actions, stdio.stdout.fd, libc::STDOUT_FD)) return FAILED_TO_BIND_STDOUT~;
295+
if (posix::spawn_file_actions_adddup2(&actions, out.fd, libc::STDOUT_FD)) return FAILED_TO_BIND_STDOUT~;
307296
case INHERIT:
308297
break;
309298
}
@@ -314,21 +303,23 @@ fn Process? spawn(String[] command_line, SpawnOptions options = { }, String[] en
314303
}
315304
else
316305
{
317-
switch (stdio.stderr.kind)
306+
switch (err.action)
318307
{
308+
case DEFAULT:
309+
unreachable();
319310
case CAPTURE:
320311
if (posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR~;
321312
os::fcntl((NativeSocket)stderrfd[0], F_SETFD, FD_CLOEXEC);
322313
os::fcntl((NativeSocket)stderrfd[1], F_SETFD, FD_CLOEXEC);
323-
314+
324315
if (options & ASYNC_OUTPUT)
325316
{
326317
((NativeSocket)stderrfd[0]).set_non_blocking(true)!;
327318
((NativeSocket)stderrfd[1]).set_non_blocking(true)!;
328319
}
329320
if (posix::spawn_file_actions_adddup2(&actions, stderrfd[1], libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR~;
330321
case BIND:
331-
if (posix::spawn_file_actions_adddup2(&actions, stdio.stderr.fd, libc::STDERR_FD)) return FAILED_TO_BIND_STDERR~;
322+
if (posix::spawn_file_actions_adddup2(&actions, err.fd, libc::STDERR_FD)) return FAILED_TO_BIND_STDERR~;
332323
case INHERIT:
333324
break;
334325
}
@@ -350,17 +341,17 @@ fn Process? spawn(String[] command_line, SpawnOptions options = { }, String[] en
350341
};
351342

352343
// Only set up file handles for when stdio is captured
353-
if (stdio.stdin.kind == CAPTURE)
344+
if (in.action == CAPTURE)
354345
{
355346
libc::close(stdinfd[0]);
356347
stdin = libc::fdopen(stdinfd[1], "wb");
357348
}
358-
if (stdio.stdout.kind == CAPTURE)
349+
if (out.action == CAPTURE)
359350
{
360351
libc::close(stdoutfd[1]);
361352
stdout = libc::fdopen(stdoutfd[0], "rb");
362353
}
363-
if (stdio.stderr.kind == CAPTURE)
354+
if (err.action == CAPTURE)
364355
{
365356
if (options & STDERR_TO_STDOUT)
366357
{
@@ -381,8 +372,13 @@ fn Process? spawn(String[] command_line, SpawnOptions options = { }, String[] en
381372
.child = child,
382373
.is_alive = true,
383374
};
375+
$endif
384376
}
385377

378+
379+
380+
381+
386382
fn CInt? Process.join(&self) @if(env::POSIX)
387383
{
388384
if (self.stdin_file)
@@ -434,7 +430,7 @@ fn bool Process.destroy(&self)
434430
{
435431
if (self.stdin_file) libc::fclose(self.stdin_file);
436432
if (self.stdout_file) libc::fclose(self.stdout_file);
437-
if (self.stderr_file && self.stderr_file != self.stdout_file) libc::fclose(self.stderr_file);
433+
if (self.stderr_file && self.stderr_file != self.stdout_file) libc::fclose(self.stderr_file);
438434
self.stdin_file = self.stdout_file = self.stderr_file = null;
439435
$if env::WIN32:
440436
if (self.hProcess) win32::closeHandle(self.hProcess);
@@ -630,3 +626,11 @@ fn ZString* copy_env(Allocator mem, String[] environment) @local @inline @if(env
630626
}
631627
return copy;
632628
}
629+
630+
macro update_default(ProcessStdio* in, ProcessStdio* out, ProcessStdio* err, bool inherit_by_default) @local
631+
{
632+
ProcessStdioAction action = inherit_by_default ? INHERIT : CAPTURE;
633+
if (in.action == DEFAULT) in.action = action;
634+
if (out.action == DEFAULT) out.action = action;
635+
if (err.action == DEFAULT) err.action = action;
636+
}

releasenotes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
- `ini::parse` and related takes an `error_line` argument to identify the line with error.
1919
- JSON marshaling will return INVALID_NUMBER when encountering a inf or NaN for a float.
2020
- JSON decoding will reject `1.` literals.
21-
21+
- `spawn` now allows binding I/O and using different settings per pipe.
22+
2223
### Fixes
2324
- `@volatile_store` on arrays were sometimes incorrectly lowered.
2425
- NPOT vectors as associated variables were incorrectly lowered on load. #3228

0 commit comments

Comments
 (0)