Commit d44a69689e

Andrew Kelley <andrew@ziglang.org>
2019-11-10 20:58:24
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
Changed files (2)
lib/std/build.zig
@@ -957,6 +957,7 @@ pub const Builder = struct {
                 error.ProcessTerminated => error.PkgConfigCrashed,
                 error.ExitCodeFailure => error.PkgConfigFailed,
                 error.FileNotFound => error.PkgConfigNotInstalled,
+                error.InvalidName => error.PkgConfigNotInstalled,
                 error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,
                 else => return err,
             };
lib/std/child_process.zig
@@ -50,8 +50,20 @@ pub const ChildProcess = struct {
     err_pipe: if (builtin.os == .windows) void else [2]os.fd_t,
     llnode: if (builtin.os == .windows) void else TailQueue(*ChildProcess).Node,
 
-    pub const SpawnError = error{OutOfMemory} || os.ExecveError || os.SetIdError ||
-        os.ChangeCurDirError || windows.CreateProcessError;
+    pub const SpawnError = error{
+        OutOfMemory,
+
+        /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
+        NoDevice,
+
+        /// Windows-only. One of:
+        /// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
+        /// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
+        InvalidUtf8,
+
+        /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
+        CurrentWorkingDirectoryUnlinked,
+    } || os.ExecveError || os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError;
 
     pub const Term = union(enum) {
         Exited: u32,
@@ -102,7 +114,7 @@ pub const ChildProcess = struct {
     }
 
     /// On success must call `kill` or `wait`.
-    pub fn spawn(self: *ChildProcess) !void {
+    pub fn spawn(self: *ChildProcess) SpawnError!void {
         if (builtin.os == .windows) {
             return self.spawnWindows();
         } else {
@@ -110,7 +122,7 @@ pub const ChildProcess = struct {
         }
     }
 
-    pub fn spawnAndWait(self: *ChildProcess) !Term {
+    pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term {
         try self.spawn();
         return self.wait();
     }
@@ -162,7 +174,13 @@ pub const ChildProcess = struct {
 
     /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
     /// If it succeeds, the caller owns result.stdout and result.stderr memory.
-    pub fn exec(allocator: *mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const BufMap, max_output_size: usize) !ExecResult {
+    pub fn exec(
+        allocator: *mem.Allocator,
+        argv: []const []const u8,
+        cwd: ?[]const u8,
+        env_map: ?*const BufMap,
+        max_output_size: usize,
+    ) !ExecResult {
         const child = try ChildProcess.init(argv, allocator);
         defer child.deinit();
 
@@ -292,7 +310,7 @@ pub const ChildProcess = struct {
             Term{ .Unknown = status };
     }
 
-    fn spawnPosix(self: *ChildProcess) !void {
+    fn spawnPosix(self: *ChildProcess) SpawnError!void {
         const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;
         errdefer if (self.stdin_behavior == StdIo.Pipe) {
             destroyPipe(stdin_pipe);
@@ -309,7 +327,16 @@ pub const ChildProcess = struct {
         };
 
         const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
-        const dev_null_fd = if (any_ignore) try os.openC(c"/dev/null", os.O_RDWR, 0) else undefined;
+        const dev_null_fd = if (any_ignore)
+            os.openC(c"/dev/null", os.O_RDWR, 0) catch |err| switch (err) {
+                error.PathAlreadyExists => unreachable,
+                error.NoSpaceLeft => unreachable,
+                error.FileTooBig => unreachable,
+                error.DeviceBusy => unreachable,
+                else => |e| return e,
+            }
+        else
+            undefined;
         defer {
             if (any_ignore) os.close(dev_null_fd);
         }
@@ -403,7 +430,7 @@ pub const ChildProcess = struct {
         }
     }
 
-    fn spawnWindows(self: *ChildProcess) !void {
+    fn spawnWindows(self: *ChildProcess) SpawnError!void {
         const saAttr = windows.SECURITY_ATTRIBUTES{
             .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
             .bInheritHandle = windows.TRUE,
@@ -412,11 +439,28 @@ pub const ChildProcess = struct {
 
         const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
 
-        const nul_handle = if (any_ignore) blk: {
-            break :blk try windows.CreateFile("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null);
-        } else blk: {
-            break :blk undefined;
-        };
+        const nul_handle = if (any_ignore)
+            windows.CreateFile(
+                "NUL",
+                windows.GENERIC_READ,
+                windows.FILE_SHARE_READ,
+                null,
+                windows.OPEN_EXISTING,
+                windows.FILE_ATTRIBUTE_NORMAL,
+                null,
+            ) catch |err| switch (err) {
+                error.SharingViolation => unreachable, // not possible for "NUL"
+                error.PathAlreadyExists => unreachable, // not possible for "NUL"
+                error.PipeBusy => unreachable, // not possible for "NUL"
+                error.InvalidUtf8 => unreachable, // not possible for "NUL"
+                error.BadPathName => unreachable, // not possible for "NUL"
+                error.FileNotFound => unreachable, // not possible for "NUL"
+                error.AccessDenied => unreachable, // not possible for "NUL"
+                error.NameTooLong => unreachable, // not possible for "NUL"
+                else => |e| return e,
+            }
+        else
+            undefined;
         defer {
             if (any_ignore) os.close(nul_handle);
         }
@@ -542,10 +586,25 @@ pub const ChildProcess = struct {
         windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
             if (no_path_err != error.FileNotFound) return no_path_err;
 
-            const PATH = try process.getEnvVarOwned(self.allocator, "PATH");
-            defer self.allocator.free(PATH);
-            const PATHEXT = try process.getEnvVarOwned(self.allocator, "PATHEXT");
-            defer self.allocator.free(PATHEXT);
+            var free_path = true;
+            const PATH = process.getEnvVarOwned(self.allocator, "PATH") catch |err| switch (err) {
+                error.EnvironmentVariableNotFound => blk: {
+                    free_path = false;
+                    break :blk "";
+                },
+                else => |e| return e,
+            };
+            defer if (free_path) self.allocator.free(PATH);
+
+            var free_path_ext = true;
+            const PATHEXT = process.getEnvVarOwned(self.allocator, "PATHEXT") catch |err| switch (err) {
+                error.EnvironmentVariableNotFound => blk: {
+                    free_path_ext = false;
+                    break :blk "";
+                },
+                else => |e| return e,
+            };
+            defer if (free_path_ext) self.allocator.free(PATHEXT);
 
             var it = mem.tokenize(PATH, ";");
             retry: while (it.next()) |search_path| {
@@ -717,7 +776,7 @@ fn destroyPipe(pipe: [2]os.fd_t) void {
 // Child of fork calls this to report an error to the fork parent.
 // Then the child exits.
 fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
-    writeIntFd(fd, @as(ErrInt,@errorToInt(err))) catch {};
+    writeIntFd(fd, @as(ErrInt, @errorToInt(err))) catch {};
     os.exit(1);
 }