Skip to content

Commit d44a696

Browse files
committed
std.ChildProcess.spawn has a consistent error set
across targets. Also fix detection of pkg-config not installed on Windows when using zig build.
1 parent 891e214 commit d44a696

File tree

2 files changed

+78
-18
lines changed

2 files changed

+78
-18
lines changed

lib/std/build.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,7 @@ pub const Builder = struct {
957957
error.ProcessTerminated => error.PkgConfigCrashed,
958958
error.ExitCodeFailure => error.PkgConfigFailed,
959959
error.FileNotFound => error.PkgConfigNotInstalled,
960+
error.InvalidName => error.PkgConfigNotInstalled,
960961
error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,
961962
else => return err,
962963
};

lib/std/child_process.zig

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,20 @@ pub const ChildProcess = struct {
5050
err_pipe: if (builtin.os == .windows) void else [2]os.fd_t,
5151
llnode: if (builtin.os == .windows) void else TailQueue(*ChildProcess).Node,
5252

53-
pub const SpawnError = error{OutOfMemory} || os.ExecveError || os.SetIdError ||
54-
os.ChangeCurDirError || windows.CreateProcessError;
53+
pub const SpawnError = error{
54+
OutOfMemory,
55+
56+
/// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
57+
NoDevice,
58+
59+
/// Windows-only. One of:
60+
/// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
61+
/// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
62+
InvalidUtf8,
63+
64+
/// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
65+
CurrentWorkingDirectoryUnlinked,
66+
} || os.ExecveError || os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError;
5567

5668
pub const Term = union(enum) {
5769
Exited: u32,
@@ -102,15 +114,15 @@ pub const ChildProcess = struct {
102114
}
103115

104116
/// On success must call `kill` or `wait`.
105-
pub fn spawn(self: *ChildProcess) !void {
117+
pub fn spawn(self: *ChildProcess) SpawnError!void {
106118
if (builtin.os == .windows) {
107119
return self.spawnWindows();
108120
} else {
109121
return self.spawnPosix();
110122
}
111123
}
112124

113-
pub fn spawnAndWait(self: *ChildProcess) !Term {
125+
pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term {
114126
try self.spawn();
115127
return self.wait();
116128
}
@@ -162,7 +174,13 @@ pub const ChildProcess = struct {
162174

163175
/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
164176
/// If it succeeds, the caller owns result.stdout and result.stderr memory.
165-
pub fn exec(allocator: *mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const BufMap, max_output_size: usize) !ExecResult {
177+
pub fn exec(
178+
allocator: *mem.Allocator,
179+
argv: []const []const u8,
180+
cwd: ?[]const u8,
181+
env_map: ?*const BufMap,
182+
max_output_size: usize,
183+
) !ExecResult {
166184
const child = try ChildProcess.init(argv, allocator);
167185
defer child.deinit();
168186

@@ -292,7 +310,7 @@ pub const ChildProcess = struct {
292310
Term{ .Unknown = status };
293311
}
294312

295-
fn spawnPosix(self: *ChildProcess) !void {
313+
fn spawnPosix(self: *ChildProcess) SpawnError!void {
296314
const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;
297315
errdefer if (self.stdin_behavior == StdIo.Pipe) {
298316
destroyPipe(stdin_pipe);
@@ -309,7 +327,16 @@ pub const ChildProcess = struct {
309327
};
310328

311329
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
312-
const dev_null_fd = if (any_ignore) try os.openC(c"/dev/null", os.O_RDWR, 0) else undefined;
330+
const dev_null_fd = if (any_ignore)
331+
os.openC(c"/dev/null", os.O_RDWR, 0) catch |err| switch (err) {
332+
error.PathAlreadyExists => unreachable,
333+
error.NoSpaceLeft => unreachable,
334+
error.FileTooBig => unreachable,
335+
error.DeviceBusy => unreachable,
336+
else => |e| return e,
337+
}
338+
else
339+
undefined;
313340
defer {
314341
if (any_ignore) os.close(dev_null_fd);
315342
}
@@ -403,7 +430,7 @@ pub const ChildProcess = struct {
403430
}
404431
}
405432

406-
fn spawnWindows(self: *ChildProcess) !void {
433+
fn spawnWindows(self: *ChildProcess) SpawnError!void {
407434
const saAttr = windows.SECURITY_ATTRIBUTES{
408435
.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
409436
.bInheritHandle = windows.TRUE,
@@ -412,11 +439,28 @@ pub const ChildProcess = struct {
412439

413440
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
414441

415-
const nul_handle = if (any_ignore) blk: {
416-
break :blk try windows.CreateFile("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null);
417-
} else blk: {
418-
break :blk undefined;
419-
};
442+
const nul_handle = if (any_ignore)
443+
windows.CreateFile(
444+
"NUL",
445+
windows.GENERIC_READ,
446+
windows.FILE_SHARE_READ,
447+
null,
448+
windows.OPEN_EXISTING,
449+
windows.FILE_ATTRIBUTE_NORMAL,
450+
null,
451+
) catch |err| switch (err) {
452+
error.SharingViolation => unreachable, // not possible for "NUL"
453+
error.PathAlreadyExists => unreachable, // not possible for "NUL"
454+
error.PipeBusy => unreachable, // not possible for "NUL"
455+
error.InvalidUtf8 => unreachable, // not possible for "NUL"
456+
error.BadPathName => unreachable, // not possible for "NUL"
457+
error.FileNotFound => unreachable, // not possible for "NUL"
458+
error.AccessDenied => unreachable, // not possible for "NUL"
459+
error.NameTooLong => unreachable, // not possible for "NUL"
460+
else => |e| return e,
461+
}
462+
else
463+
undefined;
420464
defer {
421465
if (any_ignore) os.close(nul_handle);
422466
}
@@ -542,10 +586,25 @@ pub const ChildProcess = struct {
542586
windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
543587
if (no_path_err != error.FileNotFound) return no_path_err;
544588

545-
const PATH = try process.getEnvVarOwned(self.allocator, "PATH");
546-
defer self.allocator.free(PATH);
547-
const PATHEXT = try process.getEnvVarOwned(self.allocator, "PATHEXT");
548-
defer self.allocator.free(PATHEXT);
589+
var free_path = true;
590+
const PATH = process.getEnvVarOwned(self.allocator, "PATH") catch |err| switch (err) {
591+
error.EnvironmentVariableNotFound => blk: {
592+
free_path = false;
593+
break :blk "";
594+
},
595+
else => |e| return e,
596+
};
597+
defer if (free_path) self.allocator.free(PATH);
598+
599+
var free_path_ext = true;
600+
const PATHEXT = process.getEnvVarOwned(self.allocator, "PATHEXT") catch |err| switch (err) {
601+
error.EnvironmentVariableNotFound => blk: {
602+
free_path_ext = false;
603+
break :blk "";
604+
},
605+
else => |e| return e,
606+
};
607+
defer if (free_path_ext) self.allocator.free(PATHEXT);
549608

550609
var it = mem.tokenize(PATH, ";");
551610
retry: while (it.next()) |search_path| {
@@ -717,7 +776,7 @@ fn destroyPipe(pipe: [2]os.fd_t) void {
717776
// Child of fork calls this to report an error to the fork parent.
718777
// Then the child exits.
719778
fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
720-
writeIntFd(fd, @as(ErrInt,@errorToInt(err))) catch {};
779+
writeIntFd(fd, @as(ErrInt, @errorToInt(err))) catch {};
721780
os.exit(1);
722781
}
723782

0 commit comments

Comments
 (0)