authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-12-07 02:56:41-08:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-02-02 01:41:35-08:00
log05346e123bc5dbc20a7a60655b06b55030a84f47
treecb3fc030e20efdda9e427d08568e7ecf26562d06
parent83abd73801a6ab3125c1b0b8ba432be188c9e7ca

Add process.Child.Cwd, use it for cwd and remove cwd_dir field

The user must now explicitly choose between inheriting the current CWD, passing a path for the CWD, or passing a Dir for the CWD.

13 files changed, 112 insertions(+), 95 deletions(-)

lib/compiler/std-docs.zig+5-5
...@@ -398,7 +398,7 @@ fn buildWasmBinary(...@@ -398,7 +398,7 @@ fn buildWasmBinary(
398 if (code != 0) {398 if (code != 0) {
399 std.log.err(399 std.log.err(
400 "the following command exited with error code {d}:\n{s}",400 "the following command exited with error code {d}:\n{s}",
401 .{ code, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) },401 .{ code, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
402 );402 );
403 return error.WasmCompilationFailed;403 return error.WasmCompilationFailed;
404 }404 }
...@@ -406,14 +406,14 @@ fn buildWasmBinary(...@@ -406,14 +406,14 @@ fn buildWasmBinary(
406 .signal => |sig| {406 .signal => |sig| {
407 std.log.err(407 std.log.err(
408 "the following command terminated with signal {t}:\n{s}",408 "the following command terminated with signal {t}:\n{s}",
409 .{ sig, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) },409 .{ sig, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
410 );410 );
411 return error.WasmCompilationFailed;411 return error.WasmCompilationFailed;
412 },412 },
413 .stopped, .unknown => {413 .stopped, .unknown => {
414 std.log.err(414 std.log.err(
415 "the following command terminated unexpectedly:\n{s}",415 "the following command terminated unexpectedly:\n{s}",
416 .{try std.Build.Step.allocPrintCmd(arena, null, null, argv.items)},416 .{try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)},
417 );417 );
418 return error.WasmCompilationFailed;418 return error.WasmCompilationFailed;
419 },419 },
...@@ -423,14 +423,14 @@ fn buildWasmBinary(...@@ -423,14 +423,14 @@ fn buildWasmBinary(
423 try result_error_bundle.renderToStderr(io, .{}, .auto);423 try result_error_bundle.renderToStderr(io, .{}, .auto);
424 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{424 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
425 result_error_bundle.errorMessageCount(),425 result_error_bundle.errorMessageCount(),
426 try std.Build.Step.allocPrintCmd(arena, null, null, argv.items),426 try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
427 });427 });
428 return error.WasmCompilationFailed;428 return error.WasmCompilationFailed;
429 }429 }
430430
431 return result orelse {431 return result orelse {
432 std.log.err("child process failed to report result\n{s}", .{432 std.log.err("child process failed to report result\n{s}", .{
433 try std.Build.Step.allocPrintCmd(arena, null, null, argv.items),433 try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
434 });434 });
435 return error.WasmCompilationFailed;435 return error.WasmCompilationFailed;
436 };436 };
lib/std/Build.zig+2-2
...@@ -1868,7 +1868,7 @@ pub fn runAllowFail(...@@ -1868,7 +1868,7 @@ pub fn runAllowFail(
1868 const io = graph.io;1868 const io = graph.io;
18691869
1870 const max_output_size = 400 * 1024;1870 const max_output_size = 400 * 1024;
1871 try Step.handleVerbose2(b, null, &graph.environ_map, argv);1871 try Step.handleVerbose2(b, .inherit, &graph.environ_map, argv);
18721872
1873 var child = try std.process.spawn(io, .{1873 var child = try std.process.spawn(io, .{
1874 .argv = argv,1874 .argv = argv,
...@@ -1911,7 +1911,7 @@ pub fn run(b: *Build, argv: []const []const u8) []u8 {...@@ -1911,7 +1911,7 @@ pub fn run(b: *Build, argv: []const []const u8) []u8 {
1911 var code: u8 = undefined;1911 var code: u8 = undefined;
1912 return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal(1912 return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal(
1913 "the following command failed with {t}:\n{s}",1913 "the following command failed with {t}:\n{s}",
1914 .{ err, Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM") },1914 .{ err, Step.allocPrintCmd(b.allocator, .inherit, null, argv) catch @panic("OOM") },
1915 );1915 );
1916}1916}
19171917
lib/std/Build/Step.zig+16-12
...@@ -350,10 +350,10 @@ pub fn captureChildProcess(...@@ -350,10 +350,10 @@ pub fn captureChildProcess(
350350
351 // If an error occurs, it's happened in this command:351 // If an error occurs, it's happened in this command:
352 assert(s.result_failed_command == null);352 assert(s.result_failed_command == null);
353 s.result_failed_command = try allocPrintCmd(gpa, null, null, argv);353 s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv);
354354
355 try handleChildProcUnsupported(s);355 try handleChildProcUnsupported(s);
356 try handleVerbose(s.owner, null, argv);356 try handleVerbose(s.owner, .inherit, argv);
357357
358 const result = std.process.run(arena, io, .{358 const result = std.process.run(arena, io, .{
359 .argv = argv,359 .argv = argv,
...@@ -410,7 +410,7 @@ pub fn evalZigProcess(...@@ -410,7 +410,7 @@ pub fn evalZigProcess(
410410
411 // If an error occurs, it's happened in this command:411 // If an error occurs, it's happened in this command:
412 assert(s.result_failed_command == null);412 assert(s.result_failed_command == null);
413 s.result_failed_command = try allocPrintCmd(gpa, null, null, argv);413 s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv);
414414
415 if (s.getZigProcess()) |zp| update: {415 if (s.getZigProcess()) |zp| update: {
416 assert(watch);416 assert(watch);
...@@ -449,7 +449,7 @@ pub fn evalZigProcess(...@@ -449,7 +449,7 @@ pub fn evalZigProcess(
449 assert(argv.len != 0);449 assert(argv.len != 0);
450450
451 try handleChildProcUnsupported(s);451 try handleChildProcUnsupported(s);
452 try handleVerbose(s.owner, null, argv);452 try handleVerbose(s.owner, .inherit, argv);
453453
454 const zp = try gpa.create(ZigProcess);454 const zp = try gpa.create(ZigProcess);
455 defer if (!watch) gpa.destroy(zp);455 defer if (!watch) gpa.destroy(zp);
...@@ -515,7 +515,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u...@@ -515,7 +515,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u
515 const b = s.owner;515 const b = s.owner;
516 const io = b.graph.io;516 const io = b.graph.io;
517 const src_path = src_lazy_path.getPath3(b, s);517 const src_path = src_lazy_path.getPath3(b, s);
518 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });518 try handleVerbose(b, .inherit, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
519 return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|519 return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|
520 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });520 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });
521}521}
...@@ -524,7 +524,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u...@@ -524,7 +524,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u
524pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus {524pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus {
525 const b = s.owner;525 const b = s.owner;
526 const io = b.graph.io;526 const io = b.graph.io;
527 try handleVerbose(b, null, &.{ "install", "-d", dest_path });527 try handleVerbose(b, .inherit, &.{ "install", "-d", dest_path });
528 return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err|528 return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err|
529 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });529 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });
530}530}
...@@ -700,15 +700,15 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {...@@ -700,15 +700,15 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
700700
701pub fn handleVerbose(701pub fn handleVerbose(
702 b: *Build,702 b: *Build,
703 opt_cwd: ?[]const u8,703 cwd: std.process.Child.Cwd,
704 argv: []const []const u8,704 argv: []const []const u8,
705) error{OutOfMemory}!void {705) error{OutOfMemory}!void {
706 return handleVerbose2(b, opt_cwd, null, argv);706 return handleVerbose2(b, cwd, null, argv);
707}707}
708708
709pub fn handleVerbose2(709pub fn handleVerbose2(
710 b: *Build,710 b: *Build,
711 opt_cwd: ?[]const u8,711 cwd: std.process.Child.Cwd,
712 opt_env: ?*const std.process.Environ.Map,712 opt_env: ?*const std.process.Environ.Map,
713 argv: []const []const u8,713 argv: []const []const u8,
714) error{OutOfMemory}!void {714) error{OutOfMemory}!void {
...@@ -716,7 +716,7 @@ pub fn handleVerbose2(...@@ -716,7 +716,7 @@ pub fn handleVerbose2(
716 const graph = b.graph;716 const graph = b.graph;
717 // Intention of verbose is to print all sub-process command lines to717 // Intention of verbose is to print all sub-process command lines to
718 // stderr before spawning them.718 // stderr before spawning them.
719 const text = try allocPrintCmd(b.allocator, opt_cwd, if (opt_env) |env| .{719 const text = try allocPrintCmd(b.allocator, cwd, if (opt_env) |env| .{
720 .child = env,720 .child = env,
721 .parent = &graph.environ_map,721 .parent = &graph.environ_map,
722 } else null, argv);722 } else null, argv);
...@@ -751,7 +751,7 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak...@@ -751,7 +751,7 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak
751751
752pub fn allocPrintCmd(752pub fn allocPrintCmd(
753 gpa: Allocator,753 gpa: Allocator,
754 opt_cwd: ?[]const u8,754 cwd: std.process.Child.Cwd,
755 opt_env: ?struct {755 opt_env: ?struct {
756 child: *const std.process.Environ.Map,756 child: *const std.process.Environ.Map,
757 parent: *const std.process.Environ.Map,757 parent: *const std.process.Environ.Map,
...@@ -796,7 +796,11 @@ pub fn allocPrintCmd(...@@ -796,7 +796,11 @@ pub fn allocPrintCmd(
796 var aw: Io.Writer.Allocating = .init(gpa);796 var aw: Io.Writer.Allocating = .init(gpa);
797 defer aw.deinit();797 defer aw.deinit();
798 const writer = &aw.writer;798 const writer = &aw.writer;
799 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;799 switch (cwd) {
800 .inherit => {},
801 .path => |path| writer.print("cd {s} && ", .{path}) catch return error.OutOfMemory,
802 .dir => @panic("TODO"),
803 }
800 if (opt_env) |env| {804 if (opt_env) |env| {
801 var it = env.child.iterator();805 var it = env.child.iterator();
802 while (it.next()) |entry| {806 while (it.next()) |entry| {
lib/std/Build/Step/InstallArtifact.zig+1-1
...@@ -187,7 +187,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -187,7 +187,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
187 const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path });187 const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path });
188 switch (entry.kind) {188 switch (entry.kind) {
189 .directory => {189 .directory => {
190 try Step.handleVerbose(b, null, &.{ "install", "-d", full_dest_path });190 try Step.handleVerbose(b, .inherit, &.{ "install", "-d", full_dest_path });
191 const p = try step.installDir(full_dest_path);191 const p = try step.installDir(full_dest_path);
192 all_cached = all_cached and p == .existed;192 all_cached = all_cached and p == .existed;
193 },193 },
lib/std/Build/Step/Run.zig+2-2
...@@ -1227,7 +1227,7 @@ fn runCommand(...@@ -1227,7 +1227,7 @@ fn runCommand(
1227 const gpa = options.gpa;1227 const gpa = options.gpa;
1228 const io = b.graph.io;1228 const io = b.graph.io;
12291229
1230 const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;1230 const cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, step) } else .inherit;
12311231
1232 try step.handleChildProcUnsupported();1232 try step.handleChildProcUnsupported();
1233 try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv);1233 try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv);
...@@ -1549,7 +1549,7 @@ fn spawnChildAndCollect(...@@ -1549,7 +1549,7 @@ fn spawnChildAndCollect(
1549 assert(run.stdio == .zig_test);1549 assert(run.stdio == .zig_test);
1550 }1550 }
15511551
1552 const child_cwd = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, &run.step) else null;1552 const child_cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, &run.step) } else .inherit;
15531553
1554 // If an error occurs, it's caused by this command:1554 // If an error occurs, it's caused by this command:
1555 assert(run.step.result_failed_command == null);1555 assert(run.step.result_failed_command == null);
lib/std/Build/WebServer.zig+5-5
...@@ -652,7 +652,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -652,7 +652,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
652 if (code != 0) {652 if (code != 0) {
653 log.err(653 log.err(
654 "the following command exited with error code {d}:\n{s}",654 "the following command exited with error code {d}:\n{s}",
655 .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) },655 .{ code, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
656 );656 );
657 return error.WasmCompilationFailed;657 return error.WasmCompilationFailed;
658 }658 }
...@@ -660,14 +660,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -660,14 +660,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
660 .signal => |sig| {660 .signal => |sig| {
661 log.err(661 log.err(
662 "the following command terminated with signal {t}:\n{s}",662 "the following command terminated with signal {t}:\n{s}",
663 .{ sig, try Build.Step.allocPrintCmd(arena, null, null, argv.items) },663 .{ sig, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
664 );664 );
665 return error.WasmCompilationFailed;665 return error.WasmCompilationFailed;
666 },666 },
667 .stopped, .unknown => {667 .stopped, .unknown => {
668 log.err(668 log.err(
669 "the following command terminated unexpectedly:\n{s}",669 "the following command terminated unexpectedly:\n{s}",
670 .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)},670 .{try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)},
671 );671 );
672 return error.WasmCompilationFailed;672 return error.WasmCompilationFailed;
673 },673 },
...@@ -677,14 +677,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -677,14 +677,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
677 try result_error_bundle.renderToStderr(io, .{}, .auto);677 try result_error_bundle.renderToStderr(io, .{}, .auto);
678 log.err("the following command failed with {d} compilation errors:\n{s}", .{678 log.err("the following command failed with {d} compilation errors:\n{s}", .{
679 result_error_bundle.errorMessageCount(),679 result_error_bundle.errorMessageCount(),
680 try Build.Step.allocPrintCmd(arena, null, null, argv.items),680 try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
681 });681 });
682 return error.WasmCompilationFailed;682 return error.WasmCompilationFailed;
683 }683 }
684684
685 const base_path = result orelse {685 const base_path = result orelse {
686 log.err("child process failed to report result\n{s}", .{686 log.err("child process failed to report result\n{s}", .{
687 try Build.Step.allocPrintCmd(arena, null, null, argv.items),687 try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
688 });688 });
689 return error.WasmCompilationFailed;689 return error.WasmCompilationFailed;
690 };690 };
lib/std/Io/Threaded.zig+27-21
...@@ -14606,10 +14606,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14606,10 +14606,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14606 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);14606 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
14607 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);14607 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
1460814608
14609 if (options.cwd_dir) |cwd| {14609 switch (options.cwd) {
14610 fchdir(cwd.handle) catch |err| forkBail(ep1, err);14610 .inherit => {},
14611 } else if (options.cwd) |cwd| {14611 .dir => |cwd| {
14612 chdir(cwd) catch |err| forkBail(ep1, err);14612 fchdir(cwd.handle) catch |err| forkBail(ep1, err);
14613 },
14614 .path => |cwd| {
14615 chdir(cwd) catch |err| forkBail(ep1, err);
14616 },
14613 }14617 }
1461414618
14615 // Must happen after fchdir above, the cwd file descriptor might be14619 // Must happen after fchdir above, the cwd file descriptor might be
...@@ -15194,23 +15198,25 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15194,23 +15198,25 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15194 const arena = arena_allocator.allocator();15198 const arena = arena_allocator.allocator();
1519515199
15196 const cwd_w = cwd_w: {15200 const cwd_w = cwd_w: {
15197 if (options.cwd_dir) |cwd_dir| {15201 switch (options.cwd) {
15198 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);15202 .inherit => break :cwd_w null,
15199 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks15203 .dir => |cwd_dir| {
15200 try Thread.checkCancel();15204 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15201 const dir_path = try windows.GetFinalPathNameByHandle(15205 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
15202 cwd_dir.handle,15206 try Thread.checkCancel();
15203 .{},15207 const dir_path = try windows.GetFinalPathNameByHandle(
15204 dir_path_buffer[0..windows.PATH_MAX_WIDE],15208 cwd_dir.handle,
15205 );15209 .{},
15206 dir_path_buffer[dir_path.len] = 0;15210 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15207 // Shrink the allocation down to just the path buffer + sentinel15211 );
15208 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);15212 dir_path_buffer[dir_path.len] = 0;
15209 break :cwd_w dir_path_buffer[0..dir_path.len :0];15213 // Shrink the allocation down to just the path buffer + sentinel
15210 } else if (options.cwd) |cwd| {15214 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15211 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);15215 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15212 } else {15216 },
15213 break :cwd_w null;15217 .path => |cwd| {
15218 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15219 },
15214 }15220 }
15215 };15221 };
15216 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;15222 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
lib/std/process.zig+2-11
...@@ -368,11 +368,7 @@ pub const SpawnOptions = struct {...@@ -368,11 +368,7 @@ pub const SpawnOptions = struct {
368 argv: []const []const u8,368 argv: []const []const u8,
369369
370 /// Set to change the current working directory when spawning the child process.370 /// Set to change the current working directory when spawning the child process.
371 cwd: ?[]const u8 = null,371 cwd: Child.Cwd = .inherit,
372 /// Set to change the current working directory when spawning the child process.
373 /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
374 /// Once that is done, `cwd` will be deprecated in favor of this field.
375 cwd_dir: ?Io.Dir = null,
376 /// Replaces the child environment when provided. The PATH value from here372 /// Replaces the child environment when provided. The PATH value from here
377 /// is not used to resolve `argv[0]`; that resolution always uses parent373 /// is not used to resolve `argv[0]`; that resolution always uses parent
378 /// environment.374 /// environment.
...@@ -468,11 +464,7 @@ pub const RunOptions = struct {...@@ -468,11 +464,7 @@ pub const RunOptions = struct {
468 reserve_amount: usize = 64,464 reserve_amount: usize = 64,
469465
470 /// Set to change the current working directory when spawning the child process.466 /// Set to change the current working directory when spawning the child process.
471 cwd: ?[]const u8 = null,467 cwd: Child.Cwd = .inherit,
472 /// Set to change the current working directory when spawning the child process.
473 /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
474 /// Once that is done, `cwd` will be deprecated in favor of this field.
475 cwd_dir: ?Io.Dir = null,
476 /// Replaces the child environment when provided. The PATH value from here468 /// Replaces the child environment when provided. The PATH value from here
477 /// is not used to resolve `argv[0]`; that resolution always uses parent469 /// is not used to resolve `argv[0]`; that resolution always uses parent
478 /// environment.470 /// environment.
...@@ -506,7 +498,6 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {...@@ -506,7 +498,6 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
506 var child = try spawn(io, .{498 var child = try spawn(io, .{
507 .argv = options.argv,499 .argv = options.argv,
508 .cwd = options.cwd,500 .cwd = options.cwd,
509 .cwd_dir = options.cwd_dir,
510 .environ_map = options.environ_map,501 .environ_map = options.environ_map,
511 .expand_arg0 = options.expand_arg0,502 .expand_arg0 = options.expand_arg0,
512 .progress_node = options.progress_node,503 .progress_node = options.progress_node,
lib/std/process/Child.zig+11
...@@ -98,6 +98,17 @@ pub const Term = union(enum) {...@@ -98,6 +98,17 @@ pub const Term = union(enum) {
98 unknown: u32,98 unknown: u32,
99};99};
100100
101pub const Cwd = union(enum) {
102 /// CWD of the child is the same as the current CWD.
103 inherit,
104 /// On POSIX systems, `fchdir` is called after `fork` using this handle.
105 /// On Windows, the path is inferred from the provided handle and that path is used when calling `CreateProcessW`.
106 dir: Io.Dir,
107 /// On POSIX systems, `chdir` is called after `fork` using this path.
108 /// On Windows, this path is used when calling `CreateProcessW`.
109 path: []const u8,
110};
111
101/// Requests for the operating system to forcibly terminate the child process,112/// Requests for the operating system to forcibly terminate the child process,
102/// then blocks until it terminates, then cleans up all resources.113/// then blocks until it terminates, then cleans up all resources.
103///114///
test/standalone/windows_paths/test.zig+20-22
...@@ -32,39 +32,39 @@ pub fn main(init: std.process.Init) !void {...@@ -32,39 +32,39 @@ pub fn main(init: std.process.Init) !void {
3232
33 // With the special =X: environment variable set, drive-relative paths that33 // With the special =X: environment variable set, drive-relative paths that
34 // don't match the CWD's drive letter are resolved against that env var.34 // don't match the CWD's drive letter are resolved against that env var.
35 try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map);35 try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, &alt_drive_env_map);
36 try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map);36 try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, &alt_drive_env_map);
3737
38 // Without that environment variable set, drive-relative paths that don't match the38 // Without that environment variable set, drive-relative paths that don't match the
39 // CWD's drive letter are resolved against the root of the drive.39 // CWD's drive letter are resolved against the root of the drive.
40 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);40 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, &empty_env);
41 try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);41 try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, &empty_env);
4242
43 // Bare drive-relative path with no components43 // Bare drive-relative path with no components
44 try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env);44 try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, &empty_env);
45 try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env);45 try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, &empty_env);
4646
47 // Bare drive-relative path with no components, drive-CWD set47 // Bare drive-relative path with no components, drive-CWD set
48 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map);48 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, &alt_drive_env_map);
49 try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map);49 try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, &alt_drive_env_map);
5050
51 // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set51 // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set
52 try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map);52 try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, &alt_drive_env_map);
53 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map);53 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, &alt_drive_env_map);
5454
55 // Bare drive-relative should always be equivalent to itself55 // Bare drive-relative should always be equivalent to itself
56 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);56 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &alt_drive_env_map);
57 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);57 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &alt_drive_env_map);
58 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);58 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &empty_env);
59 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);59 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &empty_env);
60 }60 }
6161
62 if (parsed_cwd_path.kind == .unc_absolute) {62 if (parsed_cwd_path.kind == .unc_absolute) {
63 const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter});63 const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter});
6464
65 {65 {
66 try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env);66 try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, &empty_env);
67 try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env);67 try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, &empty_env);
68 }68 }
69 } else if (parsed_cwd_path.kind == .drive_absolute) {69 } else if (parsed_cwd_path.kind == .drive_absolute) {
70 const cur_drive_letter = parsed_cwd_path.root[0];70 const cur_drive_letter = parsed_cwd_path.root[0];
...@@ -72,14 +72,14 @@ pub fn main(init: std.process.Init) !void {...@@ -72,14 +72,14 @@ pub fn main(init: std.process.Init) !void {
72 const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root });72 const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root });
7373
74 {74 {
75 try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env);75 try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, &empty_env);
76 try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env);76 try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, &empty_env);
77 }77 }
78 {78 {
79 const drive_abs = cwd_path;79 const drive_abs = cwd_path;
80 const drive_rel = parsed_cwd_path.root[0..2];80 const drive_rel = parsed_cwd_path.root[0..2];
81 try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);81 try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, &empty_env);
82 try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);82 try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, &empty_env);
83 }83 }
84 } else {84 } else {
85 return error.UnexpectedPathType;85 return error.UnexpectedPathType;
...@@ -91,12 +91,10 @@ fn checkRelative(...@@ -91,12 +91,10 @@ fn checkRelative(
91 io: Io,91 io: Io,
92 expected_stdout: []const u8,92 expected_stdout: []const u8,
93 argv: []const []const u8,93 argv: []const []const u8,
94 cwd: ?[]const u8,
95 environ_map: ?*const std.process.Environ.Map,94 environ_map: ?*const std.process.Environ.Map,
96) !void {95) !void {
97 const result = try std.process.run(allocator, io, .{96 const result = try std.process.run(allocator, io, .{
98 .argv = argv,97 .argv = argv,
99 .cwd = cwd,
100 .environ_map = environ_map,98 .environ_map = environ_map,
101 });99 });
102 defer allocator.free(result.stdout);100 defer allocator.free(result.stdout);
test/standalone/windows_spawn/main.zig+12-2
...@@ -207,10 +207,20 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !vo...@@ -207,10 +207,20 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !vo
207}207}
208208
209fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void {209fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void {
210 return testExecWithCwd(gpa, io, command, null, expected_stdout);210 return testExecWithCwdInner(gpa, io, command, .inherit, expected_stdout);
211}211}
212212
213fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {213fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: []const u8, expected_stdout: []const u8) !void {
214 // Test by passing CWD as both a path and a Dir
215 try testExecWithCwdInner(gpa, io, command, .{ .path = cwd }, expected_stdout);
216
217 var cwd_dir = try Io.Dir.cwd().openDir(io, cwd, .{});
218 defer cwd_dir.close(io);
219
220 try testExecWithCwdInner(gpa, io, command, .{ .dir = cwd_dir }, expected_stdout);
221}
222
223fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.process.Child.Cwd, expected_stdout: []const u8) !void {
214 const result = try std.process.run(gpa, io, .{224 const result = try std.process.run(gpa, io, .{
215 .argv = &[_][]const u8{command},225 .argv = &[_][]const u8{command},
216 .cwd = cwd,226 .cwd = cwd,
tools/doctest.zig+6-6
...@@ -199,7 +199,7 @@ fn printOutput(...@@ -199,7 +199,7 @@ fn printOutput(
199 if (expected_outcome == .build_fail) {199 if (expected_outcome == .build_fail) {
200 const result = try process.run(arena, io, .{200 const result = try process.run(arena, io, .{
201 .argv = build_args.items,201 .argv = build_args.items,
202 .cwd = tmp_dir_path,202 .cwd = .{ .path = tmp_dir_path },
203 .environ_map = environ_map,203 .environ_map = environ_map,
204 });204 });
205 switch (result.term) {205 switch (result.term) {
...@@ -255,7 +255,7 @@ fn printOutput(...@@ -255,7 +255,7 @@ fn printOutput(
255 const result = try process.run(arena, io, .{255 const result = try process.run(arena, io, .{
256 .argv = run_args,256 .argv = run_args,
257 .environ_map = environ_map,257 .environ_map = environ_map,
258 .cwd = tmp_dir_path,258 .cwd = .{ .path = tmp_dir_path },
259 });259 });
260 switch (result.term) {260 switch (result.term) {
261 .exited => |exit_code| {261 .exited => |exit_code| {
...@@ -373,7 +373,7 @@ fn printOutput(...@@ -373,7 +373,7 @@ fn printOutput(
373 const result = try process.run(arena, io, .{373 const result = try process.run(arena, io, .{
374 .argv = test_args.items,374 .argv = test_args.items,
375 .environ_map = environ_map,375 .environ_map = environ_map,
376 .cwd = tmp_dir_path,376 .cwd = .{ .path = tmp_dir_path },
377 });377 });
378 switch (result.term) {378 switch (result.term) {
379 .exited => |exit_code| {379 .exited => |exit_code| {
...@@ -428,7 +428,7 @@ fn printOutput(...@@ -428,7 +428,7 @@ fn printOutput(
428 const result = try process.run(arena, io, .{428 const result = try process.run(arena, io, .{
429 .argv = test_args.items,429 .argv = test_args.items,
430 .environ_map = environ_map,430 .environ_map = environ_map,
431 .cwd = tmp_dir_path,431 .cwd = .{ .path = tmp_dir_path },
432 });432 });
433 switch (result.term) {433 switch (result.term) {
434 .exited => |exit_code| {434 .exited => |exit_code| {
...@@ -503,7 +503,7 @@ fn printOutput(...@@ -503,7 +503,7 @@ fn printOutput(
503 const result = try process.run(arena, io, .{503 const result = try process.run(arena, io, .{
504 .argv = build_args.items,504 .argv = build_args.items,
505 .environ_map = environ_map,505 .environ_map = environ_map,
506 .cwd = tmp_dir_path,506 .cwd = .{ .path = tmp_dir_path },
507 });507 });
508 switch (result.term) {508 switch (result.term) {
509 .exited => |exit_code| {509 .exited => |exit_code| {
...@@ -1126,7 +1126,7 @@ fn run(...@@ -1126,7 +1126,7 @@ fn run(
1126 const result = try process.run(allocator, io, .{1126 const result = try process.run(allocator, io, .{
1127 .argv = args,1127 .argv = args,
1128 .environ_map = environ_map,1128 .environ_map = environ_map,
1129 .cwd = cwd,1129 .cwd = .{ .path = cwd },
1130 });1130 });
1131 switch (result.term) {1131 switch (result.term) {
1132 .exited => |exit_code| {1132 .exited => |exit_code| {
tools/incr-check.zig+3-6
...@@ -202,8 +202,7 @@ pub fn main(init: std.process.Init) !void {...@@ -202,8 +202,7 @@ pub fn main(init: std.process.Init) !void {
202 .stdout = .pipe,202 .stdout = .pipe,
203 .stderr = .pipe,203 .stderr = .pipe,
204 .progress_node = zig_prog_node,204 .progress_node = zig_prog_node,
205 .cwd_dir = tmp_dir,205 .cwd = .{ .path = tmp_dir_path },
206 .cwd = tmp_dir_path,
207 });206 });
208 defer child.kill(io);207 defer child.kill(io);
209208
...@@ -533,8 +532,7 @@ const Eval = struct {...@@ -533,8 +532,7 @@ const Eval = struct {
533532
534 const result = std.process.run(eval.arena, io, .{533 const result = std.process.run(eval.arena, io, .{
535 .argv = argv,534 .argv = argv,
536 .cwd_dir = eval.tmp_dir,535 .cwd = .{ .path = eval.tmp_dir_path },
537 .cwd = eval.tmp_dir_path,
538 }) catch |err| {536 }) catch |err| {
539 if (is_foreign) {537 if (is_foreign) {
540 // Chances are the foreign executor isn't available. Skip this evaluation.538 // Chances are the foreign executor isn't available. Skip this evaluation.
...@@ -626,8 +624,7 @@ const Eval = struct {...@@ -626,8 +624,7 @@ const Eval = struct {
626624
627 const result = std.process.run(eval.arena, eval.io, .{625 const result = std.process.run(eval.arena, eval.io, .{
628 .argv = eval.cc_child_args.items,626 .argv = eval.cc_child_args.items,
629 .cwd_dir = eval.tmp_dir,627 .cwd = .{ .path = eval.tmp_dir_path },
630 .cwd = eval.tmp_dir_path,
631 .progress_node = child_prog_node,628 .progress_node = child_prog_node,
632 }) catch |err| {629 }) catch |err| {
633 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });630 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });