| author | |
| committer | |
| log | d6a1e73142396732be80a7e0757cca1c07551d30 |
| tree | 3b97b816f0bfa9eb1c88c6d202c304b286727dd4 |
| parent | 86a9a9048e28efc8b49d42db12db123822c3450b |
this commit is unfinished. It marks a spot where I wanted to start
moving child process stuff below the std.Io.VTable29 files changed, 2093 insertions(+), 2104 deletions(-)
lib/compiler/build_runner.zig+7-7| ... | @@ -24,7 +24,7 @@ pub const std_options: std.Options = .{ | ... | @@ -24,7 +24,7 @@ pub const std_options: std.Options = .{ |
| 24 | .crypto_fork_safety = false, | 24 | .crypto_fork_safety = false, |
| 25 | }; | 25 | }; |
| 26 | 26 | ||
| 27 | pub fn main() !void { | 27 | pub fn main(init: process.Init.Minimal) !void { |
| 28 | // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not | 28 | // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not |
| 29 | // always the case. So, we do need a true gpa for some things. | 29 | // always the case. So, we do need a true gpa for some things. |
| 30 | var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init; | 30 | var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init; |
| ... | @@ -37,7 +37,7 @@ pub fn main() !void { | ... | @@ -37,7 +37,7 @@ pub fn main() !void { |
| 37 | var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() }; | 37 | var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() }; |
| 38 | const arena = thread_safe_arena.allocator(); | 38 | const arena = thread_safe_arena.allocator(); |
| 39 | 39 | ||
| 40 | const args = try process.argsAlloc(arena); | 40 | const args = try init.args.toSlice(arena); |
| 41 | 41 | ||
| 42 | var threaded: std.Io.Threaded = .init(gpa, .{}); | 42 | var threaded: std.Io.Threaded = .init(gpa, .{}); |
| 43 | defer threaded.deinit(); | 43 | defer threaded.deinit(); |
| ... | @@ -83,7 +83,7 @@ pub fn main() !void { | ... | @@ -83,7 +83,7 @@ pub fn main() !void { |
| 83 | .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), | 83 | .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), |
| 84 | }, | 84 | }, |
| 85 | .zig_exe = zig_exe, | 85 | .zig_exe = zig_exe, |
| 86 | .env_map = try process.getEnvMap(arena), | 86 | .env_map = try init.environ.createMap(arena), |
| 87 | .global_cache_root = global_cache_directory, | 87 | .global_cache_root = global_cache_directory, |
| 88 | .zig_lib_directory = zig_lib_directory, | 88 | .zig_lib_directory = zig_lib_directory, |
| 89 | .host = .{ | 89 | .host = .{ |
| ... | @@ -126,13 +126,13 @@ pub fn main() !void { | ... | @@ -126,13 +126,13 @@ pub fn main() !void { |
| 126 | var debounce_interval_ms: u16 = 50; | 126 | var debounce_interval_ms: u16 = 50; |
| 127 | var webui_listen: ?Io.net.IpAddress = null; | 127 | var webui_listen: ?Io.net.IpAddress = null; |
| 128 | 128 | ||
| 129 | if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| { | 129 | if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.env_map)) |str| { |
| 130 | if (std.meta.stringToEnum(ErrorStyle, str)) |style| { | 130 | if (std.meta.stringToEnum(ErrorStyle, str)) |style| { |
| 131 | error_style = style; | 131 | error_style = style; |
| 132 | } | 132 | } |
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| { | 135 | if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.env_map)) |str| { |
| 136 | if (std.meta.stringToEnum(MultilineErrors, str)) |style| { | 136 | if (std.meta.stringToEnum(MultilineErrors, str)) |style| { |
| 137 | multiline_errors = style; | 137 | multiline_errors = style; |
| 138 | } | 138 | } |
| ... | @@ -429,8 +429,8 @@ pub fn main() !void { | ... | @@ -429,8 +429,8 @@ pub fn main() !void { |
| 429 | } | 429 | } |
| 430 | } | 430 | } |
| 431 | 431 | ||
| 432 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); | 432 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.env_map); |
| 433 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); | 433 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.env_map); |
| 434 | 434 | ||
| 435 | graph.stderr_mode = switch (color) { | 435 | graph.stderr_mode = switch (color) { |
| 436 | .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), | 436 | .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), |
lib/std/Build.zig+6-9| ... | @@ -12,7 +12,7 @@ const StringHashMap = std.StringHashMap; | ... | @@ -12,7 +12,7 @@ const StringHashMap = std.StringHashMap; |
| 12 | const Allocator = std.mem.Allocator; | 12 | const Allocator = std.mem.Allocator; |
| 13 | const Target = std.Target; | 13 | const Target = std.Target; |
| 14 | const process = std.process; | 14 | const process = std.process; |
| 15 | const EnvMap = std.process.EnvMap; | 15 | const EnvMap = std.process.Environ.Map; |
| 16 | const File = std.Io.File; | 16 | const File = std.Io.File; |
| 17 | const Sha256 = std.crypto.hash.sha2.Sha256; | 17 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 18 | const ArrayList = std.ArrayList; | 18 | const ArrayList = std.ArrayList; |
| ... | @@ -1840,13 +1840,12 @@ pub fn runAllowFail( | ... | @@ -1840,13 +1840,12 @@ pub fn runAllowFail( |
| 1840 | const io = b.graph.io; | 1840 | const io = b.graph.io; |
| 1841 | 1841 | ||
| 1842 | const max_output_size = 400 * 1024; | 1842 | const max_output_size = 400 * 1024; |
| 1843 | var child = std.process.Child.init(argv, b.allocator); | 1843 | var child = std.process.Child.init(b.allocator, argv, .{ .map = &b.graph.env_map }); |
| 1844 | child.stdin_behavior = .Ignore; | 1844 | child.stdin_behavior = .Ignore; |
| 1845 | child.stdout_behavior = .Pipe; | 1845 | child.stdout_behavior = .Pipe; |
| 1846 | child.stderr_behavior = stderr_behavior; | 1846 | child.stderr_behavior = stderr_behavior; |
| 1847 | child.env_map = &b.graph.env_map; | ||
| 1848 | 1847 | ||
| 1849 | try Step.handleVerbose2(b, null, child.env_map, argv); | 1848 | try Step.handleVerbose2(b, null, child.environ.map, argv); |
| 1850 | try child.spawn(io); | 1849 | try child.spawn(io); |
| 1851 | 1850 | ||
| 1852 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); | 1851 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); |
| ... | @@ -1877,17 +1876,15 @@ pub fn runAllowFail( | ... | @@ -1877,17 +1876,15 @@ pub fn runAllowFail( |
| 1877 | pub fn run(b: *Build, argv: []const []const u8) []u8 { | 1876 | pub fn run(b: *Build, argv: []const []const u8) []u8 { |
| 1878 | if (!process.can_spawn) { | 1877 | if (!process.can_spawn) { |
| 1879 | std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{ | 1878 | std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{ |
| 1880 | try Step.allocPrintCmd(b.allocator, null, argv), | 1879 | try Step.allocPrintCmd(b.allocator, null, null, argv), |
| 1881 | }); | 1880 | }); |
| 1882 | process.exit(1); | 1881 | process.exit(1); |
| 1883 | } | 1882 | } |
| 1884 | 1883 | ||
| 1885 | var code: u8 = undefined; | 1884 | var code: u8 = undefined; |
| 1886 | return b.runAllowFail(argv, &code, .Inherit) catch |err| { | 1885 | return b.runAllowFail(argv, &code, .Inherit) catch |err| { |
| 1887 | const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM"); | 1886 | const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM"); |
| 1888 | std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{ | 1887 | std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd }); |
| 1889 | @errorName(err), printed_cmd, | ||
| 1890 | }); | ||
| 1891 | process.exit(1); | 1888 | process.exit(1); |
| 1892 | }; | 1889 | }; |
| 1893 | } | 1890 | } |
lib/std/Build/Step.zig+19-21| ... | @@ -349,18 +349,20 @@ pub fn captureChildProcess( | ... | @@ -349,18 +349,20 @@ pub fn captureChildProcess( |
| 349 | progress_node: std.Progress.Node, | 349 | progress_node: std.Progress.Node, |
| 350 | argv: []const []const u8, | 350 | argv: []const []const u8, |
| 351 | ) !std.process.Child.RunResult { | 351 | ) !std.process.Child.RunResult { |
| 352 | const arena = s.owner.allocator; | 352 | const graph = s.owner.graph; |
| 353 | const io = s.owner.graph.io; | 353 | const arena = graph.arena; |
| 354 | const io = graph.io; | ||
| 354 | 355 | ||
| 355 | // If an error occurs, it's happened in this command: | 356 | // If an error occurs, it's happened in this command: |
| 356 | assert(s.result_failed_command == null); | 357 | assert(s.result_failed_command == null); |
| 357 | s.result_failed_command = try allocPrintCmd(gpa, null, argv); | 358 | s.result_failed_command = try allocPrintCmd(gpa, null, null, argv); |
| 358 | 359 | ||
| 359 | try handleChildProcUnsupported(s); | 360 | try handleChildProcUnsupported(s); |
| 360 | try handleVerbose(s.owner, null, argv); | 361 | try handleVerbose(s.owner, null, argv); |
| 361 | 362 | ||
| 362 | const result = std.process.Child.run(arena, io, .{ | 363 | const result = std.process.Child.run(arena, io, .{ |
| 363 | .argv = argv, | 364 | .argv = argv, |
| 365 | .environ = .{ .map = &graph.env_map }, | ||
| 364 | .progress_node = progress_node, | 366 | .progress_node = progress_node, |
| 365 | }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); | 367 | }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); |
| 366 | 368 | ||
| ... | @@ -406,7 +408,7 @@ pub fn evalZigProcess( | ... | @@ -406,7 +408,7 @@ pub fn evalZigProcess( |
| 406 | 408 | ||
| 407 | // If an error occurs, it's happened in this command: | 409 | // If an error occurs, it's happened in this command: |
| 408 | assert(s.result_failed_command == null); | 410 | assert(s.result_failed_command == null); |
| 409 | s.result_failed_command = try allocPrintCmd(gpa, null, argv); | 411 | s.result_failed_command = try allocPrintCmd(gpa, null, null, argv); |
| 410 | 412 | ||
| 411 | if (s.getZigProcess()) |zp| update: { | 413 | if (s.getZigProcess()) |zp| update: { |
| 412 | assert(watch); | 414 | assert(watch); |
| ... | @@ -447,8 +449,7 @@ pub fn evalZigProcess( | ... | @@ -447,8 +449,7 @@ pub fn evalZigProcess( |
| 447 | try handleChildProcUnsupported(s); | 449 | try handleChildProcUnsupported(s); |
| 448 | try handleVerbose(s.owner, null, argv); | 450 | try handleVerbose(s.owner, null, argv); |
| 449 | 451 | ||
| 450 | var child = std.process.Child.init(argv, arena); | 452 | var child = std.process.Child.init(arena, argv, .{ .map = &b.graph.env_map }); |
| 451 | child.env_map = &b.graph.env_map; | ||
| 452 | child.stdin_behavior = .Pipe; | 453 | child.stdin_behavior = .Pipe; |
| 453 | child.stdout_behavior = .Pipe; | 454 | child.stdout_behavior = .Pipe; |
| 454 | child.stderr_behavior = .Pipe; | 455 | child.stderr_behavior = .Pipe; |
| ... | @@ -692,13 +693,17 @@ pub fn handleVerbose( | ... | @@ -692,13 +693,17 @@ pub fn handleVerbose( |
| 692 | pub fn handleVerbose2( | 693 | pub fn handleVerbose2( |
| 693 | b: *Build, | 694 | b: *Build, |
| 694 | opt_cwd: ?[]const u8, | 695 | opt_cwd: ?[]const u8, |
| 695 | opt_env: ?*const std.process.EnvMap, | 696 | opt_env: ?*const std.process.Environ.Map, |
| 696 | argv: []const []const u8, | 697 | argv: []const []const u8, |
| 697 | ) error{OutOfMemory}!void { | 698 | ) error{OutOfMemory}!void { |
| 698 | if (b.verbose) { | 699 | if (b.verbose) { |
| 700 | const graph = b.graph; | ||
| 699 | // Intention of verbose is to print all sub-process command lines to | 701 | // Intention of verbose is to print all sub-process command lines to |
| 700 | // stderr before spawning them. | 702 | // stderr before spawning them. |
| 701 | const text = try allocPrintCmd2(b.allocator, opt_cwd, opt_env, argv); | 703 | const text = try allocPrintCmd(b.allocator, opt_cwd, if (opt_env) |env| .{ |
| 704 | .child = env, | ||
| 705 | .parent = &graph.env_map, | ||
| 706 | } else null, argv); | ||
| 702 | std.debug.print("{s}\n", .{text}); | 707 | std.debug.print("{s}\n", .{text}); |
| 703 | } | 708 | } |
| 704 | } | 709 | } |
| ... | @@ -728,15 +733,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak | ... | @@ -728,15 +733,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak |
| 728 | pub fn allocPrintCmd( | 733 | pub fn allocPrintCmd( |
| 729 | gpa: Allocator, | 734 | gpa: Allocator, |
| 730 | opt_cwd: ?[]const u8, | 735 | opt_cwd: ?[]const u8, |
| 731 | argv: []const []const u8, | 736 | opt_env: ?struct { |
| 732 | ) Allocator.Error![]u8 { | 737 | child: *const std.process.Environ.Map, |
| 733 | return allocPrintCmd2(gpa, opt_cwd, null, argv); | 738 | parent: *const std.process.Environ.Map, |
| 734 | } | 739 | }, |
| 735 | |||
| 736 | pub fn allocPrintCmd2( | ||
| 737 | gpa: Allocator, | ||
| 738 | opt_cwd: ?[]const u8, | ||
| 739 | opt_env: ?*const std.process.EnvMap, | ||
| 740 | argv: []const []const u8, | 740 | argv: []const []const u8, |
| 741 | ) Allocator.Error![]u8 { | 741 | ) Allocator.Error![]u8 { |
| 742 | const shell = struct { | 742 | const shell = struct { |
| ... | @@ -779,13 +779,11 @@ pub fn allocPrintCmd2( | ... | @@ -779,13 +779,11 @@ pub fn allocPrintCmd2( |
| 779 | const writer = &aw.writer; | 779 | const writer = &aw.writer; |
| 780 | if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory; | 780 | if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory; |
| 781 | if (opt_env) |env| { | 781 | if (opt_env) |env| { |
| 782 | var process_env_map = std.process.getEnvMap(gpa) catch std.process.EnvMap.init(gpa); | 782 | var it = env.child.iterator(); |
| 783 | defer process_env_map.deinit(); | ||
| 784 | var it = env.iterator(); | ||
| 785 | while (it.next()) |entry| { | 783 | while (it.next()) |entry| { |
| 786 | const key = entry.key_ptr.*; | 784 | const key = entry.key_ptr.*; |
| 787 | const value = entry.value_ptr.*; | 785 | const value = entry.value_ptr.*; |
| 788 | if (process_env_map.get(key)) |process_value| { | 786 | if (env.parent.get(key)) |process_value| { |
| 789 | if (std.mem.eql(u8, value, process_value)) continue; | 787 | if (std.mem.eql(u8, value, process_value)) continue; |
| 790 | } | 788 | } |
| 791 | writer.print("{s}=", .{key}) catch return error.OutOfMemory; | 789 | writer.print("{s}=", .{key}) catch return error.OutOfMemory; |
lib/std/Build/Step/Options.zig+1-1| ... | @@ -546,7 +546,7 @@ test Options { | ... | @@ -546,7 +546,7 @@ test Options { |
| 546 | .manifest_dir = Io.Dir.cwd(), | 546 | .manifest_dir = Io.Dir.cwd(), |
| 547 | }, | 547 | }, |
| 548 | .zig_exe = "test", | 548 | .zig_exe = "test", |
| 549 | .env_map = std.process.EnvMap.init(arena.allocator()), | 549 | .env_map = std.process.Environ.Map.init(arena.allocator()), |
| 550 | .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, | 550 | .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, |
| 551 | .host = .{ | 551 | .host = .{ |
| 552 | .query = .{}, | 552 | .query = .{}, |
lib/std/Build/Step/Run.zig+17-14| ... | @@ -8,7 +8,7 @@ const Step = std.Build.Step; | ... | @@ -8,7 +8,7 @@ const Step = std.Build.Step; |
| 8 | const Dir = std.Io.Dir; | 8 | const Dir = std.Io.Dir; |
| 9 | const mem = std.mem; | 9 | const mem = std.mem; |
| 10 | const process = std.process; | 10 | const process = std.process; |
| 11 | const EnvMap = std.process.EnvMap; | 11 | const EnvMap = std.process.Environ.Map; |
| 12 | const assert = std.debug.assert; | 12 | const assert = std.debug.assert; |
| 13 | const Path = std.Build.Cache.Path; | 13 | const Path = std.Build.Cache.Path; |
| 14 | 14 | ||
| ... | @@ -581,23 +581,24 @@ pub fn getEnvMap(run: *Run) *EnvMap { | ... | @@ -581,23 +581,24 @@ pub fn getEnvMap(run: *Run) *EnvMap { |
| 581 | } | 581 | } |
| 582 | 582 | ||
| 583 | fn getEnvMapInternal(run: *Run) *EnvMap { | 583 | fn getEnvMapInternal(run: *Run) *EnvMap { |
| 584 | const arena = run.step.owner.allocator; | 584 | const graph = run.step.owner.graph; |
| 585 | const arena = graph.arena; | ||
| 585 | return run.env_map orelse { | 586 | return run.env_map orelse { |
| 586 | const env_map = arena.create(EnvMap) catch @panic("OOM"); | 587 | const cloned_map = arena.create(EnvMap) catch @panic("OOM"); |
| 587 | env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); | 588 | cloned_map.* = graph.env_map.clone(arena) catch @panic("OOM"); |
| 588 | run.env_map = env_map; | 589 | run.env_map = cloned_map; |
| 589 | return env_map; | 590 | return cloned_map; |
| 590 | }; | 591 | }; |
| 591 | } | 592 | } |
| 592 | 593 | ||
| 593 | pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void { | 594 | pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void { |
| 594 | const b = run.step.owner; | ||
| 595 | const env_map = run.getEnvMap(); | 595 | const env_map = run.getEnvMap(); |
| 596 | env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); | 596 | // This data structure already dupes keys and values. |
| 597 | env_map.put(key, value) catch @panic("OOM"); | ||
| 597 | } | 598 | } |
| 598 | 599 | ||
| 599 | pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { | 600 | pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { |
| 600 | run.getEnvMap().remove(key); | 601 | _ = run.getEnvMap().swapRemove(key); |
| 601 | } | 602 | } |
| 602 | 603 | ||
| 603 | /// Adds a check for exact stderr match. Does not add any other checks. | 604 | /// Adds a check for exact stderr match. Does not add any other checks. |
| ... | @@ -1563,11 +1564,10 @@ fn spawnChildAndCollect( | ... | @@ -1563,11 +1564,10 @@ fn spawnChildAndCollect( |
| 1563 | assert(run.stdio == .zig_test); | 1564 | assert(run.stdio == .zig_test); |
| 1564 | } | 1565 | } |
| 1565 | 1566 | ||
| 1566 | var child = std.process.Child.init(argv, arena); | 1567 | var child = std.process.Child.init(arena, argv, .{ .map = env_map }); |
| 1567 | if (run.cwd) |lazy_cwd| { | 1568 | if (run.cwd) |lazy_cwd| { |
| 1568 | child.cwd = lazy_cwd.getPath2(b, &run.step); | 1569 | child.cwd = lazy_cwd.getPath2(b, &run.step); |
| 1569 | } | 1570 | } |
| 1570 | child.env_map = env_map; | ||
| 1571 | child.request_resource_usage_statistics = true; | 1571 | child.request_resource_usage_statistics = true; |
| 1572 | 1572 | ||
| 1573 | child.stdin_behavior = switch (run.stdio) { | 1573 | child.stdin_behavior = switch (run.stdio) { |
| ... | @@ -1597,7 +1597,10 @@ fn spawnChildAndCollect( | ... | @@ -1597,7 +1597,10 @@ fn spawnChildAndCollect( |
| 1597 | 1597 | ||
| 1598 | // If an error occurs, it's caused by this command: | 1598 | // If an error occurs, it's caused by this command: |
| 1599 | assert(run.step.result_failed_command == null); | 1599 | assert(run.step.result_failed_command == null); |
| 1600 | run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, argv); | 1600 | run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, .{ |
| 1601 | .child = env_map, | ||
| 1602 | .parent = &graph.env_map, | ||
| 1603 | }, argv); | ||
| 1601 | 1604 | ||
| 1602 | if (run.stdio == .zig_test) { | 1605 | if (run.stdio == .zig_test) { |
| 1603 | var timer = try std.time.Timer.start(); | 1606 | var timer = try std.time.Timer.start(); |
| ... | @@ -1627,11 +1630,11 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.T | ... | @@ -1627,11 +1630,11 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.T |
| 1627 | .manual => {}, | 1630 | .manual => {}, |
| 1628 | .enable => { | 1631 | .enable => { |
| 1629 | try env_map.put("CLICOLOR_FORCE", "1"); | 1632 | try env_map.put("CLICOLOR_FORCE", "1"); |
| 1630 | env_map.remove("NO_COLOR"); | 1633 | _ = env_map.swapRemove("NO_COLOR"); |
| 1631 | }, | 1634 | }, |
| 1632 | .disable => { | 1635 | .disable => { |
| 1633 | try env_map.put("NO_COLOR", "1"); | 1636 | try env_map.put("NO_COLOR", "1"); |
| 1634 | env_map.remove("CLICOLOR_FORCE"); | 1637 | _ = env_map.swapRemove("CLICOLOR_FORCE"); |
| 1635 | }, | 1638 | }, |
| 1636 | .inherit => switch (terminal_mode) { | 1639 | .inherit => switch (terminal_mode) { |
| 1637 | .no_color, .windows_api => continue :color .disable, | 1640 | .no_color, .windows_api => continue :color .disable, |
lib/std/Build/WebServer.zig+6-6| ... | @@ -528,13 +528,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons | ... | @@ -528,13 +528,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons |
| 528 | } | 528 | } |
| 529 | 529 | ||
| 530 | fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { | 530 | fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { |
| 531 | const io = ws.graph.io; | ||
| 532 | const root_name = "build-web"; | 531 | const root_name = "build-web"; |
| 533 | const arch_os_abi = "wasm32-freestanding"; | 532 | const arch_os_abi = "wasm32-freestanding"; |
| 534 | const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; | 533 | const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; |
| 535 | 534 | ||
| 536 | const gpa = ws.gpa; | 535 | const gpa = ws.gpa; |
| 537 | const graph = ws.graph; | 536 | const graph = ws.graph; |
| 537 | const io = graph.io; | ||
| 538 | 538 | ||
| 539 | const main_src_path: Cache.Path = .{ | 539 | const main_src_path: Cache.Path = .{ |
| 540 | .root_dir = graph.zig_lib_directory, | 540 | .root_dir = graph.zig_lib_directory, |
| ... | @@ -572,7 +572,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim | ... | @@ -572,7 +572,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 572 | "--listen=-", | 572 | "--listen=-", |
| 573 | }); | 573 | }); |
| 574 | 574 | ||
| 575 | var child: std.process.Child = .init(argv.items, gpa); | 575 | var child: std.process.Child = .init(gpa, argv.items, .{ .map = &graph.env_map }); |
| 576 | child.stdin_behavior = .Pipe; | 576 | child.stdin_behavior = .Pipe; |
| 577 | child.stdout_behavior = .Pipe; | 577 | child.stdout_behavior = .Pipe; |
| 578 | child.stderr_behavior = .Pipe; | 578 | child.stderr_behavior = .Pipe; |
| ... | @@ -640,7 +640,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim | ... | @@ -640,7 +640,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 640 | if (code != 0) { | 640 | if (code != 0) { |
| 641 | log.err( | 641 | log.err( |
| 642 | "the following command exited with error code {d}:\n{s}", | 642 | "the following command exited with error code {d}:\n{s}", |
| 643 | .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) }, | 643 | .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) }, |
| 644 | ); | 644 | ); |
| 645 | return error.WasmCompilationFailed; | 645 | return error.WasmCompilationFailed; |
| 646 | } | 646 | } |
| ... | @@ -648,7 +648,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim | ... | @@ -648,7 +648,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 648 | .Signal, .Stopped, .Unknown => { | 648 | .Signal, .Stopped, .Unknown => { |
| 649 | log.err( | 649 | log.err( |
| 650 | "the following command terminated unexpectedly:\n{s}", | 650 | "the following command terminated unexpectedly:\n{s}", |
| 651 | .{try Build.Step.allocPrintCmd(arena, null, argv.items)}, | 651 | .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)}, |
| 652 | ); | 652 | ); |
| 653 | return error.WasmCompilationFailed; | 653 | return error.WasmCompilationFailed; |
| 654 | }, | 654 | }, |
| ... | @@ -658,14 +658,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim | ... | @@ -658,14 +658,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 658 | try result_error_bundle.renderToStderr(io, .{}, .auto); | 658 | try result_error_bundle.renderToStderr(io, .{}, .auto); |
| 659 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ | 659 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ |
| 660 | result_error_bundle.errorMessageCount(), | 660 | result_error_bundle.errorMessageCount(), |
| 661 | try Build.Step.allocPrintCmd(arena, null, argv.items), | 661 | try Build.Step.allocPrintCmd(arena, null, null, argv.items), |
| 662 | }); | 662 | }); |
| 663 | return error.WasmCompilationFailed; | 663 | return error.WasmCompilationFailed; |
| 664 | } | 664 | } |
| 665 | 665 | ||
| 666 | const base_path = result orelse { | 666 | const base_path = result orelse { |
| 667 | log.err("child process failed to report result\n{s}", .{ | 667 | log.err("child process failed to report result\n{s}", .{ |
| 668 | try Build.Step.allocPrintCmd(arena, null, argv.items), | 668 | try Build.Step.allocPrintCmd(arena, null, null, argv.items), |
| 669 | }); | 669 | }); |
| 670 | return error.WasmCompilationFailed; | 670 | return error.WasmCompilationFailed; |
| 671 | }; | 671 | }; |
lib/std/Io.zig+6| ... | @@ -2232,3 +2232,9 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel | ... | @@ -2232,3 +2232,9 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel |
| 2232 | pub fn unlockStderr(io: Io) void { | 2232 | pub fn unlockStderr(io: Io) void { |
| 2233 | return io.vtable.unlockStderr(io.userdata); | 2233 | return io.vtable.unlockStderr(io.userdata); |
| 2234 | } | 2234 | } |
| 2235 | |||
| 2236 | pub fn environ(io: Io, name: []const u8) ?[]const u8 { | ||
| 2237 | _ = io; | ||
| 2238 | _ = name; | ||
| 2239 | if (true) @panic("TODO"); | ||
| 2240 | } |
lib/std/Io/Dir.zig+4-3| ... | @@ -82,13 +82,14 @@ pub const Entry = struct { | ... | @@ -82,13 +82,14 @@ pub const Entry = struct { |
| 82 | /// | 82 | /// |
| 83 | /// On POSIX targets, this function is comptime-callable. | 83 | /// On POSIX targets, this function is comptime-callable. |
| 84 | /// | 84 | /// |
| 85 | /// On WASI, the value this returns is application-configurable. | 85 | /// This function is overridable via `std.Options.cwd`. |
| 86 | pub fn cwd() Dir { | 86 | pub fn cwd() Dir { |
| 87 | return switch (native_os) { | 87 | const cwdFn = std.Options.cwd orelse return switch (native_os) { |
| 88 | .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, | 88 | .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, |
| 89 | .wasi => .{ .handle = std.options.wasiCwd() }, | 89 | .wasi => .{ .handle = 3 }, // Expect the first preopen to be current working directory. |
| 90 | else => .{ .handle = std.posix.AT.FDCWD }, | 90 | else => .{ .handle = std.posix.AT.FDCWD }, |
| 91 | }; | 91 | }; |
| 92 | return cwdFn(); | ||
| 92 | } | 93 | } |
| 93 | 94 | ||
| 94 | pub const Reader = struct { | 95 | pub const Reader = struct { |
lib/std/Io/Threaded.zig+1-3| ... | @@ -72,7 +72,7 @@ pub const Argv0 = switch (native_os) { | ... | @@ -72,7 +72,7 @@ pub const Argv0 = switch (native_os) { |
| 72 | 72 | ||
| 73 | pub const Environ = struct { | 73 | pub const Environ = struct { |
| 74 | /// Unmodified data directly from the OS. | 74 | /// Unmodified data directly from the OS. |
| 75 | block: Block = &.{}, | 75 | block: std.process.Environ.Block = &.{}, |
| 76 | /// Protected by `mutex`. Determines whether the other fields have been | 76 | /// Protected by `mutex`. Determines whether the other fields have been |
| 77 | /// memoized based on `block`. | 77 | /// memoized based on `block`. |
| 78 | initialized: bool = false, | 78 | initialized: bool = false, |
| ... | @@ -89,8 +89,6 @@ pub const Environ = struct { | ... | @@ -89,8 +89,6 @@ pub const Environ = struct { |
| 89 | 89 | ||
| 90 | pub const Error = Allocator.Error || Io.UnexpectedError; | 90 | pub const Error = Allocator.Error || Io.UnexpectedError; |
| 91 | 91 | ||
| 92 | pub const Block = []const [*:0]const u8; | ||
| 93 | |||
| 94 | pub const Exist = struct { | 92 | pub const Exist = struct { |
| 95 | NO_COLOR: bool = false, | 93 | NO_COLOR: bool = false, |
| 96 | CLICOLOR_FORCE: bool = false, | 94 | CLICOLOR_FORCE: bool = false, |
lib/std/Progress.zig+1-1| ... | @@ -476,7 +476,7 @@ pub fn start(io: Io, options: Options) Node { | ... | @@ -476,7 +476,7 @@ pub fn start(io: Io, options: Options) Node { |
| 476 | 476 | ||
| 477 | global_progress.io = io; | 477 | global_progress.io = io; |
| 478 | 478 | ||
| 479 | if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { | 479 | if (std.process.Environ.parseInt(io, "ZIG_PROGRESS", u31, 10)) |ipc_fd| { |
| 480 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ | 480 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ |
| 481 | io, | 481 | io, |
| 482 | @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) { | 482 | @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) { |
lib/std/debug.zig+1-1| ... | @@ -40,7 +40,7 @@ pub const cpu_context = @import("debug/cpu_context.zig"); | ... | @@ -40,7 +40,7 @@ pub const cpu_context = @import("debug/cpu_context.zig"); |
| 40 | /// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; | 40 | /// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; |
| 41 | /// | 41 | /// |
| 42 | /// /// Returns the symbol and source location of the instruction at `address`. | 42 | /// /// Returns the symbol and source location of the instruction at `address`. |
| 43 | /// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol; | 43 | /// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) SelfInfoError!Symbol; |
| 44 | /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. | 44 | /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. |
| 45 | /// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; | 45 | /// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; |
| 46 | /// | 46 | /// |
lib/std/debug/ElfFile.zig+4-4| ... | @@ -66,16 +66,16 @@ pub const DebugInfoSearchPaths = struct { | ... | @@ -66,16 +66,16 @@ pub const DebugInfoSearchPaths = struct { |
| 66 | .exe_dir = null, | 66 | .exe_dir = null, |
| 67 | }; | 67 | }; |
| 68 | 68 | ||
| 69 | pub fn native(exe_path: []const u8) DebugInfoSearchPaths { | 69 | pub fn native(exe_path: []const u8, io: Io) DebugInfoSearchPaths { |
| 70 | return .{ | 70 | return .{ |
| 71 | .debuginfod_client = p: { | 71 | .debuginfod_client = p: { |
| 72 | if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| { | 72 | if (io.environ("DEBUGINFOD_CACHE_PATH")) |p| { |
| 73 | break :p .{ p, "" }; | 73 | break :p .{ p, "" }; |
| 74 | } | 74 | } |
| 75 | if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { | 75 | if (io.environ("XDG_CACHE_HOME")) |cache_path| { |
| 76 | break :p .{ cache_path, "/debuginfod_client" }; | 76 | break :p .{ cache_path, "/debuginfod_client" }; |
| 77 | } | 77 | } |
| 78 | if (std.posix.getenv("HOME")) |home_path| { | 78 | if (io.environ("HOME")) |home_path| { |
| 79 | break :p .{ home_path, "/.cache/debuginfod_client" }; | 79 | break :p .{ home_path, "/.cache/debuginfod_client" }; |
| 80 | } | 80 | } |
| 81 | break :p null; | 81 | break :p null; |
lib/std/debug/SelfInfo/Elf.zig+3-2| ... | @@ -322,11 +322,12 @@ const Module = struct { | ... | @@ -322,11 +322,12 @@ const Module = struct { |
| 322 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); | 322 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); |
| 323 | return if (mod.loaded_elf.?) |*elf| elf else |err| err; | 323 | return if (mod.loaded_elf.?) |*elf| elf else |err| err; |
| 324 | } | 324 | } |
| 325 | |||
| 325 | fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { | 326 | fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { |
| 326 | const load_result = if (mod.name.len > 0) res: { | 327 | const load_result = if (mod.name.len > 0) res: { |
| 327 | var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; | 328 | var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; |
| 328 | defer file.close(io); | 329 | defer file.close(io); |
| 329 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name)); | 330 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name, io)); |
| 330 | } else res: { | 331 | } else res: { |
| 331 | const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { | 332 | const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { |
| 332 | error.OutOfMemory => |e| return e, | 333 | error.OutOfMemory => |e| return e, |
| ... | @@ -335,7 +336,7 @@ const Module = struct { | ... | @@ -335,7 +336,7 @@ const Module = struct { |
| 335 | defer gpa.free(path); | 336 | defer gpa.free(path); |
| 336 | var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; | 337 | var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; |
| 337 | defer file.close(io); | 338 | defer file.close(io); |
| 338 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path)); | 339 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path, io)); |
| 339 | }; | 340 | }; |
| 340 | 341 | ||
| 341 | var elf_file = load_result catch |err| switch (err) { | 342 | var elf_file = load_result catch |err| switch (err) { |
lib/std/os.zig+1-64| ... | @@ -1,27 +1,4 @@ | ... | @@ -1,27 +1,4 @@ |
| 1 | //! This file contains thin wrappers around OS-specific APIs, with these | ||
| 2 | //! specific goals in mind: | ||
| 3 | //! * Convert "errno"-style error codes into Zig errors. | ||
| 4 | //! * When null-terminated byte buffers are required, provide APIs which accept | ||
| 5 | //! slices as well as APIs which accept null-terminated byte buffers. Same goes | ||
| 6 | //! for WTF-16LE encoding. | ||
| 7 | //! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide | ||
| 8 | //! cross platform abstracting. | ||
| 9 | //! * When there exists a corresponding libc function and linking libc, the libc | ||
| 10 | //! implementation is used. Exceptions are made for known buggy areas of libc. | ||
| 11 | //! On Linux libc can be side-stepped by using `std.os.linux` directly. | ||
| 12 | //! * For Windows, this file represents the API that libc would provide for | ||
| 13 | //! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`. | ||
| 14 | |||
| 15 | const root = @import("root"); | ||
| 16 | const std = @import("std.zig"); | ||
| 17 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 18 | const assert = std.debug.assert; | ||
| 19 | const math = std.math; | ||
| 20 | const mem = std.mem; | ||
| 21 | const elf = std.elf; | ||
| 22 | const fs = std.fs; | ||
| 23 | const dl = @import("dynamic_library.zig"); | ||
| 24 | const posix = std.posix; | ||
| 25 | const native_os = builtin.os.tag; | 2 | const native_os = builtin.os.tag; |
| 26 | 3 | ||
| 27 | pub const linux = @import("os/linux.zig"); | 4 | pub const linux = @import("os/linux.zig"); |
| ... | @@ -33,47 +10,7 @@ pub const windows = @import("os/windows.zig"); | ... | @@ -33,47 +10,7 @@ pub const windows = @import("os/windows.zig"); |
| 33 | 10 | ||
| 34 | test { | 11 | test { |
| 35 | _ = linux; | 12 | _ = linux; |
| 36 | if (native_os == .uefi) { | 13 | if (native_os == .uefi) _ = uefi; |
| 37 | _ = uefi; | ||
| 38 | } | ||
| 39 | _ = wasi; | 14 | _ = wasi; |
| 40 | _ = windows; | 15 | _ = windows; |
| 41 | } | 16 | } |
| 42 | |||
| 43 | /// See also `getenv`. Populated by startup code before main(). | ||
| 44 | /// TODO this is a footgun because the value will be undefined when using `zig build-lib`. | ||
| 45 | /// https://github.com/ziglang/zig/issues/4524 | ||
| 46 | pub var environ: [][*:0]u8 = undefined; | ||
| 47 | |||
| 48 | /// Populated by startup code before main(). | ||
| 49 | /// Not available on WASI or Windows without libc. See `std.process.argsAlloc` | ||
| 50 | /// or `std.process.argsWithAllocator` for a cross-platform alternative. | ||
| 51 | pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) { | ||
| 52 | .windows => @compileError("argv isn't supported on Windows: use std.process.argsAlloc instead"), | ||
| 53 | .wasi => @compileError("argv isn't supported on WASI: use std.process.argsAlloc instead"), | ||
| 54 | else => undefined, | ||
| 55 | }; | ||
| 56 | |||
| 57 | pub const FstatError = error{ | ||
| 58 | SystemResources, | ||
| 59 | AccessDenied, | ||
| 60 | Unexpected, | ||
| 61 | }; | ||
| 62 | |||
| 63 | pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t { | ||
| 64 | var stat: wasi.filestat_t = undefined; | ||
| 65 | switch (wasi.fd_filestat_get(fd, &stat)) { | ||
| 66 | .SUCCESS => return stat, | ||
| 67 | .INVAL => unreachable, | ||
| 68 | .BADF => unreachable, // Always a race condition. | ||
| 69 | .NOMEM => return error.SystemResources, | ||
| 70 | .ACCES => return error.AccessDenied, | ||
| 71 | .NOTCAPABLE => return error.AccessDenied, | ||
| 72 | else => |err| return posix.unexpectedErrno(err), | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | pub fn defaultWasiCwd() std.os.wasi.fd_t { | ||
| 77 | // Expect the first preopen to be current working directory. | ||
| 78 | return 3; | ||
| 79 | } |
lib/std/posix.zig+3-115| ... | @@ -857,127 +857,15 @@ pub fn execveZ( | ... | @@ -857,127 +857,15 @@ pub fn execveZ( |
| 857 | } | 857 | } |
| 858 | } | 858 | } |
| 859 | 859 | ||
| 860 | pub const Arg0Expand = enum { | ||
| 861 | expand, | ||
| 862 | no_expand, | ||
| 863 | }; | ||
| 864 | |||
| 865 | /// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, | ||
| 866 | /// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. | ||
| 867 | /// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. | ||
| 868 | pub fn execvpeZ_expandArg0( | ||
| 869 | comptime arg0_expand: Arg0Expand, | ||
| 870 | file: [*:0]const u8, | ||
| 871 | child_argv: switch (arg0_expand) { | ||
| 872 | .expand => [*:null]?[*:0]const u8, | ||
| 873 | .no_expand => [*:null]const ?[*:0]const u8, | ||
| 874 | }, | ||
| 875 | envp: [*:null]const ?[*:0]const u8, | ||
| 876 | ) ExecveError { | ||
| 877 | const file_slice = mem.sliceTo(file, 0); | ||
| 878 | if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); | ||
| 879 | |||
| 880 | const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; | ||
| 881 | // Use of PATH_MAX here is valid as the path_buf will be passed | ||
| 882 | // directly to the operating system in execveZ. | ||
| 883 | var path_buf: [PATH_MAX]u8 = undefined; | ||
| 884 | var it = mem.tokenizeScalar(u8, PATH, ':'); | ||
| 885 | var seen_eacces = false; | ||
| 886 | var err: ExecveError = error.FileNotFound; | ||
| 887 | |||
| 888 | // In case of expanding arg0 we must put it back if we return with an error. | ||
| 889 | const prev_arg0 = child_argv[0]; | ||
| 890 | defer switch (arg0_expand) { | ||
| 891 | .expand => child_argv[0] = prev_arg0, | ||
| 892 | .no_expand => {}, | ||
| 893 | }; | ||
| 894 | |||
| 895 | while (it.next()) |search_path| { | ||
| 896 | const path_len = search_path.len + file_slice.len + 1; | ||
| 897 | if (path_buf.len < path_len + 1) return error.NameTooLong; | ||
| 898 | @memcpy(path_buf[0..search_path.len], search_path); | ||
| 899 | path_buf[search_path.len] = '/'; | ||
| 900 | @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); | ||
| 901 | path_buf[path_len] = 0; | ||
| 902 | const full_path = path_buf[0..path_len :0].ptr; | ||
| 903 | switch (arg0_expand) { | ||
| 904 | .expand => child_argv[0] = full_path, | ||
| 905 | .no_expand => {}, | ||
| 906 | } | ||
| 907 | err = execveZ(full_path, child_argv, envp); | ||
| 908 | switch (err) { | ||
| 909 | error.AccessDenied => seen_eacces = true, | ||
| 910 | error.FileNotFound, error.NotDir => {}, | ||
| 911 | else => |e| return e, | ||
| 912 | } | ||
| 913 | } | ||
| 914 | if (seen_eacces) return error.AccessDenied; | ||
| 915 | return err; | ||
| 916 | } | ||
| 917 | |||
| 918 | /// This function also uses the PATH environment variable to get the full path to the executable. | 860 | /// This function also uses the PATH environment variable to get the full path to the executable. |
| 919 | /// If `file` is an absolute path, this is the same as `execveZ`. | 861 | /// If `file` is an absolute path, this is the same as `execveZ`. |
| 920 | pub fn execvpeZ( | 862 | pub fn execvpeZ( |
| 921 | file: [*:0]const u8, | 863 | file: [*:0]const u8, |
| 922 | argv_ptr: [*:null]const ?[*:0]const u8, | 864 | argv_ptr: [*:null]const ?[*:0]const u8, |
| 923 | envp: [*:null]const ?[*:0]const u8, | 865 | envp: [*:null]const ?[*:0]const u8, |
| 866 | optional_PATH: ?[]const u8, | ||
| 924 | ) ExecveError { | 867 | ) ExecveError { |
| 925 | return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp); | 868 | return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH); |
| 926 | } | ||
| 927 | |||
| 928 | /// Get an environment variable. | ||
| 929 | /// See also `getenvZ`. | ||
| 930 | pub fn getenv(key: []const u8) ?[:0]const u8 { | ||
| 931 | if (native_os == .windows) { | ||
| 932 | @compileError("std.posix.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API."); | ||
| 933 | } | ||
| 934 | if (mem.findScalar(u8, key, '=') != null) { | ||
| 935 | return null; | ||
| 936 | } | ||
| 937 | if (builtin.link_libc) { | ||
| 938 | var ptr = std.c.environ; | ||
| 939 | while (ptr[0]) |line| : (ptr += 1) { | ||
| 940 | var line_i: usize = 0; | ||
| 941 | while (line[line_i] != 0) : (line_i += 1) { | ||
| 942 | if (line_i == key.len) break; | ||
| 943 | if (line[line_i] != key[line_i]) break; | ||
| 944 | } | ||
| 945 | if ((line_i != key.len) or (line[line_i] != '=')) continue; | ||
| 946 | |||
| 947 | return mem.sliceTo(line + line_i + 1, 0); | ||
| 948 | } | ||
| 949 | return null; | ||
| 950 | } | ||
| 951 | if (native_os == .wasi) { | ||
| 952 | @compileError("std.posix.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API."); | ||
| 953 | } | ||
| 954 | // The simplified start logic doesn't populate environ. | ||
| 955 | if (std.start.simplified_logic) return null; | ||
| 956 | // TODO see https://github.com/ziglang/zig/issues/4524 | ||
| 957 | for (std.os.environ) |ptr| { | ||
| 958 | var line_i: usize = 0; | ||
| 959 | while (ptr[line_i] != 0) : (line_i += 1) { | ||
| 960 | if (line_i == key.len) break; | ||
| 961 | if (ptr[line_i] != key[line_i]) break; | ||
| 962 | } | ||
| 963 | if ((line_i != key.len) or (ptr[line_i] != '=')) continue; | ||
| 964 | |||
| 965 | return mem.sliceTo(ptr + line_i + 1, 0); | ||
| 966 | } | ||
| 967 | return null; | ||
| 968 | } | ||
| 969 | |||
| 970 | /// Get an environment variable with a null-terminated name. | ||
| 971 | /// See also `getenv`. | ||
| 972 | pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 { | ||
| 973 | if (builtin.link_libc) { | ||
| 974 | const value = system.getenv(key) orelse return null; | ||
| 975 | return mem.sliceTo(value, 0); | ||
| 976 | } | ||
| 977 | if (native_os == .windows) { | ||
| 978 | @compileError("std.posix.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API."); | ||
| 979 | } | ||
| 980 | return getenv(mem.sliceTo(key, 0)); | ||
| 981 | } | 869 | } |
| 982 | 870 | ||
| 983 | pub const GetCwdError = error{ | 871 | pub const GetCwdError = error{ |
| ... | @@ -1945,7 +1833,7 @@ pub const FStatError = std.Io.File.StatError; | ... | @@ -1945,7 +1833,7 @@ pub const FStatError = std.Io.File.StatError; |
| 1945 | /// Return information about a file descriptor. | 1833 | /// Return information about a file descriptor. |
| 1946 | pub fn fstat(fd: fd_t) FStatError!Stat { | 1834 | pub fn fstat(fd: fd_t) FStatError!Stat { |
| 1947 | if (native_os == .wasi and !builtin.link_libc) { | 1835 | if (native_os == .wasi and !builtin.link_libc) { |
| 1948 | return Stat.fromFilestat(try std.os.fstat_wasi(fd)); | 1836 | @compileError("unsupported OS"); |
| 1949 | } | 1837 | } |
| 1950 | 1838 | ||
| 1951 | var stat = mem.zeroes(Stat); | 1839 | var stat = mem.zeroes(Stat); |
lib/std/process.zig+154-1740| ... | @@ -16,10 +16,8 @@ const unicode = std.unicode; | ... | @@ -16,10 +16,8 @@ const unicode = std.unicode; |
| 16 | const max_path_bytes = std.fs.max_path_bytes; | 16 | const max_path_bytes = std.fs.max_path_bytes; |
| 17 | 17 | ||
| 18 | pub const Child = @import("process/Child.zig"); | 18 | pub const Child = @import("process/Child.zig"); |
| 19 | pub const changeCurDir = posix.chdir; | 19 | pub const Args = @import("process/Args.zig"); |
| 20 | pub const changeCurDirZ = posix.chdirZ; | 20 | pub const Environ = @import("process/Environ.zig"); |
| 21 | |||
| 22 | pub const GetCwdError = posix.GetCwdError; | ||
| 23 | 21 | ||
| 24 | /// This is the global, process-wide protection to coordinate stderr writes. | 22 | /// This is the global, process-wide protection to coordinate stderr writes. |
| 25 | /// | 23 | /// |
| ... | @@ -28,6 +26,39 @@ pub const GetCwdError = posix.GetCwdError; | ... | @@ -28,6 +26,39 @@ pub const GetCwdError = posix.GetCwdError; |
| 28 | /// information. | 26 | /// information. |
| 29 | pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; | 27 | pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; |
| 30 | 28 | ||
| 29 | /// A standard set of pre-initialized useful APIs for programs to take | ||
| 30 | /// advantage of. This is the type of the first parameter of the main function. | ||
| 31 | /// Applications wanting more flexibility can accept `Init.Minimal` instead. | ||
| 32 | /// | ||
| 33 | /// Completion of https://github.com/ziglang/zig/issues/24510 will also allow | ||
| 34 | /// the second parameter of the main function to be a custom struct that | ||
| 35 | /// contain auto-parsed CLI arguments. | ||
| 36 | pub const Init = struct { | ||
| 37 | /// `Init` is a superset of `Minimal`; the latter is included here. | ||
| 38 | minimal: Minimal, | ||
| 39 | /// Permanent storage for the entire process, cleaned automatically on | ||
| 40 | /// exit. Not threadsafe. | ||
| 41 | arena: *std.heap.ArenaAllocator, | ||
| 42 | /// A default-selected general purpose allocator for temporary heap | ||
| 43 | /// allocations. Debug mode will set up leak checking. Threadsafe. | ||
| 44 | gpa: Allocator, | ||
| 45 | /// An appropriate default Io implementation based on the target | ||
| 46 | /// configuration. Debug mode will set up leak checking. | ||
| 47 | io: Io, | ||
| 48 | /// Environment variables, initialized with `gpa`. Not threadsafe. | ||
| 49 | env_map: *Environ.Map, | ||
| 50 | |||
| 51 | /// Alternative to `Init` as the first parameter of the main function. | ||
| 52 | pub const Minimal = struct { | ||
| 53 | /// Environment variables. | ||
| 54 | environ: Environ, | ||
| 55 | /// Command line arguments. | ||
| 56 | args: Args, | ||
| 57 | }; | ||
| 58 | }; | ||
| 59 | |||
| 60 | pub const GetCwdError = posix.GetCwdError; | ||
| 61 | |||
| 31 | /// The result is a slice of `out_buffer`, from index `0`. | 62 | /// The result is a slice of `out_buffer`, from index `0`. |
| 32 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | 63 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 33 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | 64 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. |
| ... | @@ -73,1484 +104,6 @@ test getCwdAlloc { | ... | @@ -73,1484 +104,6 @@ test getCwdAlloc { |
| 73 | testing.allocator.free(cwd); | 104 | testing.allocator.free(cwd); |
| 74 | } | 105 | } |
| 75 | 106 | ||
| 76 | pub const EnvMap = struct { | ||
| 77 | hash_map: HashMap, | ||
| 78 | |||
| 79 | const HashMap = std.HashMap( | ||
| 80 | []const u8, | ||
| 81 | []const u8, | ||
| 82 | EnvNameHashContext, | ||
| 83 | std.hash_map.default_max_load_percentage, | ||
| 84 | ); | ||
| 85 | |||
| 86 | pub const Size = HashMap.Size; | ||
| 87 | |||
| 88 | pub const EnvNameHashContext = struct { | ||
| 89 | fn upcase(c: u21) u21 { | ||
| 90 | if (c <= std.math.maxInt(u16)) | ||
| 91 | return windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); | ||
| 92 | return c; | ||
| 93 | } | ||
| 94 | |||
| 95 | pub fn hash(self: @This(), s: []const u8) u64 { | ||
| 96 | _ = self; | ||
| 97 | if (native_os == .windows) { | ||
| 98 | var h = std.hash.Wyhash.init(0); | ||
| 99 | var it = unicode.Wtf8View.initUnchecked(s).iterator(); | ||
| 100 | while (it.nextCodepoint()) |cp| { | ||
| 101 | const cp_upper = upcase(cp); | ||
| 102 | h.update(&[_]u8{ | ||
| 103 | @as(u8, @intCast((cp_upper >> 16) & 0xff)), | ||
| 104 | @as(u8, @intCast((cp_upper >> 8) & 0xff)), | ||
| 105 | @as(u8, @intCast((cp_upper >> 0) & 0xff)), | ||
| 106 | }); | ||
| 107 | } | ||
| 108 | return h.final(); | ||
| 109 | } | ||
| 110 | return std.hash_map.hashString(s); | ||
| 111 | } | ||
| 112 | |||
| 113 | pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { | ||
| 114 | _ = self; | ||
| 115 | if (native_os == .windows) { | ||
| 116 | var it_a = unicode.Wtf8View.initUnchecked(a).iterator(); | ||
| 117 | var it_b = unicode.Wtf8View.initUnchecked(b).iterator(); | ||
| 118 | while (true) { | ||
| 119 | const c_a = it_a.nextCodepoint() orelse break; | ||
| 120 | const c_b = it_b.nextCodepoint() orelse return false; | ||
| 121 | if (upcase(c_a) != upcase(c_b)) | ||
| 122 | return false; | ||
| 123 | } | ||
| 124 | return if (it_b.nextCodepoint()) |_| false else true; | ||
| 125 | } | ||
| 126 | return std.hash_map.eqlString(a, b); | ||
| 127 | } | ||
| 128 | }; | ||
| 129 | |||
| 130 | /// Create a EnvMap backed by a specific allocator. | ||
| 131 | /// That allocator will be used for both backing allocations | ||
| 132 | /// and string deduplication. | ||
| 133 | pub fn init(allocator: Allocator) EnvMap { | ||
| 134 | return EnvMap{ .hash_map = HashMap.init(allocator) }; | ||
| 135 | } | ||
| 136 | |||
| 137 | /// Free the backing storage of the map, as well as all | ||
| 138 | /// of the stored keys and values. | ||
| 139 | pub fn deinit(self: *EnvMap) void { | ||
| 140 | var it = self.hash_map.iterator(); | ||
| 141 | while (it.next()) |entry| { | ||
| 142 | self.free(entry.key_ptr.*); | ||
| 143 | self.free(entry.value_ptr.*); | ||
| 144 | } | ||
| 145 | |||
| 146 | self.hash_map.deinit(); | ||
| 147 | } | ||
| 148 | |||
| 149 | /// Same as `put` but the key and value become owned by the EnvMap rather | ||
| 150 | /// than being copied. | ||
| 151 | /// If `putMove` fails, the ownership of key and value does not transfer. | ||
| 152 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 153 | pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void { | ||
| 154 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 155 | const get_or_put = try self.hash_map.getOrPut(key); | ||
| 156 | if (get_or_put.found_existing) { | ||
| 157 | self.free(get_or_put.key_ptr.*); | ||
| 158 | self.free(get_or_put.value_ptr.*); | ||
| 159 | get_or_put.key_ptr.* = key; | ||
| 160 | } | ||
| 161 | get_or_put.value_ptr.* = value; | ||
| 162 | } | ||
| 163 | |||
| 164 | /// `key` and `value` are copied into the EnvMap. | ||
| 165 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 166 | pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void { | ||
| 167 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 168 | const value_copy = try self.copy(value); | ||
| 169 | errdefer self.free(value_copy); | ||
| 170 | const get_or_put = try self.hash_map.getOrPut(key); | ||
| 171 | if (get_or_put.found_existing) { | ||
| 172 | self.free(get_or_put.value_ptr.*); | ||
| 173 | } else { | ||
| 174 | get_or_put.key_ptr.* = self.copy(key) catch |err| { | ||
| 175 | _ = self.hash_map.remove(key); | ||
| 176 | return err; | ||
| 177 | }; | ||
| 178 | } | ||
| 179 | get_or_put.value_ptr.* = value_copy; | ||
| 180 | } | ||
| 181 | |||
| 182 | /// Find the address of the value associated with a key. | ||
| 183 | /// The returned pointer is invalidated if the map resizes. | ||
| 184 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 185 | pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 { | ||
| 186 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 187 | return self.hash_map.getPtr(key); | ||
| 188 | } | ||
| 189 | |||
| 190 | /// Return the map's copy of the value associated with | ||
| 191 | /// a key. The returned string is invalidated if this | ||
| 192 | /// key is removed from the map. | ||
| 193 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 194 | pub fn get(self: EnvMap, key: []const u8) ?[]const u8 { | ||
| 195 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 196 | return self.hash_map.get(key); | ||
| 197 | } | ||
| 198 | |||
| 199 | /// Removes the item from the map and frees its value. | ||
| 200 | /// This invalidates the value returned by get() for this key. | ||
| 201 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 202 | pub fn remove(self: *EnvMap, key: []const u8) void { | ||
| 203 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 204 | const kv = self.hash_map.fetchRemove(key) orelse return; | ||
| 205 | self.free(kv.key); | ||
| 206 | self.free(kv.value); | ||
| 207 | } | ||
| 208 | |||
| 209 | /// Returns the number of KV pairs stored in the map. | ||
| 210 | pub fn count(self: EnvMap) HashMap.Size { | ||
| 211 | return self.hash_map.count(); | ||
| 212 | } | ||
| 213 | |||
| 214 | /// Returns an iterator over entries in the map. | ||
| 215 | pub fn iterator(self: *const EnvMap) HashMap.Iterator { | ||
| 216 | return self.hash_map.iterator(); | ||
| 217 | } | ||
| 218 | |||
| 219 | /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily | ||
| 220 | /// the same allocator used to allocate `em`. | ||
| 221 | pub fn clone(em: *const EnvMap, gpa: Allocator) Allocator.Error!EnvMap { | ||
| 222 | var new: EnvMap = .init(gpa); | ||
| 223 | errdefer new.deinit(); | ||
| 224 | // Since we need to dupe the keys and values, the only way for error handling to not be a | ||
| 225 | // nightmare is to add keys to an empty map one-by-one. This could be avoided if this | ||
| 226 | // abstraction were a bit less... OOP-esque. | ||
| 227 | try new.hash_map.ensureUnusedCapacity(em.hash_map.count()); | ||
| 228 | var it = em.hash_map.iterator(); | ||
| 229 | while (it.next()) |entry| { | ||
| 230 | try new.put(entry.key_ptr.*, entry.value_ptr.*); | ||
| 231 | } | ||
| 232 | return new; | ||
| 233 | } | ||
| 234 | |||
| 235 | fn free(self: EnvMap, value: []const u8) void { | ||
| 236 | self.hash_map.allocator.free(value); | ||
| 237 | } | ||
| 238 | |||
| 239 | fn copy(self: EnvMap, value: []const u8) ![]u8 { | ||
| 240 | return self.hash_map.allocator.dupe(u8, value); | ||
| 241 | } | ||
| 242 | }; | ||
| 243 | |||
| 244 | test EnvMap { | ||
| 245 | var env = EnvMap.init(testing.allocator); | ||
| 246 | defer env.deinit(); | ||
| 247 | |||
| 248 | try env.put("SOMETHING_NEW", "hello"); | ||
| 249 | try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?); | ||
| 250 | try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); | ||
| 251 | |||
| 252 | // overwrite | ||
| 253 | try env.put("SOMETHING_NEW", "something"); | ||
| 254 | try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?); | ||
| 255 | try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); | ||
| 256 | |||
| 257 | // a new longer name to test the Windows-specific conversion buffer | ||
| 258 | try env.put("SOMETHING_NEW_AND_LONGER", "1"); | ||
| 259 | try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?); | ||
| 260 | try testing.expectEqual(@as(EnvMap.Size, 2), env.count()); | ||
| 261 | |||
| 262 | // case insensitivity on Windows only | ||
| 263 | if (native_os == .windows) { | ||
| 264 | try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?); | ||
| 265 | } else { | ||
| 266 | try testing.expect(null == env.get("something_New_aNd_LONGER")); | ||
| 267 | } | ||
| 268 | |||
| 269 | var it = env.iterator(); | ||
| 270 | var count: EnvMap.Size = 0; | ||
| 271 | while (it.next()) |entry| { | ||
| 272 | const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); | ||
| 273 | try testing.expect(is_an_expected_name); | ||
| 274 | count += 1; | ||
| 275 | } | ||
| 276 | try testing.expectEqual(@as(EnvMap.Size, 2), count); | ||
| 277 | |||
| 278 | env.remove("SOMETHING_NEW"); | ||
| 279 | try testing.expect(env.get("SOMETHING_NEW") == null); | ||
| 280 | |||
| 281 | try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); | ||
| 282 | |||
| 283 | if (native_os == .windows) { | ||
| 284 | // test Unicode case-insensitivity on Windows | ||
| 285 | try env.put("КИРиллИЦА", "something else"); | ||
| 286 | try testing.expectEqualStrings("something else", env.get("кириллица").?); | ||
| 287 | |||
| 288 | // and WTF-8 that's not valid UTF-8 | ||
| 289 | const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ | ||
| 290 | std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate | ||
| 291 | }); | ||
| 292 | defer testing.allocator.free(wtf8_with_surrogate_pair); | ||
| 293 | |||
| 294 | try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); | ||
| 295 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | pub const GetEnvMapError = error{ | ||
| 300 | OutOfMemory, | ||
| 301 | /// WASI-only. `environ_sizes_get` or `environ_get` | ||
| 302 | /// failed for an unexpected reason. | ||
| 303 | Unexpected, | ||
| 304 | }; | ||
| 305 | |||
| 306 | /// Returns a snapshot of the environment variables of the current process. | ||
| 307 | /// Any modifications to the resulting EnvMap will not be reflected in the environment, and | ||
| 308 | /// likewise, any future modifications to the environment will not be reflected in the EnvMap. | ||
| 309 | /// Caller owns resulting `EnvMap` and should call its `deinit` fn when done. | ||
| 310 | pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { | ||
| 311 | var result = EnvMap.init(allocator); | ||
| 312 | errdefer result.deinit(); | ||
| 313 | |||
| 314 | if (native_os == .windows) { | ||
| 315 | const ptr = windows.peb().ProcessParameters.Environment; | ||
| 316 | |||
| 317 | var i: usize = 0; | ||
| 318 | while (ptr[i] != 0) { | ||
| 319 | const key_start = i; | ||
| 320 | |||
| 321 | // There are some special environment variables that start with =, | ||
| 322 | // so we need a special case to not treat = as a key/value separator | ||
| 323 | // if it's the first character. | ||
| 324 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | ||
| 325 | if (ptr[key_start] == '=') i += 1; | ||
| 326 | |||
| 327 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} | ||
| 328 | const key_w = ptr[key_start..i]; | ||
| 329 | const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w); | ||
| 330 | errdefer allocator.free(key); | ||
| 331 | |||
| 332 | if (ptr[i] == '=') i += 1; | ||
| 333 | |||
| 334 | const value_start = i; | ||
| 335 | while (ptr[i] != 0) : (i += 1) {} | ||
| 336 | const value_w = ptr[value_start..i]; | ||
| 337 | const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w); | ||
| 338 | errdefer allocator.free(value); | ||
| 339 | |||
| 340 | i += 1; // skip over null byte | ||
| 341 | |||
| 342 | try result.putMove(key, value); | ||
| 343 | } | ||
| 344 | return result; | ||
| 345 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 346 | var environ_count: usize = undefined; | ||
| 347 | var environ_buf_size: usize = undefined; | ||
| 348 | |||
| 349 | const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); | ||
| 350 | if (environ_sizes_get_ret != .SUCCESS) { | ||
| 351 | return posix.unexpectedErrno(environ_sizes_get_ret); | ||
| 352 | } | ||
| 353 | |||
| 354 | if (environ_count == 0) { | ||
| 355 | return result; | ||
| 356 | } | ||
| 357 | |||
| 358 | const environ = try allocator.alloc([*:0]u8, environ_count); | ||
| 359 | defer allocator.free(environ); | ||
| 360 | const environ_buf = try allocator.alloc(u8, environ_buf_size); | ||
| 361 | defer allocator.free(environ_buf); | ||
| 362 | |||
| 363 | const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); | ||
| 364 | if (environ_get_ret != .SUCCESS) { | ||
| 365 | return posix.unexpectedErrno(environ_get_ret); | ||
| 366 | } | ||
| 367 | |||
| 368 | for (environ) |env| { | ||
| 369 | const pair = mem.sliceTo(env, 0); | ||
| 370 | var parts = mem.splitScalar(u8, pair, '='); | ||
| 371 | const key = parts.first(); | ||
| 372 | const value = parts.rest(); | ||
| 373 | try result.put(key, value); | ||
| 374 | } | ||
| 375 | return result; | ||
| 376 | } else if (builtin.link_libc) { | ||
| 377 | var ptr = std.c.environ; | ||
| 378 | while (ptr[0]) |line| : (ptr += 1) { | ||
| 379 | var line_i: usize = 0; | ||
| 380 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | ||
| 381 | const key = line[0..line_i]; | ||
| 382 | |||
| 383 | var end_i: usize = line_i; | ||
| 384 | while (line[end_i] != 0) : (end_i += 1) {} | ||
| 385 | const value = line[line_i + 1 .. end_i]; | ||
| 386 | |||
| 387 | try result.put(key, value); | ||
| 388 | } | ||
| 389 | return result; | ||
| 390 | } else { | ||
| 391 | for (std.os.environ) |line| { | ||
| 392 | var line_i: usize = 0; | ||
| 393 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | ||
| 394 | const key = line[0..line_i]; | ||
| 395 | |||
| 396 | var end_i: usize = line_i; | ||
| 397 | while (line[end_i] != 0) : (end_i += 1) {} | ||
| 398 | const value = line[line_i + 1 .. end_i]; | ||
| 399 | |||
| 400 | try result.put(key, value); | ||
| 401 | } | ||
| 402 | return result; | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | test getEnvMap { | ||
| 407 | var env = try getEnvMap(testing.allocator); | ||
| 408 | defer env.deinit(); | ||
| 409 | } | ||
| 410 | |||
| 411 | pub const GetEnvVarOwnedError = error{ | ||
| 412 | OutOfMemory, | ||
| 413 | EnvironmentVariableNotFound, | ||
| 414 | |||
| 415 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | ||
| 416 | /// https://wtf-8.codeberg.page/ | ||
| 417 | InvalidWtf8, | ||
| 418 | }; | ||
| 419 | |||
| 420 | /// Caller must free returned memory. | ||
| 421 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | ||
| 422 | /// then `error.InvalidWtf8` is returned. | ||
| 423 | /// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 424 | /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. | ||
| 425 | pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { | ||
| 426 | if (native_os == .windows) { | ||
| 427 | const result_w = blk: { | ||
| 428 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | ||
| 429 | const stack_allocator = stack_alloc.get(); | ||
| 430 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | ||
| 431 | defer stack_allocator.free(key_w); | ||
| 432 | |||
| 433 | break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; | ||
| 434 | }; | ||
| 435 | // wtf16LeToWtf8Alloc can only fail with OutOfMemory | ||
| 436 | return unicode.wtf16LeToWtf8Alloc(allocator, result_w); | ||
| 437 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 438 | var envmap = getEnvMap(allocator) catch return error.OutOfMemory; | ||
| 439 | defer envmap.deinit(); | ||
| 440 | const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; | ||
| 441 | return allocator.dupe(u8, val); | ||
| 442 | } else { | ||
| 443 | const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; | ||
| 444 | return allocator.dupe(u8, result); | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | /// On Windows, `key` must be valid WTF-8. | ||
| 449 | pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { | ||
| 450 | if (native_os == .windows) { | ||
| 451 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); | ||
| 452 | return getenvW(key_w) != null; | ||
| 453 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 454 | return false; | ||
| 455 | } else { | ||
| 456 | return posix.getenv(key) != null; | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | /// On Windows, `key` must be valid WTF-8. | ||
| 461 | pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { | ||
| 462 | if (native_os == .windows) { | ||
| 463 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); | ||
| 464 | const value = getenvW(key_w) orelse return false; | ||
| 465 | return value.len != 0; | ||
| 466 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 467 | return false; | ||
| 468 | } else { | ||
| 469 | const value = posix.getenv(key) orelse return false; | ||
| 470 | return value.len != 0; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; | ||
| 475 | |||
| 476 | /// Parses an environment variable as an integer. | ||
| 477 | /// | ||
| 478 | /// Since the key is comptime-known, no allocation is needed. | ||
| 479 | /// | ||
| 480 | /// On Windows, `key` must be valid WTF-8. | ||
| 481 | pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I { | ||
| 482 | if (native_os == .windows) { | ||
| 483 | const key_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(key); | ||
| 484 | const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound; | ||
| 485 | return std.fmt.parseIntWithGenericCharacter(I, u16, text, base); | ||
| 486 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 487 | @compileError("parseEnvVarInt is not supported for WASI without libc"); | ||
| 488 | } else { | ||
| 489 | const text = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; | ||
| 490 | return std.fmt.parseInt(I, text, base); | ||
| 491 | } | ||
| 492 | } | ||
| 493 | |||
| 494 | pub const HasEnvVarError = error{ | ||
| 495 | OutOfMemory, | ||
| 496 | |||
| 497 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | ||
| 498 | /// https://wtf-8.codeberg.page/ | ||
| 499 | InvalidWtf8, | ||
| 500 | }; | ||
| 501 | |||
| 502 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | ||
| 503 | /// then `error.InvalidWtf8` is returned. | ||
| 504 | pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | ||
| 505 | if (native_os == .windows) { | ||
| 506 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | ||
| 507 | const stack_allocator = stack_alloc.get(); | ||
| 508 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | ||
| 509 | defer stack_allocator.free(key_w); | ||
| 510 | return getenvW(key_w) != null; | ||
| 511 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 512 | var envmap = getEnvMap(allocator) catch return error.OutOfMemory; | ||
| 513 | defer envmap.deinit(); | ||
| 514 | return envmap.getPtr(key) != null; | ||
| 515 | } else { | ||
| 516 | return posix.getenv(key) != null; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | ||
| 521 | /// then `error.InvalidWtf8` is returned. | ||
| 522 | pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | ||
| 523 | if (native_os == .windows) { | ||
| 524 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | ||
| 525 | const stack_allocator = stack_alloc.get(); | ||
| 526 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | ||
| 527 | defer stack_allocator.free(key_w); | ||
| 528 | const value = getenvW(key_w) orelse return false; | ||
| 529 | return value.len != 0; | ||
| 530 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 531 | var envmap = getEnvMap(allocator) catch return error.OutOfMemory; | ||
| 532 | defer envmap.deinit(); | ||
| 533 | const value = envmap.getPtr(key) orelse return false; | ||
| 534 | return value.len != 0; | ||
| 535 | } else { | ||
| 536 | const value = posix.getenv(key) orelse return false; | ||
| 537 | return value.len != 0; | ||
| 538 | } | ||
| 539 | } | ||
| 540 | |||
| 541 | /// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. | ||
| 542 | /// The returned slice points to memory in the PEB. | ||
| 543 | /// | ||
| 544 | /// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. | ||
| 545 | /// | ||
| 546 | /// See also: | ||
| 547 | /// * `std.posix.getenv` | ||
| 548 | /// * `getEnvMap` | ||
| 549 | /// * `getEnvVarOwned` | ||
| 550 | /// * `hasEnvVarConstant` | ||
| 551 | /// * `hasEnvVar` | ||
| 552 | pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { | ||
| 553 | if (native_os != .windows) { | ||
| 554 | @compileError("Windows-only"); | ||
| 555 | } | ||
| 556 | const key_slice = mem.sliceTo(key, 0); | ||
| 557 | // '=' anywhere but the start makes this an invalid environment variable name | ||
| 558 | if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { | ||
| 559 | return null; | ||
| 560 | } | ||
| 561 | const ptr = windows.peb().ProcessParameters.Environment; | ||
| 562 | var i: usize = 0; | ||
| 563 | while (ptr[i] != 0) { | ||
| 564 | const key_value = mem.sliceTo(ptr[i..], 0); | ||
| 565 | |||
| 566 | // There are some special environment variables that start with =, | ||
| 567 | // so we need a special case to not treat = as a key/value separator | ||
| 568 | // if it's the first character. | ||
| 569 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | ||
| 570 | const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; | ||
| 571 | const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { | ||
| 572 | // This is enforced by CreateProcess. | ||
| 573 | // If violated, CreateProcess will fail with INVALID_PARAMETER. | ||
| 574 | unreachable; // must contain a = | ||
| 575 | }; | ||
| 576 | |||
| 577 | const this_key = key_value[0..equal_index]; | ||
| 578 | if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) { | ||
| 579 | return key_value[equal_index + 1 ..]; | ||
| 580 | } | ||
| 581 | |||
| 582 | // skip past the NUL terminator | ||
| 583 | i += key_value.len + 1; | ||
| 584 | } | ||
| 585 | return null; | ||
| 586 | } | ||
| 587 | |||
| 588 | test getEnvVarOwned { | ||
| 589 | try testing.expectError( | ||
| 590 | error.EnvironmentVariableNotFound, | ||
| 591 | getEnvVarOwned(std.testing.allocator, "BADENV"), | ||
| 592 | ); | ||
| 593 | } | ||
| 594 | |||
| 595 | test hasEnvVarConstant { | ||
| 596 | if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; | ||
| 597 | |||
| 598 | try testing.expect(!hasEnvVarConstant("BADENV")); | ||
| 599 | } | ||
| 600 | |||
| 601 | test hasEnvVar { | ||
| 602 | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); | ||
| 603 | try testing.expect(!has_env); | ||
| 604 | } | ||
| 605 | |||
| 606 | pub const ArgIteratorPosix = struct { | ||
| 607 | index: usize, | ||
| 608 | count: usize, | ||
| 609 | |||
| 610 | pub const InitError = error{}; | ||
| 611 | |||
| 612 | pub fn init() ArgIteratorPosix { | ||
| 613 | return ArgIteratorPosix{ | ||
| 614 | .index = 0, | ||
| 615 | .count = std.os.argv.len, | ||
| 616 | }; | ||
| 617 | } | ||
| 618 | |||
| 619 | pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 { | ||
| 620 | if (self.index == self.count) return null; | ||
| 621 | |||
| 622 | const s = std.os.argv[self.index]; | ||
| 623 | self.index += 1; | ||
| 624 | return mem.sliceTo(s, 0); | ||
| 625 | } | ||
| 626 | |||
| 627 | pub fn skip(self: *ArgIteratorPosix) bool { | ||
| 628 | if (self.index == self.count) return false; | ||
| 629 | |||
| 630 | self.index += 1; | ||
| 631 | return true; | ||
| 632 | } | ||
| 633 | }; | ||
| 634 | |||
| 635 | pub const ArgIteratorWasi = struct { | ||
| 636 | allocator: Allocator, | ||
| 637 | index: usize, | ||
| 638 | args: [][:0]u8, | ||
| 639 | |||
| 640 | pub const InitError = error{OutOfMemory} || posix.UnexpectedError; | ||
| 641 | |||
| 642 | /// You must call deinit to free the internal buffer of the | ||
| 643 | /// iterator after you are done. | ||
| 644 | pub fn init(allocator: Allocator) InitError!ArgIteratorWasi { | ||
| 645 | const fetched_args = try ArgIteratorWasi.internalInit(allocator); | ||
| 646 | return ArgIteratorWasi{ | ||
| 647 | .allocator = allocator, | ||
| 648 | .index = 0, | ||
| 649 | .args = fetched_args, | ||
| 650 | }; | ||
| 651 | } | ||
| 652 | |||
| 653 | fn internalInit(allocator: Allocator) InitError![][:0]u8 { | ||
| 654 | var count: usize = undefined; | ||
| 655 | var buf_size: usize = undefined; | ||
| 656 | |||
| 657 | switch (std.os.wasi.args_sizes_get(&count, &buf_size)) { | ||
| 658 | .SUCCESS => {}, | ||
| 659 | else => |err| return posix.unexpectedErrno(err), | ||
| 660 | } | ||
| 661 | |||
| 662 | if (count == 0) { | ||
| 663 | return &[_][:0]u8{}; | ||
| 664 | } | ||
| 665 | |||
| 666 | const argv = try allocator.alloc([*:0]u8, count); | ||
| 667 | defer allocator.free(argv); | ||
| 668 | |||
| 669 | const argv_buf = try allocator.alloc(u8, buf_size); | ||
| 670 | |||
| 671 | switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) { | ||
| 672 | .SUCCESS => {}, | ||
| 673 | else => |err| return posix.unexpectedErrno(err), | ||
| 674 | } | ||
| 675 | |||
| 676 | var result_args = try allocator.alloc([:0]u8, count); | ||
| 677 | var i: usize = 0; | ||
| 678 | while (i < count) : (i += 1) { | ||
| 679 | result_args[i] = mem.sliceTo(argv[i], 0); | ||
| 680 | } | ||
| 681 | |||
| 682 | return result_args; | ||
| 683 | } | ||
| 684 | |||
| 685 | pub fn next(self: *ArgIteratorWasi) ?[:0]const u8 { | ||
| 686 | if (self.index == self.args.len) return null; | ||
| 687 | |||
| 688 | const arg = self.args[self.index]; | ||
| 689 | self.index += 1; | ||
| 690 | return arg; | ||
| 691 | } | ||
| 692 | |||
| 693 | pub fn skip(self: *ArgIteratorWasi) bool { | ||
| 694 | if (self.index == self.args.len) return false; | ||
| 695 | |||
| 696 | self.index += 1; | ||
| 697 | return true; | ||
| 698 | } | ||
| 699 | |||
| 700 | /// Call to free the internal buffer of the iterator. | ||
| 701 | pub fn deinit(self: *ArgIteratorWasi) void { | ||
| 702 | // Nothing is allocated when there are no args | ||
| 703 | if (self.args.len == 0) return; | ||
| 704 | |||
| 705 | const last_item = self.args[self.args.len - 1]; | ||
| 706 | const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated | ||
| 707 | const first_item_ptr = self.args[0].ptr; | ||
| 708 | const len = last_byte_addr - @intFromPtr(first_item_ptr); | ||
| 709 | self.allocator.free(first_item_ptr[0..len]); | ||
| 710 | self.allocator.free(self.args); | ||
| 711 | } | ||
| 712 | }; | ||
| 713 | |||
| 714 | /// Iterator that implements the Windows command-line parsing algorithm. | ||
| 715 | /// The implementation is intended to be compatible with the post-2008 C runtime, | ||
| 716 | /// but is *not* intended to be compatible with `CommandLineToArgvW` since | ||
| 717 | /// `CommandLineToArgvW` uses the pre-2008 parsing rules. | ||
| 718 | /// | ||
| 719 | /// This iterator faithfully implements the parsing behavior observed from the C runtime with | ||
| 720 | /// one exception: if the command-line string is empty, the iterator will immediately complete | ||
| 721 | /// without returning any arguments (whereas the C runtime will return a single argument | ||
| 722 | /// representing the name of the current executable). | ||
| 723 | /// | ||
| 724 | /// The essential parts of the algorithm are described in Microsoft's documentation: | ||
| 725 | /// | ||
| 726 | /// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments | ||
| 727 | /// | ||
| 728 | /// David Deley explains some additional undocumented quirks in great detail: | ||
| 729 | /// | ||
| 730 | /// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES | ||
| 731 | pub const ArgIteratorWindows = struct { | ||
| 732 | allocator: Allocator, | ||
| 733 | /// Encoded as WTF-16 LE. | ||
| 734 | cmd_line: []const u16, | ||
| 735 | index: usize = 0, | ||
| 736 | /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices | ||
| 737 | /// of each argument encoded as WTF-8. | ||
| 738 | buffer: []u8, | ||
| 739 | start: usize = 0, | ||
| 740 | end: usize = 0, | ||
| 741 | |||
| 742 | pub const InitError = error{OutOfMemory}; | ||
| 743 | |||
| 744 | /// `cmd_line_w` *must* be a WTF16-LE-encoded string. | ||
| 745 | /// | ||
| 746 | /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for | ||
| 747 | /// at least as long as the returned ArgIteratorWindows. | ||
| 748 | pub fn init(allocator: Allocator, cmd_line_w: []const u16) InitError!ArgIteratorWindows { | ||
| 749 | const wtf8_len = unicode.calcWtf8Len(cmd_line_w); | ||
| 750 | |||
| 751 | // This buffer must be large enough to contain contiguous NUL-terminated slices | ||
| 752 | // of each argument. | ||
| 753 | // - During parsing, the length of a parsed argument will always be equal to | ||
| 754 | // to less than its unparsed length | ||
| 755 | // - The first argument needs one extra byte of space allocated for its NUL | ||
| 756 | // terminator, but for each subsequent argument the necessary whitespace | ||
| 757 | // between arguments guarantees room for their NUL terminator(s). | ||
| 758 | const buffer = try allocator.alloc(u8, wtf8_len + 1); | ||
| 759 | errdefer allocator.free(buffer); | ||
| 760 | |||
| 761 | return .{ | ||
| 762 | .allocator = allocator, | ||
| 763 | .cmd_line = cmd_line_w, | ||
| 764 | .buffer = buffer, | ||
| 765 | }; | ||
| 766 | } | ||
| 767 | |||
| 768 | /// Returns the next argument and advances the iterator. Returns `null` if at the end of the | ||
| 769 | /// command-line string. The iterator owns the returned slice. | ||
| 770 | /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 771 | pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 { | ||
| 772 | return self.nextWithStrategy(next_strategy); | ||
| 773 | } | ||
| 774 | |||
| 775 | /// Skips the next argument and advances the iterator. Returns `true` if an argument was | ||
| 776 | /// skipped, `false` if at the end of the command-line string. | ||
| 777 | pub fn skip(self: *ArgIteratorWindows) bool { | ||
| 778 | return self.nextWithStrategy(skip_strategy); | ||
| 779 | } | ||
| 780 | |||
| 781 | const next_strategy = struct { | ||
| 782 | const T = ?[:0]const u8; | ||
| 783 | |||
| 784 | const eof = null; | ||
| 785 | |||
| 786 | /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`. | ||
| 787 | fn emitBackslashes(self: *ArgIteratorWindows, count: usize, last_emitted_code_unit: ?u16) ?u16 { | ||
| 788 | for (0..count) |_| { | ||
| 789 | self.buffer[self.end] = '\\'; | ||
| 790 | self.end += 1; | ||
| 791 | } | ||
| 792 | return if (count != 0) '\\' else last_emitted_code_unit; | ||
| 793 | } | ||
| 794 | |||
| 795 | /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then | ||
| 796 | /// the previously emitted high surrogate is overwritten by the codepoint encoded | ||
| 797 | /// by the surrogate pair, and `null` is returned. | ||
| 798 | /// Otherwise, `code_unit` is emitted and returned. | ||
| 799 | fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 { | ||
| 800 | // Because we are emitting WTF-8, we need to | ||
| 801 | // check to see if we've emitted two consecutive surrogate | ||
| 802 | // codepoints that form a valid surrogate pair in order | ||
| 803 | // to ensure that we're always emitting well-formed WTF-8 | ||
| 804 | // (https://wtf-8.codeberg.page/#concatenating). | ||
| 805 | // | ||
| 806 | // If we do have a valid surrogate pair, we need to emit | ||
| 807 | // the UTF-8 sequence for the codepoint that they encode | ||
| 808 | // instead of the WTF-8 encoding for the two surrogate pairs | ||
| 809 | // separately. | ||
| 810 | // | ||
| 811 | // This is relevant when dealing with a WTF-16 encoded | ||
| 812 | // command line like this: | ||
| 813 | // "<0xD801>"<0xDC37> | ||
| 814 | // which would get parsed and converted to WTF-8 as: | ||
| 815 | // <0xED><0xA0><0x81><0xED><0xB0><0xB7> | ||
| 816 | // but instead, we need to recognize the surrogate pair | ||
| 817 | // and emit the codepoint it encodes, which in this | ||
| 818 | // example is U+10437 (𐐷), which is encoded in UTF-8 as: | ||
| 819 | // <0xF0><0x90><0x90><0xB7> | ||
| 820 | if (last_emitted_code_unit != null and | ||
| 821 | std.unicode.utf16IsLowSurrogate(code_unit) and | ||
| 822 | std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?)) | ||
| 823 | { | ||
| 824 | const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable; | ||
| 825 | |||
| 826 | // Unpaired surrogate is 3 bytes long | ||
| 827 | const dest = self.buffer[self.end - 3 ..]; | ||
| 828 | const len = unicode.utf8Encode(codepoint, dest) catch unreachable; | ||
| 829 | // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes | ||
| 830 | assert(len == 4); | ||
| 831 | self.end += 1; | ||
| 832 | return null; | ||
| 833 | } | ||
| 834 | |||
| 835 | const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable; | ||
| 836 | self.end += wtf8_len; | ||
| 837 | return code_unit; | ||
| 838 | } | ||
| 839 | |||
| 840 | fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 { | ||
| 841 | self.buffer[self.end] = 0; | ||
| 842 | const arg = self.buffer[self.start..self.end :0]; | ||
| 843 | self.end += 1; | ||
| 844 | self.start = self.end; | ||
| 845 | return arg; | ||
| 846 | } | ||
| 847 | }; | ||
| 848 | |||
| 849 | const skip_strategy = struct { | ||
| 850 | const T = bool; | ||
| 851 | |||
| 852 | const eof = false; | ||
| 853 | |||
| 854 | fn emitBackslashes(_: *ArgIteratorWindows, _: usize, last_emitted_code_unit: ?u16) ?u16 { | ||
| 855 | return last_emitted_code_unit; | ||
| 856 | } | ||
| 857 | |||
| 858 | fn emitCharacter(_: *ArgIteratorWindows, _: u16, last_emitted_code_unit: ?u16) ?u16 { | ||
| 859 | return last_emitted_code_unit; | ||
| 860 | } | ||
| 861 | |||
| 862 | fn yieldArg(_: *ArgIteratorWindows) bool { | ||
| 863 | return true; | ||
| 864 | } | ||
| 865 | }; | ||
| 866 | |||
| 867 | fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T { | ||
| 868 | var last_emitted_code_unit: ?u16 = null; | ||
| 869 | // The first argument (the executable name) uses different parsing rules. | ||
| 870 | if (self.index == 0) { | ||
| 871 | if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) { | ||
| 872 | // Immediately complete the iterator. | ||
| 873 | // The C runtime would return the name of the current executable here. | ||
| 874 | return strategy.eof; | ||
| 875 | } | ||
| 876 | |||
| 877 | var inside_quotes = false; | ||
| 878 | while (true) : (self.index += 1) { | ||
| 879 | const char = if (self.index != self.cmd_line.len) | ||
| 880 | mem.littleToNative(u16, self.cmd_line[self.index]) | ||
| 881 | else | ||
| 882 | 0; | ||
| 883 | switch (char) { | ||
| 884 | 0 => { | ||
| 885 | return strategy.yieldArg(self); | ||
| 886 | }, | ||
| 887 | '"' => { | ||
| 888 | inside_quotes = !inside_quotes; | ||
| 889 | }, | ||
| 890 | ' ', '\t' => { | ||
| 891 | if (inside_quotes) { | ||
| 892 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 893 | } else { | ||
| 894 | self.index += 1; | ||
| 895 | return strategy.yieldArg(self); | ||
| 896 | } | ||
| 897 | }, | ||
| 898 | else => { | ||
| 899 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 900 | }, | ||
| 901 | } | ||
| 902 | } | ||
| 903 | } | ||
| 904 | |||
| 905 | // Skip spaces and tabs. The iterator completes if we reach the end of the string here. | ||
| 906 | while (true) : (self.index += 1) { | ||
| 907 | const char = if (self.index != self.cmd_line.len) | ||
| 908 | mem.littleToNative(u16, self.cmd_line[self.index]) | ||
| 909 | else | ||
| 910 | 0; | ||
| 911 | switch (char) { | ||
| 912 | 0 => return strategy.eof, | ||
| 913 | ' ', '\t' => continue, | ||
| 914 | else => break, | ||
| 915 | } | ||
| 916 | } | ||
| 917 | |||
| 918 | // Parsing rules for subsequent arguments: | ||
| 919 | // | ||
| 920 | // - The end of the string always terminates the current argument. | ||
| 921 | // - When not in 'inside_quotes' mode, a space or tab terminates the current argument. | ||
| 922 | // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero). | ||
| 923 | // If in 'inside_quotes' and the quote is immediately followed by a second quote, | ||
| 924 | // one quote is emitted and the other is skipped, otherwise, the quote is skipped | ||
| 925 | // and 'inside_quotes' is toggled. | ||
| 926 | // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote. | ||
| 927 | // - n backslashes not followed by a quote emit n backslashes. | ||
| 928 | var backslash_count: usize = 0; | ||
| 929 | var inside_quotes = false; | ||
| 930 | while (true) : (self.index += 1) { | ||
| 931 | const char = if (self.index != self.cmd_line.len) | ||
| 932 | mem.littleToNative(u16, self.cmd_line[self.index]) | ||
| 933 | else | ||
| 934 | 0; | ||
| 935 | switch (char) { | ||
| 936 | 0 => { | ||
| 937 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); | ||
| 938 | return strategy.yieldArg(self); | ||
| 939 | }, | ||
| 940 | ' ', '\t' => { | ||
| 941 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); | ||
| 942 | backslash_count = 0; | ||
| 943 | if (inside_quotes) { | ||
| 944 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 945 | } else return strategy.yieldArg(self); | ||
| 946 | }, | ||
| 947 | '"' => { | ||
| 948 | const char_is_escaped_quote = backslash_count % 2 != 0; | ||
| 949 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit); | ||
| 950 | backslash_count = 0; | ||
| 951 | if (char_is_escaped_quote) { | ||
| 952 | last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); | ||
| 953 | } else { | ||
| 954 | if (inside_quotes and | ||
| 955 | self.index + 1 != self.cmd_line.len and | ||
| 956 | mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"') | ||
| 957 | { | ||
| 958 | last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); | ||
| 959 | self.index += 1; | ||
| 960 | } else { | ||
| 961 | inside_quotes = !inside_quotes; | ||
| 962 | } | ||
| 963 | } | ||
| 964 | }, | ||
| 965 | '\\' => { | ||
| 966 | backslash_count += 1; | ||
| 967 | }, | ||
| 968 | else => { | ||
| 969 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); | ||
| 970 | backslash_count = 0; | ||
| 971 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 972 | }, | ||
| 973 | } | ||
| 974 | } | ||
| 975 | } | ||
| 976 | |||
| 977 | /// Frees the iterator's copy of the command-line string and all previously returned | ||
| 978 | /// argument slices. | ||
| 979 | pub fn deinit(self: *ArgIteratorWindows) void { | ||
| 980 | self.allocator.free(self.buffer); | ||
| 981 | } | ||
| 982 | }; | ||
| 983 | |||
| 984 | /// Optional parameters for `ArgIteratorGeneral` | ||
| 985 | pub const ArgIteratorGeneralOptions = struct { | ||
| 986 | comments: bool = false, | ||
| 987 | single_quotes: bool = false, | ||
| 988 | }; | ||
| 989 | |||
| 990 | /// A general Iterator to parse a string into a set of arguments | ||
| 991 | pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { | ||
| 992 | return struct { | ||
| 993 | allocator: Allocator, | ||
| 994 | index: usize = 0, | ||
| 995 | cmd_line: []const u8, | ||
| 996 | |||
| 997 | /// Should the cmd_line field be free'd (using the allocator) on deinit()? | ||
| 998 | free_cmd_line_on_deinit: bool, | ||
| 999 | |||
| 1000 | /// buffer MUST be long enough to hold the cmd_line plus a null terminator. | ||
| 1001 | /// buffer will we free'd (using the allocator) on deinit() | ||
| 1002 | buffer: []u8, | ||
| 1003 | start: usize = 0, | ||
| 1004 | end: usize = 0, | ||
| 1005 | |||
| 1006 | pub const Self = @This(); | ||
| 1007 | |||
| 1008 | pub const InitError = error{OutOfMemory}; | ||
| 1009 | |||
| 1010 | /// cmd_line_utf8 MUST remain valid and constant while using this instance | ||
| 1011 | pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { | ||
| 1012 | const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); | ||
| 1013 | errdefer allocator.free(buffer); | ||
| 1014 | |||
| 1015 | return Self{ | ||
| 1016 | .allocator = allocator, | ||
| 1017 | .cmd_line = cmd_line_utf8, | ||
| 1018 | .free_cmd_line_on_deinit = false, | ||
| 1019 | .buffer = buffer, | ||
| 1020 | }; | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | /// cmd_line_utf8 will be free'd (with the allocator) on deinit() | ||
| 1024 | pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { | ||
| 1025 | const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); | ||
| 1026 | errdefer allocator.free(buffer); | ||
| 1027 | |||
| 1028 | return Self{ | ||
| 1029 | .allocator = allocator, | ||
| 1030 | .cmd_line = cmd_line_utf8, | ||
| 1031 | .free_cmd_line_on_deinit = true, | ||
| 1032 | .buffer = buffer, | ||
| 1033 | }; | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | // Skips over whitespace in the cmd_line. | ||
| 1037 | // Returns false if the terminating sentinel is reached, true otherwise. | ||
| 1038 | // Also skips over comments (if supported). | ||
| 1039 | fn skipWhitespace(self: *Self) bool { | ||
| 1040 | while (true) : (self.index += 1) { | ||
| 1041 | const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | ||
| 1042 | switch (character) { | ||
| 1043 | 0 => return false, | ||
| 1044 | ' ', '\t', '\r', '\n' => continue, | ||
| 1045 | '#' => { | ||
| 1046 | if (options.comments) { | ||
| 1047 | while (true) : (self.index += 1) { | ||
| 1048 | switch (self.cmd_line[self.index]) { | ||
| 1049 | '\n' => break, | ||
| 1050 | 0 => return false, | ||
| 1051 | else => continue, | ||
| 1052 | } | ||
| 1053 | } | ||
| 1054 | continue; | ||
| 1055 | } else { | ||
| 1056 | break; | ||
| 1057 | } | ||
| 1058 | }, | ||
| 1059 | else => break, | ||
| 1060 | } | ||
| 1061 | } | ||
| 1062 | return true; | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | pub fn skip(self: *Self) bool { | ||
| 1066 | if (!self.skipWhitespace()) { | ||
| 1067 | return false; | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | var backslash_count: usize = 0; | ||
| 1071 | var in_quote = false; | ||
| 1072 | while (true) : (self.index += 1) { | ||
| 1073 | const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | ||
| 1074 | switch (character) { | ||
| 1075 | 0 => return true, | ||
| 1076 | '"', '\'' => { | ||
| 1077 | if (!options.single_quotes and character == '\'') { | ||
| 1078 | backslash_count = 0; | ||
| 1079 | continue; | ||
| 1080 | } | ||
| 1081 | const quote_is_real = backslash_count % 2 == 0; | ||
| 1082 | if (quote_is_real) { | ||
| 1083 | in_quote = !in_quote; | ||
| 1084 | } | ||
| 1085 | }, | ||
| 1086 | '\\' => { | ||
| 1087 | backslash_count += 1; | ||
| 1088 | }, | ||
| 1089 | ' ', '\t', '\r', '\n' => { | ||
| 1090 | if (!in_quote) { | ||
| 1091 | return true; | ||
| 1092 | } | ||
| 1093 | backslash_count = 0; | ||
| 1094 | }, | ||
| 1095 | else => { | ||
| 1096 | backslash_count = 0; | ||
| 1097 | continue; | ||
| 1098 | }, | ||
| 1099 | } | ||
| 1100 | } | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | /// Returns a slice of the internal buffer that contains the next argument. | ||
| 1104 | /// Returns null when it reaches the end. | ||
| 1105 | pub fn next(self: *Self) ?[:0]const u8 { | ||
| 1106 | if (!self.skipWhitespace()) { | ||
| 1107 | return null; | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | var backslash_count: usize = 0; | ||
| 1111 | var in_quote = false; | ||
| 1112 | while (true) : (self.index += 1) { | ||
| 1113 | const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | ||
| 1114 | switch (character) { | ||
| 1115 | 0 => { | ||
| 1116 | self.emitBackslashes(backslash_count); | ||
| 1117 | self.buffer[self.end] = 0; | ||
| 1118 | const token = self.buffer[self.start..self.end :0]; | ||
| 1119 | self.end += 1; | ||
| 1120 | self.start = self.end; | ||
| 1121 | return token; | ||
| 1122 | }, | ||
| 1123 | '"', '\'' => { | ||
| 1124 | if (!options.single_quotes and character == '\'') { | ||
| 1125 | self.emitBackslashes(backslash_count); | ||
| 1126 | backslash_count = 0; | ||
| 1127 | self.emitCharacter(character); | ||
| 1128 | continue; | ||
| 1129 | } | ||
| 1130 | const quote_is_real = backslash_count % 2 == 0; | ||
| 1131 | self.emitBackslashes(backslash_count / 2); | ||
| 1132 | backslash_count = 0; | ||
| 1133 | |||
| 1134 | if (quote_is_real) { | ||
| 1135 | in_quote = !in_quote; | ||
| 1136 | } else { | ||
| 1137 | self.emitCharacter('"'); | ||
| 1138 | } | ||
| 1139 | }, | ||
| 1140 | '\\' => { | ||
| 1141 | backslash_count += 1; | ||
| 1142 | }, | ||
| 1143 | ' ', '\t', '\r', '\n' => { | ||
| 1144 | self.emitBackslashes(backslash_count); | ||
| 1145 | backslash_count = 0; | ||
| 1146 | if (in_quote) { | ||
| 1147 | self.emitCharacter(character); | ||
| 1148 | } else { | ||
| 1149 | self.buffer[self.end] = 0; | ||
| 1150 | const token = self.buffer[self.start..self.end :0]; | ||
| 1151 | self.end += 1; | ||
| 1152 | self.start = self.end; | ||
| 1153 | return token; | ||
| 1154 | } | ||
| 1155 | }, | ||
| 1156 | else => { | ||
| 1157 | self.emitBackslashes(backslash_count); | ||
| 1158 | backslash_count = 0; | ||
| 1159 | self.emitCharacter(character); | ||
| 1160 | }, | ||
| 1161 | } | ||
| 1162 | } | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | fn emitBackslashes(self: *Self, emit_count: usize) void { | ||
| 1166 | var i: usize = 0; | ||
| 1167 | while (i < emit_count) : (i += 1) { | ||
| 1168 | self.emitCharacter('\\'); | ||
| 1169 | } | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | fn emitCharacter(self: *Self, char: u8) void { | ||
| 1173 | self.buffer[self.end] = char; | ||
| 1174 | self.end += 1; | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | /// Call to free the internal buffer of the iterator. | ||
| 1178 | pub fn deinit(self: *Self) void { | ||
| 1179 | self.allocator.free(self.buffer); | ||
| 1180 | |||
| 1181 | if (self.free_cmd_line_on_deinit) { | ||
| 1182 | self.allocator.free(self.cmd_line); | ||
| 1183 | } | ||
| 1184 | } | ||
| 1185 | }; | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | /// Cross-platform command line argument iterator. | ||
| 1189 | pub const ArgIterator = struct { | ||
| 1190 | const InnerType = switch (native_os) { | ||
| 1191 | .windows => ArgIteratorWindows, | ||
| 1192 | .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi, | ||
| 1193 | else => ArgIteratorPosix, | ||
| 1194 | }; | ||
| 1195 | |||
| 1196 | inner: InnerType, | ||
| 1197 | |||
| 1198 | /// Initialize the args iterator. Consider using initWithAllocator() instead | ||
| 1199 | /// for cross-platform compatibility. | ||
| 1200 | pub fn init() ArgIterator { | ||
| 1201 | if (native_os == .wasi) { | ||
| 1202 | @compileError("In WASI, use initWithAllocator instead."); | ||
| 1203 | } | ||
| 1204 | if (native_os == .windows) { | ||
| 1205 | @compileError("In Windows, use initWithAllocator instead."); | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | return ArgIterator{ .inner = InnerType.init() }; | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | pub const InitError = InnerType.InitError; | ||
| 1212 | |||
| 1213 | /// You must deinitialize iterator's internal buffers by calling `deinit` when done. | ||
| 1214 | pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator { | ||
| 1215 | if (native_os == .wasi and !builtin.link_libc) { | ||
| 1216 | return ArgIterator{ .inner = try InnerType.init(allocator) }; | ||
| 1217 | } | ||
| 1218 | if (native_os == .windows) { | ||
| 1219 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; | ||
| 1220 | const cmd_line_w = cmd_line.Buffer.?[0 .. cmd_line.Length / 2]; | ||
| 1221 | return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) }; | ||
| 1222 | } | ||
| 1223 | |||
| 1224 | return ArgIterator{ .inner = InnerType.init() }; | ||
| 1225 | } | ||
| 1226 | |||
| 1227 | /// Get the next argument. Returns 'null' if we are at the end. | ||
| 1228 | /// Returned slice is pointing to the iterator's internal buffer. | ||
| 1229 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 1230 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 1231 | pub fn next(self: *ArgIterator) ?([:0]const u8) { | ||
| 1232 | return self.inner.next(); | ||
| 1233 | } | ||
| 1234 | |||
| 1235 | /// Parse past 1 argument without capturing it. | ||
| 1236 | /// Returns `true` if skipped an arg, `false` if we are at the end. | ||
| 1237 | pub fn skip(self: *ArgIterator) bool { | ||
| 1238 | return self.inner.skip(); | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | /// Call this to free the iterator's internal buffer if the iterator | ||
| 1242 | /// was created with `initWithAllocator` function. | ||
| 1243 | pub fn deinit(self: *ArgIterator) void { | ||
| 1244 | // Unless we're targeting WASI or Windows, this is a no-op. | ||
| 1245 | if (native_os == .wasi and !builtin.link_libc) { | ||
| 1246 | self.inner.deinit(); | ||
| 1247 | } | ||
| 1248 | |||
| 1249 | if (native_os == .windows) { | ||
| 1250 | self.inner.deinit(); | ||
| 1251 | } | ||
| 1252 | } | ||
| 1253 | }; | ||
| 1254 | |||
| 1255 | /// Holds the command-line arguments, with the program name as the first entry. | ||
| 1256 | /// Use argsWithAllocator() for cross-platform code. | ||
| 1257 | pub fn args() ArgIterator { | ||
| 1258 | return ArgIterator.init(); | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | /// You must deinitialize iterator's internal buffers by calling `deinit` when done. | ||
| 1262 | pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator { | ||
| 1263 | return ArgIterator.initWithAllocator(allocator); | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | /// Caller must call argsFree on result. | ||
| 1267 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 1268 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 1269 | pub fn argsAlloc(allocator: Allocator) ![][:0]u8 { | ||
| 1270 | // TODO refactor to only make 1 allocation. | ||
| 1271 | var it = try argsWithAllocator(allocator); | ||
| 1272 | defer it.deinit(); | ||
| 1273 | |||
| 1274 | var contents = std.array_list.Managed(u8).init(allocator); | ||
| 1275 | defer contents.deinit(); | ||
| 1276 | |||
| 1277 | var slice_list = std.array_list.Managed(usize).init(allocator); | ||
| 1278 | defer slice_list.deinit(); | ||
| 1279 | |||
| 1280 | while (it.next()) |arg| { | ||
| 1281 | try contents.appendSlice(arg[0 .. arg.len + 1]); | ||
| 1282 | try slice_list.append(arg.len); | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | const contents_slice = contents.items; | ||
| 1286 | const slice_sizes = slice_list.items; | ||
| 1287 | const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len); | ||
| 1288 | const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len); | ||
| 1289 | const buf = try allocator.alignedAlloc(u8, .of([]u8), total_bytes); | ||
| 1290 | errdefer allocator.free(buf); | ||
| 1291 | |||
| 1292 | const result_slice_list = mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]); | ||
| 1293 | const result_contents = buf[slice_list_bytes..]; | ||
| 1294 | @memcpy(result_contents[0..contents_slice.len], contents_slice); | ||
| 1295 | |||
| 1296 | var contents_index: usize = 0; | ||
| 1297 | for (slice_sizes, 0..) |len, i| { | ||
| 1298 | const new_index = contents_index + len; | ||
| 1299 | result_slice_list[i] = result_contents[contents_index..new_index :0]; | ||
| 1300 | contents_index = new_index + 1; | ||
| 1301 | } | ||
| 1302 | |||
| 1303 | return result_slice_list; | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | pub fn argsFree(allocator: Allocator, args_alloc: []const [:0]u8) void { | ||
| 1307 | var total_bytes: usize = 0; | ||
| 1308 | for (args_alloc) |arg| { | ||
| 1309 | total_bytes += @sizeOf([]u8) + arg.len + 1; | ||
| 1310 | } | ||
| 1311 | const unaligned_allocated_buf = @as([*]const u8, @ptrCast(args_alloc.ptr))[0..total_bytes]; | ||
| 1312 | const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf); | ||
| 1313 | return allocator.free(aligned_allocated_buf); | ||
| 1314 | } | ||
| 1315 | |||
| 1316 | test ArgIteratorWindows { | ||
| 1317 | const t = testArgIteratorWindows; | ||
| 1318 | |||
| 1319 | try t( | ||
| 1320 | \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" | ||
| 1321 | , &.{ | ||
| 1322 | \\C:\Program Files\zig\zig.exe | ||
| 1323 | , | ||
| 1324 | \\run | ||
| 1325 | , | ||
| 1326 | \\.\src\main.zig | ||
| 1327 | , | ||
| 1328 | \\-target | ||
| 1329 | , | ||
| 1330 | \\x86_64-windows-gnu | ||
| 1331 | , | ||
| 1332 | \\-O | ||
| 1333 | , | ||
| 1334 | \\ReleaseSafe | ||
| 1335 | , | ||
| 1336 | \\-- | ||
| 1337 | , | ||
| 1338 | \\--emoji=🗿 | ||
| 1339 | , | ||
| 1340 | \\--eval=new Regex("Dwayne \"The Rock\" Johnson") | ||
| 1341 | , | ||
| 1342 | }); | ||
| 1343 | |||
| 1344 | // Empty | ||
| 1345 | try t("", &.{}); | ||
| 1346 | |||
| 1347 | // Separators | ||
| 1348 | try t("aa bb cc", &.{ "aa", "bb", "cc" }); | ||
| 1349 | try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" }); | ||
| 1350 | try t("aa\nbb\ncc", &.{"aa\nbb\ncc"}); | ||
| 1351 | try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"}); | ||
| 1352 | try t("aa\rbb\rcc", &.{"aa\rbb\rcc"}); | ||
| 1353 | try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"}); | ||
| 1354 | try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"}); | ||
| 1355 | try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"}); | ||
| 1356 | |||
| 1357 | // Leading/trailing whitespace | ||
| 1358 | try t(" ", &.{""}); | ||
| 1359 | try t(" aa bb ", &.{ "", "aa", "bb" }); | ||
| 1360 | try t("\t\t", &.{""}); | ||
| 1361 | try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" }); | ||
| 1362 | try t("\n\n", &.{"\n\n"}); | ||
| 1363 | try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"}); | ||
| 1364 | |||
| 1365 | // Executable name with quotes/backslashes | ||
| 1366 | try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"}); | ||
| 1367 | try t("\"", &.{""}); | ||
| 1368 | try t("\"\"", &.{""}); | ||
| 1369 | try t("\"\"\"", &.{""}); | ||
| 1370 | try t("\"\"\"\"", &.{""}); | ||
| 1371 | try t("\"\"\"\"\"", &.{""}); | ||
| 1372 | try t("aa\"bb\"cc\"dd", &.{"aabbccdd"}); | ||
| 1373 | try t("aa\"bb cc\"dd", &.{"aabb ccdd"}); | ||
| 1374 | try t("\"aa\\\"bb\"", &.{"aa\\bb"}); | ||
| 1375 | try t("\"aa\\\\\"", &.{"aa\\\\"}); | ||
| 1376 | try t("aa\\\"bb", &.{"aa\\bb"}); | ||
| 1377 | try t("aa\\\\\"bb", &.{"aa\\\\bb"}); | ||
| 1378 | |||
| 1379 | // Arguments with quotes/backslashes | ||
| 1380 | try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" }); | ||
| 1381 | try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" }); | ||
| 1382 | try t(". ", &.{"."}); | ||
| 1383 | try t(". \"", &.{ ".", "" }); | ||
| 1384 | try t(". \"\"", &.{ ".", "" }); | ||
| 1385 | try t(". \"\"\"", &.{ ".", "\"" }); | ||
| 1386 | try t(". \"\"\"\"", &.{ ".", "\"" }); | ||
| 1387 | try t(". \"\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 1388 | try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 1389 | try t(". \" \"", &.{ ".", " " }); | ||
| 1390 | try t(". \" \"\"", &.{ ".", " \"" }); | ||
| 1391 | try t(". \" \"\"\"", &.{ ".", " \"" }); | ||
| 1392 | try t(". \" \"\"\"\"", &.{ ".", " \"\"" }); | ||
| 1393 | try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" }); | ||
| 1394 | try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" }); | ||
| 1395 | try t(". \\\"", &.{ ".", "\"" }); | ||
| 1396 | try t(". \\\"\"", &.{ ".", "\"" }); | ||
| 1397 | try t(". \\\"\"\"", &.{ ".", "\"" }); | ||
| 1398 | try t(". \\\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 1399 | try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 1400 | try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" }); | ||
| 1401 | try t(". \" \\\"", &.{ ".", " \"" }); | ||
| 1402 | try t(". \" \\\"\"", &.{ ".", " \"" }); | ||
| 1403 | try t(". \" \\\"\"\"", &.{ ".", " \"\"" }); | ||
| 1404 | try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" }); | ||
| 1405 | try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" }); | ||
| 1406 | try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" }); | ||
| 1407 | try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" }); | ||
| 1408 | try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" }); | ||
| 1409 | try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" }); | ||
| 1410 | |||
| 1411 | // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines | ||
| 1412 | try t( | ||
| 1413 | \\foo.exe "abc" d e | ||
| 1414 | , &.{ "foo.exe", "abc", "d", "e" }); | ||
| 1415 | try t( | ||
| 1416 | \\foo.exe a\\b d"e f"g h | ||
| 1417 | , &.{ "foo.exe", "a\\\\b", "de fg", "h" }); | ||
| 1418 | try t( | ||
| 1419 | \\foo.exe a\\\"b c d | ||
| 1420 | , &.{ "foo.exe", "a\\\"b", "c", "d" }); | ||
| 1421 | try t( | ||
| 1422 | \\foo.exe a\\\\"b c" d e | ||
| 1423 | , &.{ "foo.exe", "a\\\\b c", "d", "e" }); | ||
| 1424 | try t( | ||
| 1425 | \\foo.exe a"b"" c d | ||
| 1426 | , &.{ "foo.exe", "ab\" c d" }); | ||
| 1427 | |||
| 1428 | // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX | ||
| 1429 | try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" }); | ||
| 1430 | try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" }); | ||
| 1431 | try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" }); | ||
| 1432 | try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" }); | ||
| 1433 | try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" }); | ||
| 1434 | try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" }); | ||
| 1435 | try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" }); | ||
| 1436 | try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" }); | ||
| 1437 | try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" }); | ||
| 1438 | |||
| 1439 | // Surrogate pair encoding of 𐐷 separated by quotes. | ||
| 1440 | // Encoded as WTF-16: | ||
| 1441 | // "<0xD801>"<0xDC37> | ||
| 1442 | // Encoded as WTF-8: | ||
| 1443 | // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7> | ||
| 1444 | // During parsing, the quotes drop out and the surrogate pair | ||
| 1445 | // should end up encoded as its normal UTF-8 representation. | ||
| 1446 | try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" }); | ||
| 1447 | } | ||
| 1448 | |||
| 1449 | fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { | ||
| 1450 | const cmd_line_w = try unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line); | ||
| 1451 | defer testing.allocator.free(cmd_line_w); | ||
| 1452 | |||
| 1453 | // next | ||
| 1454 | { | ||
| 1455 | var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w); | ||
| 1456 | defer it.deinit(); | ||
| 1457 | |||
| 1458 | for (expected_args) |expected| { | ||
| 1459 | if (it.next()) |actual| { | ||
| 1460 | try testing.expectEqualStrings(expected, actual); | ||
| 1461 | } else { | ||
| 1462 | return error.TestUnexpectedResult; | ||
| 1463 | } | ||
| 1464 | } | ||
| 1465 | try testing.expect(it.next() == null); | ||
| 1466 | } | ||
| 1467 | |||
| 1468 | // skip | ||
| 1469 | { | ||
| 1470 | var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w); | ||
| 1471 | defer it.deinit(); | ||
| 1472 | |||
| 1473 | for (0..expected_args.len) |_| { | ||
| 1474 | try testing.expect(it.skip()); | ||
| 1475 | } | ||
| 1476 | try testing.expect(!it.skip()); | ||
| 1477 | } | ||
| 1478 | } | ||
| 1479 | |||
| 1480 | test "general arg parsing" { | ||
| 1481 | try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" }); | ||
| 1482 | try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" }); | ||
| 1483 | try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" }); | ||
| 1484 | try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" }); | ||
| 1485 | try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" }); | ||
| 1486 | try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" }); | ||
| 1487 | try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" }); | ||
| 1488 | try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" }); | ||
| 1489 | |||
| 1490 | try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{ | ||
| 1491 | ".\\..\\zig-cache\\build", | ||
| 1492 | "bin\\zig.exe", | ||
| 1493 | ".\\..", | ||
| 1494 | ".\\..\\zig-cache", | ||
| 1495 | "--help", | ||
| 1496 | }); | ||
| 1497 | |||
| 1498 | try testGeneralCmdLine( | ||
| 1499 | \\ 'foo' "bar" | ||
| 1500 | , &.{ "'foo'", "bar" }); | ||
| 1501 | } | ||
| 1502 | |||
| 1503 | fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { | ||
| 1504 | var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line); | ||
| 1505 | defer it.deinit(); | ||
| 1506 | for (expected_args) |expected_arg| { | ||
| 1507 | const arg = it.next().?; | ||
| 1508 | try testing.expectEqualStrings(expected_arg, arg); | ||
| 1509 | } | ||
| 1510 | try testing.expect(it.next() == null); | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | test "response file arg parsing" { | ||
| 1514 | try testResponseFileCmdLine( | ||
| 1515 | \\a b | ||
| 1516 | \\c d\ | ||
| 1517 | , &.{ "a", "b", "c", "d\\" }); | ||
| 1518 | try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" }); | ||
| 1519 | |||
| 1520 | try testResponseFileCmdLine( | ||
| 1521 | \\j | ||
| 1522 | \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\" | ||
| 1523 | \\ "m" #another comment | ||
| 1524 | \\ | ||
| 1525 | , &.{ "j", "k", "l", "m" }); | ||
| 1526 | |||
| 1527 | try testResponseFileCmdLine( | ||
| 1528 | \\ "" q "" | ||
| 1529 | \\ "r s # t" "u\" v" #another comment | ||
| 1530 | \\ | ||
| 1531 | , &.{ "", "q", "", "r s # t", "u\" v" }); | ||
| 1532 | |||
| 1533 | try testResponseFileCmdLine( | ||
| 1534 | \\ -l"advapi32" a# b#c d# | ||
| 1535 | \\e\\\ | ||
| 1536 | , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" }); | ||
| 1537 | |||
| 1538 | try testResponseFileCmdLine( | ||
| 1539 | \\ 'foo' "bar" | ||
| 1540 | , &.{ "foo", "bar" }); | ||
| 1541 | } | ||
| 1542 | |||
| 1543 | fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { | ||
| 1544 | var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }) | ||
| 1545 | .init(std.testing.allocator, input_cmd_line); | ||
| 1546 | defer it.deinit(); | ||
| 1547 | for (expected_args) |expected_arg| { | ||
| 1548 | const arg = it.next().?; | ||
| 1549 | try testing.expectEqualStrings(expected_arg, arg); | ||
| 1550 | } | ||
| 1551 | try testing.expect(it.next() == null); | ||
| 1552 | } | ||
| 1553 | |||
| 1554 | pub const UserInfo = struct { | 107 | pub const UserInfo = struct { |
| 1555 | uid: posix.uid_t, | 108 | uid: posix.uid_t, |
| 1556 | gid: posix.gid_t, | 109 | gid: posix.gid_t, |
| ... | @@ -1706,73 +259,143 @@ pub fn getBaseAddress() usize { | ... | @@ -1706,73 +259,143 @@ pub fn getBaseAddress() usize { |
| 1706 | } | 259 | } |
| 1707 | } | 260 | } |
| 1708 | 261 | ||
| 1709 | /// Tells whether calling the `execv` or `execve` functions will be a compile error. | 262 | /// Deprecated in favor of `Child.can_spawn`. |
| 1710 | pub const can_execv = switch (native_os) { | 263 | pub const can_spawn = Child.can_spawn; |
| 264 | /// Deprecated in favor of `can_replace`. | ||
| 265 | pub const can_execv = can_replace; | ||
| 266 | |||
| 267 | /// Tells whether the target operating system supports replacing the current | ||
| 268 | /// process image. If this is `false` then calling `execv` or `replace` | ||
| 269 | /// functions will cause compilation to fail. | ||
| 270 | pub const can_replace = switch (native_os) { | ||
| 1711 | .windows, .haiku, .wasi => false, | 271 | .windows, .haiku, .wasi => false, |
| 1712 | else => true, | 272 | else => true, |
| 1713 | }; | 273 | }; |
| 1714 | 274 | ||
| 1715 | /// Tells whether spawning child processes is supported (e.g. via Child) | 275 | pub const ReplaceError = std.posix.ExecveError || error{OutOfMemory}; |
| 1716 | pub const can_spawn = switch (native_os) { | ||
| 1717 | .wasi, .ios, .tvos, .visionos, .watchos => false, | ||
| 1718 | else => true, | ||
| 1719 | }; | ||
| 1720 | 276 | ||
| 1721 | pub const ExecvError = std.posix.ExecveError || error{OutOfMemory}; | 277 | /// Replaces the current process image with the executed process. If this |
| 278 | /// function succeeds, it does not return. | ||
| 279 | /// | ||
| 280 | /// `argv[0]` is the name of the process to replace the current one with. If it | ||
| 281 | /// is not already a file path (i.e. it contains '/'), it is resolved into a | ||
| 282 | /// file path based on PATH from the parent environment. | ||
| 283 | /// | ||
| 284 | /// This operation is not available on targets for which `can_replace` is | ||
| 285 | /// `false`. | ||
| 286 | /// | ||
| 287 | /// This function must allocate memory to add a null terminating bytes on path | ||
| 288 | /// and each arg. | ||
| 289 | /// | ||
| 290 | /// Due to the heap allocation, it is illegal to call this function in a fork() | ||
| 291 | /// child. | ||
| 292 | pub fn replace(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { | ||
| 293 | if (!can_replace) @compileError("unsupported operation: replace"); | ||
| 1722 | 294 | ||
| 1723 | /// Replaces the current process image with the executed process. | 295 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); |
| 1724 | /// This function must allocate memory to add a null terminating bytes on path and each arg. | 296 | defer arena_allocator.deinit(); |
| 1725 | /// It must also convert to KEY=VALUE\0 format for environment variables, and include null | 297 | const arena = arena_allocator.allocator(); |
| 1726 | /// pointers after the args and after the environment variables. | 298 | |
| 1727 | /// `argv[0]` is the executable path. | 299 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); |
| 1728 | /// This function also uses the PATH environment variable to get the full path to the executable. | 300 | for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; |
| 1729 | /// Due to the heap-allocation, it is illegal to call this function in a fork() child. | 301 | |
| 1730 | /// For that use case, use the `std.posix` functions directly. | 302 | return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); |
| 1731 | pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError { | ||
| 1732 | return execve(allocator, argv, null); | ||
| 1733 | } | 303 | } |
| 1734 | 304 | ||
| 1735 | /// Replaces the current process image with the executed process. | 305 | /// Replaces the current process image with the executed process. If this |
| 1736 | /// This function must allocate memory to add a null terminating bytes on path and each arg. | 306 | /// function succeeds, it does not return. |
| 1737 | /// It must also convert to KEY=VALUE\0 format for environment variables, and include null | 307 | /// |
| 1738 | /// pointers after the args and after the environment variables. | 308 | /// `argv[0]` is the file path of the process to replace the current one with, |
| 1739 | /// `argv[0]` is the executable path. | 309 | /// relative to `dir`. It is *always* treated as a file path, even if it does |
| 1740 | /// This function also uses the PATH environment variable to get the full path to the executable. | 310 | /// not contain '/'. |
| 1741 | /// Due to the heap-allocation, it is illegal to call this function in a fork() child. | 311 | /// |
| 1742 | /// For that use case, use the `std.posix` functions directly. | 312 | /// This operation is not available on targets for which `can_replace` is |
| 1743 | pub fn execve( | 313 | /// `false`. |
| 1744 | allocator: Allocator, | 314 | /// |
| 1745 | argv: []const []const u8, | 315 | /// This function must allocate memory to add a null terminating bytes on path |
| 1746 | env_map: ?*const EnvMap, | 316 | /// and each arg. |
| 1747 | ) ExecvError { | 317 | /// |
| 1748 | if (!can_execv) @compileError("The target OS does not support execv"); | 318 | /// Due to the heap allocation, it is illegal to call this |
| 1749 | 319 | /// function in a fork() child. For that use case, use the `std.posix` | |
| 1750 | var arena_allocator = std.heap.ArenaAllocator.init(allocator); | 320 | /// functions directly. |
| 321 | pub fn replaceFile(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { | ||
| 322 | if (!can_replace) @compileError("unsupported operation: replaceFile"); | ||
| 323 | |||
| 324 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | ||
| 1751 | defer arena_allocator.deinit(); | 325 | defer arena_allocator.deinit(); |
| 1752 | const arena = arena_allocator.allocator(); | 326 | const arena = arena_allocator.allocator(); |
| 1753 | 327 | ||
| 1754 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); | 328 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); |
| 1755 | for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; | 329 | for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; |
| 1756 | 330 | ||
| 1757 | const envp = m: { | 331 | return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); |
| 1758 | if (env_map) |m| { | 332 | } |
| 1759 | const envp_buf = try createNullDelimitedEnvMap(arena, m); | 333 | |
| 1760 | break :m envp_buf.ptr; | 334 | pub const Arg0Expand = enum { expand, no_expand }; |
| 1761 | } else if (builtin.link_libc) { | 335 | |
| 1762 | break :m std.c.environ; | 336 | /// Replaces the current process image with the executed process. If this |
| 1763 | } else if (builtin.output_mode == .Exe) { | 337 | /// function succeeds, it does not return. |
| 1764 | // Then we have Zig start code and this works. | 338 | /// |
| 1765 | // TODO type-safety for null-termination of `os.environ`. | 339 | /// This operation is not available on all targets. `can_execv` |
| 1766 | break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr)); | 340 | /// |
| 1767 | } else { | 341 | /// This function also uses the PATH environment variable to get the full path to the executable. |
| 1768 | // TODO come up with a solution for this. | 342 | /// If `file` is an absolute path, this is the same as `execveZ`. |
| 1769 | @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process"); | 343 | /// |
| 1770 | } | 344 | /// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, |
| 345 | /// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. | ||
| 346 | /// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. | ||
| 347 | pub fn replace( | ||
| 348 | comptime arg0_expand: Arg0Expand, | ||
| 349 | file: [*:0]const u8, | ||
| 350 | child_argv: switch (arg0_expand) { | ||
| 351 | .expand => [*:null]?[*:0]const u8, | ||
| 352 | .no_expand => [*:null]const ?[*:0]const u8, | ||
| 353 | }, | ||
| 354 | envp: [*:null]const ?[*:0]const u8, | ||
| 355 | optional_PATH: ?[]const u8, | ||
| 356 | ) ExecveError { | ||
| 357 | const file_slice = mem.sliceTo(file, 0); | ||
| 358 | if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); | ||
| 359 | |||
| 360 | const PATH = optional_PATH orelse "/usr/local/bin:/bin/:/usr/bin"; | ||
| 361 | // Use of PATH_MAX here is valid as the path_buf will be passed | ||
| 362 | // directly to the operating system in execveZ. | ||
| 363 | var path_buf: [PATH_MAX]u8 = undefined; | ||
| 364 | var it = mem.tokenizeScalar(u8, PATH, ':'); | ||
| 365 | var seen_eacces = false; | ||
| 366 | var err: ExecveError = error.FileNotFound; | ||
| 367 | |||
| 368 | // In case of expanding arg0 we must put it back if we return with an error. | ||
| 369 | const prev_arg0 = child_argv[0]; | ||
| 370 | defer switch (arg0_expand) { | ||
| 371 | .expand => child_argv[0] = prev_arg0, | ||
| 372 | .no_expand => {}, | ||
| 1771 | }; | 373 | }; |
| 1772 | 374 | ||
| 1773 | return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); | 375 | while (it.next()) |search_path| { |
| 376 | const path_len = search_path.len + file_slice.len + 1; | ||
| 377 | if (path_buf.len < path_len + 1) return error.NameTooLong; | ||
| 378 | @memcpy(path_buf[0..search_path.len], search_path); | ||
| 379 | path_buf[search_path.len] = '/'; | ||
| 380 | @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); | ||
| 381 | path_buf[path_len] = 0; | ||
| 382 | const full_path = path_buf[0..path_len :0].ptr; | ||
| 383 | switch (arg0_expand) { | ||
| 384 | .expand => child_argv[0] = full_path, | ||
| 385 | .no_expand => {}, | ||
| 386 | } | ||
| 387 | err = execveZ(full_path, child_argv, envp); | ||
| 388 | switch (err) { | ||
| 389 | error.AccessDenied => seen_eacces = true, | ||
| 390 | error.FileNotFound, error.NotDir => {}, | ||
| 391 | else => |e| return e, | ||
| 392 | } | ||
| 393 | } | ||
| 394 | if (seen_eacces) return error.AccessDenied; | ||
| 395 | return err; | ||
| 1774 | } | 396 | } |
| 1775 | 397 | ||
| 398 | |||
| 1776 | pub const TotalSystemMemoryError = error{ | 399 | pub const TotalSystemMemoryError = error{ |
| 1777 | UnknownTotalSystemMemory, | 400 | UnknownTotalSystemMemory, |
| 1778 | }; | 401 | }; |
| ... | @@ -1903,215 +526,6 @@ test raiseFileDescriptorLimit { | ... | @@ -1903,215 +526,6 @@ test raiseFileDescriptorLimit { |
| 1903 | raiseFileDescriptorLimit(); | 526 | raiseFileDescriptorLimit(); |
| 1904 | } | 527 | } |
| 1905 | 528 | ||
| 1906 | pub const CreateEnvironOptions = struct { | ||
| 1907 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. | ||
| 1908 | /// If non-null, negative means to remove the environment variable, and >= 0 | ||
| 1909 | /// means to provide it with the given integer. | ||
| 1910 | zig_progress_fd: ?i32 = null, | ||
| 1911 | }; | ||
| 1912 | |||
| 1913 | /// Creates a null-delimited environment variable block in the format | ||
| 1914 | /// expected by POSIX, from a hash map plus options. | ||
| 1915 | pub fn createEnvironFromMap( | ||
| 1916 | arena: Allocator, | ||
| 1917 | map: *const EnvMap, | ||
| 1918 | options: CreateEnvironOptions, | ||
| 1919 | ) Allocator.Error![:null]?[*:0]u8 { | ||
| 1920 | const ZigProgressAction = enum { nothing, edit, delete, add }; | ||
| 1921 | const zig_progress_action: ZigProgressAction = a: { | ||
| 1922 | const fd = options.zig_progress_fd orelse break :a .nothing; | ||
| 1923 | const contains = map.get("ZIG_PROGRESS") != null; | ||
| 1924 | if (fd >= 0) { | ||
| 1925 | break :a if (contains) .edit else .add; | ||
| 1926 | } else { | ||
| 1927 | if (contains) break :a .delete; | ||
| 1928 | } | ||
| 1929 | break :a .nothing; | ||
| 1930 | }; | ||
| 1931 | |||
| 1932 | const envp_count: usize = c: { | ||
| 1933 | var count: usize = map.count(); | ||
| 1934 | switch (zig_progress_action) { | ||
| 1935 | .add => count += 1, | ||
| 1936 | .delete => count -= 1, | ||
| 1937 | .nothing, .edit => {}, | ||
| 1938 | } | ||
| 1939 | break :c count; | ||
| 1940 | }; | ||
| 1941 | |||
| 1942 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | ||
| 1943 | var i: usize = 0; | ||
| 1944 | |||
| 1945 | if (zig_progress_action == .add) { | ||
| 1946 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | ||
| 1947 | i += 1; | ||
| 1948 | } | ||
| 1949 | |||
| 1950 | { | ||
| 1951 | var it = map.iterator(); | ||
| 1952 | while (it.next()) |pair| { | ||
| 1953 | if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { | ||
| 1954 | .add => unreachable, | ||
| 1955 | .delete => continue, | ||
| 1956 | .edit => { | ||
| 1957 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ | ||
| 1958 | pair.key_ptr.*, options.zig_progress_fd.?, | ||
| 1959 | }, 0); | ||
| 1960 | i += 1; | ||
| 1961 | continue; | ||
| 1962 | }, | ||
| 1963 | .nothing => {}, | ||
| 1964 | }; | ||
| 1965 | |||
| 1966 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); | ||
| 1967 | i += 1; | ||
| 1968 | } | ||
| 1969 | } | ||
| 1970 | |||
| 1971 | assert(i == envp_count); | ||
| 1972 | return envp_buf; | ||
| 1973 | } | ||
| 1974 | |||
| 1975 | /// Creates a null-delimited environment variable block in the format | ||
| 1976 | /// expected by POSIX, from a hash map plus options. | ||
| 1977 | pub fn createEnvironFromExisting( | ||
| 1978 | arena: Allocator, | ||
| 1979 | existing: [*:null]const ?[*:0]const u8, | ||
| 1980 | options: CreateEnvironOptions, | ||
| 1981 | ) Allocator.Error![:null]?[*:0]u8 { | ||
| 1982 | const existing_count, const contains_zig_progress = c: { | ||
| 1983 | var count: usize = 0; | ||
| 1984 | var contains = false; | ||
| 1985 | while (existing[count]) |line| : (count += 1) { | ||
| 1986 | contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS"); | ||
| 1987 | } | ||
| 1988 | break :c .{ count, contains }; | ||
| 1989 | }; | ||
| 1990 | const ZigProgressAction = enum { nothing, edit, delete, add }; | ||
| 1991 | const zig_progress_action: ZigProgressAction = a: { | ||
| 1992 | const fd = options.zig_progress_fd orelse break :a .nothing; | ||
| 1993 | if (fd >= 0) { | ||
| 1994 | break :a if (contains_zig_progress) .edit else .add; | ||
| 1995 | } else { | ||
| 1996 | if (contains_zig_progress) break :a .delete; | ||
| 1997 | } | ||
| 1998 | break :a .nothing; | ||
| 1999 | }; | ||
| 2000 | |||
| 2001 | const envp_count: usize = c: { | ||
| 2002 | var count: usize = existing_count; | ||
| 2003 | switch (zig_progress_action) { | ||
| 2004 | .add => count += 1, | ||
| 2005 | .delete => count -= 1, | ||
| 2006 | .nothing, .edit => {}, | ||
| 2007 | } | ||
| 2008 | break :c count; | ||
| 2009 | }; | ||
| 2010 | |||
| 2011 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | ||
| 2012 | var i: usize = 0; | ||
| 2013 | var existing_index: usize = 0; | ||
| 2014 | |||
| 2015 | if (zig_progress_action == .add) { | ||
| 2016 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | ||
| 2017 | i += 1; | ||
| 2018 | } | ||
| 2019 | |||
| 2020 | while (existing[existing_index]) |line| : (existing_index += 1) { | ||
| 2021 | if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { | ||
| 2022 | .add => unreachable, | ||
| 2023 | .delete => continue, | ||
| 2024 | .edit => { | ||
| 2025 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | ||
| 2026 | i += 1; | ||
| 2027 | continue; | ||
| 2028 | }, | ||
| 2029 | .nothing => {}, | ||
| 2030 | }; | ||
| 2031 | envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); | ||
| 2032 | i += 1; | ||
| 2033 | } | ||
| 2034 | |||
| 2035 | assert(i == envp_count); | ||
| 2036 | return envp_buf; | ||
| 2037 | } | ||
| 2038 | |||
| 2039 | pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 { | ||
| 2040 | return createEnvironFromMap(arena, env_map, .{}); | ||
| 2041 | } | ||
| 2042 | |||
| 2043 | test createNullDelimitedEnvMap { | ||
| 2044 | const allocator = testing.allocator; | ||
| 2045 | var envmap = EnvMap.init(allocator); | ||
| 2046 | defer envmap.deinit(); | ||
| 2047 | |||
| 2048 | try envmap.put("HOME", "/home/ifreund"); | ||
| 2049 | try envmap.put("WAYLAND_DISPLAY", "wayland-1"); | ||
| 2050 | try envmap.put("DISPLAY", ":1"); | ||
| 2051 | try envmap.put("DEBUGINFOD_URLS", " "); | ||
| 2052 | try envmap.put("XCURSOR_SIZE", "24"); | ||
| 2053 | |||
| 2054 | var arena = std.heap.ArenaAllocator.init(allocator); | ||
| 2055 | defer arena.deinit(); | ||
| 2056 | const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap); | ||
| 2057 | |||
| 2058 | try testing.expectEqual(@as(usize, 5), environ.len); | ||
| 2059 | |||
| 2060 | inline for (.{ | ||
| 2061 | "HOME=/home/ifreund", | ||
| 2062 | "WAYLAND_DISPLAY=wayland-1", | ||
| 2063 | "DISPLAY=:1", | ||
| 2064 | "DEBUGINFOD_URLS= ", | ||
| 2065 | "XCURSOR_SIZE=24", | ||
| 2066 | }) |target| { | ||
| 2067 | for (environ) |variable| { | ||
| 2068 | if (mem.eql(u8, mem.span(variable orelse continue), target)) break; | ||
| 2069 | } else { | ||
| 2070 | try testing.expect(false); // Environment variable not found | ||
| 2071 | } | ||
| 2072 | } | ||
| 2073 | } | ||
| 2074 | |||
| 2075 | /// Caller must free result. | ||
| 2076 | pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 { | ||
| 2077 | // count bytes needed | ||
| 2078 | const max_chars_needed = x: { | ||
| 2079 | // Only need 2 trailing NUL code units for an empty environment | ||
| 2080 | var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; | ||
| 2081 | var it = env_map.iterator(); | ||
| 2082 | while (it.next()) |pair| { | ||
| 2083 | // +1 for '=' | ||
| 2084 | // +1 for null byte | ||
| 2085 | max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; | ||
| 2086 | } | ||
| 2087 | break :x max_chars_needed; | ||
| 2088 | }; | ||
| 2089 | const result = try allocator.alloc(u16, max_chars_needed); | ||
| 2090 | errdefer allocator.free(result); | ||
| 2091 | |||
| 2092 | var it = env_map.iterator(); | ||
| 2093 | var i: usize = 0; | ||
| 2094 | while (it.next()) |pair| { | ||
| 2095 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | ||
| 2096 | result[i] = '='; | ||
| 2097 | i += 1; | ||
| 2098 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | ||
| 2099 | result[i] = 0; | ||
| 2100 | i += 1; | ||
| 2101 | } | ||
| 2102 | result[i] = 0; | ||
| 2103 | i += 1; | ||
| 2104 | // An empty environment is a special case that requires a redundant | ||
| 2105 | // NUL terminator. CreateProcess will read the second code unit even | ||
| 2106 | // though theoretically the first should be enough to recognize that the | ||
| 2107 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) | ||
| 2108 | if (env_map.count() == 0) { | ||
| 2109 | result[i] = 0; | ||
| 2110 | i += 1; | ||
| 2111 | } | ||
| 2112 | return try allocator.realloc(result, i); | ||
| 2113 | } | ||
| 2114 | |||
| 2115 | /// Logs an error and then terminates the process with exit code 1. | 529 | /// Logs an error and then terminates the process with exit code 1. |
| 2116 | pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { | 530 | pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { |
| 2117 | std.log.err(format, format_arguments); | 531 | std.log.err(format, format_arguments); |
lib/std/process/Args.zig created+958| ... | @@ -0,0 +1,958 @@ | ||
| 1 | const Args = @This(); | ||
| 2 | |||
| 3 | const builtin = @import("builtin"); | ||
| 4 | const native_os = builtin.os.tag; | ||
| 5 | |||
| 6 | const std = @import("../std.zig"); | ||
| 7 | const Allocator = std.mem.Allocator; | ||
| 8 | const assert = std.debug.assert; | ||
| 9 | const testing = std.debug.testing; | ||
| 10 | |||
| 11 | vector: Vector, | ||
| 12 | |||
| 13 | pub const Vector = switch (native_os) { | ||
| 14 | .windows => []const u16, // WTF-16 encoded | ||
| 15 | else => []const [*:0]const u8, | ||
| 16 | }; | ||
| 17 | |||
| 18 | /// Cross-platform access to command line one argument at a time. | ||
| 19 | pub const Iterator = struct { | ||
| 20 | const Inner = switch (native_os) { | ||
| 21 | .windows => Windows, | ||
| 22 | .wasi => if (builtin.link_libc) Posix else Wasi, | ||
| 23 | else => Posix, | ||
| 24 | }; | ||
| 25 | |||
| 26 | inner: Inner, | ||
| 27 | |||
| 28 | /// Initialize the args iterator. Consider using `initAllocator` instead | ||
| 29 | /// for cross-platform compatibility. | ||
| 30 | pub fn init(a: Args) Iterator { | ||
| 31 | if (native_os == .wasi) { | ||
| 32 | @compileError("In WASI, use initAllocator instead."); | ||
| 33 | } | ||
| 34 | if (native_os == .windows) { | ||
| 35 | @compileError("In Windows, use initAllocator instead."); | ||
| 36 | } | ||
| 37 | |||
| 38 | return .{ .inner = .init(a) }; | ||
| 39 | } | ||
| 40 | |||
| 41 | pub const InitError = Inner.InitError; | ||
| 42 | |||
| 43 | /// You must deinitialize iterator's internal buffers by calling `deinit` when done. | ||
| 44 | pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator { | ||
| 45 | if (native_os == .wasi and !builtin.link_libc) { | ||
| 46 | return .{ .inner = try .init(a, gpa) }; | ||
| 47 | } | ||
| 48 | if (native_os == .windows) { | ||
| 49 | return .{ .inner = try .init(a, gpa) }; | ||
| 50 | } | ||
| 51 | |||
| 52 | return .{ .inner = .init(a) }; | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Return subsequent argument, or `null` if no more remaining. | ||
| 56 | /// | ||
| 57 | /// Returned slice is pointing to the iterator's internal buffer. | ||
| 58 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 59 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 60 | pub fn next(it: *Iterator) ?([:0]const u8) { | ||
| 61 | return it.inner.next(); | ||
| 62 | } | ||
| 63 | |||
| 64 | /// Parse past 1 argument without capturing it. | ||
| 65 | /// Returns `true` if skipped an arg, `false` if we are at the end. | ||
| 66 | pub fn skip(it: *Iterator) bool { | ||
| 67 | return it.inner.skip(); | ||
| 68 | } | ||
| 69 | |||
| 70 | /// Required to release resources if the iterator was initialized with | ||
| 71 | /// `initAllocator` function. | ||
| 72 | pub fn deinit(it: *Iterator) void { | ||
| 73 | // Unless we're targeting WASI or Windows, this is a no-op. | ||
| 74 | if (native_os == .wasi and !builtin.link_libc) it.inner.deinit(); | ||
| 75 | if (native_os == .windows) it.inner.deinit(); | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Iterator that implements the Windows command-line parsing algorithm. | ||
| 79 | /// | ||
| 80 | /// The implementation is intended to be compatible with the post-2008 C runtime, | ||
| 81 | /// but is *not* intended to be compatible with `CommandLineToArgvW` since | ||
| 82 | /// `CommandLineToArgvW` uses the pre-2008 parsing rules. | ||
| 83 | /// | ||
| 84 | /// This iterator faithfully implements the parsing behavior observed from the C runtime with | ||
| 85 | /// one exception: if the command-line string is empty, the iterator will immediately complete | ||
| 86 | /// without returning any arguments (whereas the C runtime will return a single argument | ||
| 87 | /// representing the name of the current executable). | ||
| 88 | /// | ||
| 89 | /// The essential parts of the algorithm are described in Microsoft's documentation: | ||
| 90 | /// | ||
| 91 | /// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments | ||
| 92 | /// | ||
| 93 | /// David Deley explains some additional undocumented quirks in great detail: | ||
| 94 | /// | ||
| 95 | /// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES | ||
| 96 | pub const Windows = struct { | ||
| 97 | allocator: Allocator, | ||
| 98 | /// Encoded as WTF-16 LE. | ||
| 99 | cmd_line: []const u16, | ||
| 100 | index: usize = 0, | ||
| 101 | /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices | ||
| 102 | /// of each argument encoded as WTF-8. | ||
| 103 | buffer: []u8, | ||
| 104 | start: usize = 0, | ||
| 105 | end: usize = 0, | ||
| 106 | |||
| 107 | pub const InitError = error{OutOfMemory}; | ||
| 108 | |||
| 109 | /// `cmd_line_w` *must* be a WTF16-LE-encoded string. | ||
| 110 | /// | ||
| 111 | /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for | ||
| 112 | /// at least as long as the returned Windows. | ||
| 113 | pub fn init(allocator: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows { | ||
| 114 | const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w); | ||
| 115 | |||
| 116 | // This buffer must be large enough to contain contiguous NUL-terminated slices | ||
| 117 | // of each argument. | ||
| 118 | // - During parsing, the length of a parsed argument will always be equal to | ||
| 119 | // to less than its unparsed length | ||
| 120 | // - The first argument needs one extra byte of space allocated for its NUL | ||
| 121 | // terminator, but for each subsequent argument the necessary whitespace | ||
| 122 | // between arguments guarantees room for their NUL terminator(s). | ||
| 123 | const buffer = try allocator.alloc(u8, wtf8_len + 1); | ||
| 124 | errdefer allocator.free(buffer); | ||
| 125 | |||
| 126 | return .{ | ||
| 127 | .allocator = allocator, | ||
| 128 | .cmd_line = cmd_line_w, | ||
| 129 | .buffer = buffer, | ||
| 130 | }; | ||
| 131 | } | ||
| 132 | |||
| 133 | /// Returns the next argument and advances the iterator. Returns `null` if at the end of the | ||
| 134 | /// command-line string. The iterator owns the returned slice. | ||
| 135 | /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 136 | pub fn next(self: *Windows) ?[:0]const u8 { | ||
| 137 | return self.nextWithStrategy(next_strategy); | ||
| 138 | } | ||
| 139 | |||
| 140 | /// Skips the next argument and advances the iterator. Returns `true` if an argument was | ||
| 141 | /// skipped, `false` if at the end of the command-line string. | ||
| 142 | pub fn skip(self: *Windows) bool { | ||
| 143 | return self.nextWithStrategy(skip_strategy); | ||
| 144 | } | ||
| 145 | |||
| 146 | const next_strategy = struct { | ||
| 147 | const T = ?[:0]const u8; | ||
| 148 | |||
| 149 | const eof = null; | ||
| 150 | |||
| 151 | /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`. | ||
| 152 | fn emitBackslashes(self: *Windows, count: usize, last_emitted_code_unit: ?u16) ?u16 { | ||
| 153 | for (0..count) |_| { | ||
| 154 | self.buffer[self.end] = '\\'; | ||
| 155 | self.end += 1; | ||
| 156 | } | ||
| 157 | return if (count != 0) '\\' else last_emitted_code_unit; | ||
| 158 | } | ||
| 159 | |||
| 160 | /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then | ||
| 161 | /// the previously emitted high surrogate is overwritten by the codepoint encoded | ||
| 162 | /// by the surrogate pair, and `null` is returned. | ||
| 163 | /// Otherwise, `code_unit` is emitted and returned. | ||
| 164 | fn emitCharacter(self: *Windows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 { | ||
| 165 | // Because we are emitting WTF-8, we need to | ||
| 166 | // check to see if we've emitted two consecutive surrogate | ||
| 167 | // codepoints that form a valid surrogate pair in order | ||
| 168 | // to ensure that we're always emitting well-formed WTF-8 | ||
| 169 | // (https://wtf-8.codeberg.page/#concatenating). | ||
| 170 | // | ||
| 171 | // If we do have a valid surrogate pair, we need to emit | ||
| 172 | // the UTF-8 sequence for the codepoint that they encode | ||
| 173 | // instead of the WTF-8 encoding for the two surrogate pairs | ||
| 174 | // separately. | ||
| 175 | // | ||
| 176 | // This is relevant when dealing with a WTF-16 encoded | ||
| 177 | // command line like this: | ||
| 178 | // "<0xD801>"<0xDC37> | ||
| 179 | // which would get parsed and converted to WTF-8 as: | ||
| 180 | // <0xED><0xA0><0x81><0xED><0xB0><0xB7> | ||
| 181 | // but instead, we need to recognize the surrogate pair | ||
| 182 | // and emit the codepoint it encodes, which in this | ||
| 183 | // example is U+10437 (𐐷), which is encoded in UTF-8 as: | ||
| 184 | // <0xF0><0x90><0x90><0xB7> | ||
| 185 | if (last_emitted_code_unit != null and | ||
| 186 | std.unicode.utf16IsLowSurrogate(code_unit) and | ||
| 187 | std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?)) | ||
| 188 | { | ||
| 189 | const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable; | ||
| 190 | |||
| 191 | // Unpaired surrogate is 3 bytes long | ||
| 192 | const dest = self.buffer[self.end - 3 ..]; | ||
| 193 | const len = std.unicode.utf8Encode(codepoint, dest) catch unreachable; | ||
| 194 | // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes | ||
| 195 | assert(len == 4); | ||
| 196 | self.end += 1; | ||
| 197 | return null; | ||
| 198 | } | ||
| 199 | |||
| 200 | const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable; | ||
| 201 | self.end += wtf8_len; | ||
| 202 | return code_unit; | ||
| 203 | } | ||
| 204 | |||
| 205 | fn yieldArg(self: *Windows) [:0]const u8 { | ||
| 206 | self.buffer[self.end] = 0; | ||
| 207 | const arg = self.buffer[self.start..self.end :0]; | ||
| 208 | self.end += 1; | ||
| 209 | self.start = self.end; | ||
| 210 | return arg; | ||
| 211 | } | ||
| 212 | }; | ||
| 213 | |||
| 214 | const skip_strategy = struct { | ||
| 215 | const T = bool; | ||
| 216 | |||
| 217 | const eof = false; | ||
| 218 | |||
| 219 | fn emitBackslashes(_: *Windows, _: usize, last_emitted_code_unit: ?u16) ?u16 { | ||
| 220 | return last_emitted_code_unit; | ||
| 221 | } | ||
| 222 | |||
| 223 | fn emitCharacter(_: *Windows, _: u16, last_emitted_code_unit: ?u16) ?u16 { | ||
| 224 | return last_emitted_code_unit; | ||
| 225 | } | ||
| 226 | |||
| 227 | fn yieldArg(_: *Windows) bool { | ||
| 228 | return true; | ||
| 229 | } | ||
| 230 | }; | ||
| 231 | |||
| 232 | fn nextWithStrategy(self: *Windows, comptime strategy: type) strategy.T { | ||
| 233 | var last_emitted_code_unit: ?u16 = null; | ||
| 234 | // The first argument (the executable name) uses different parsing rules. | ||
| 235 | if (self.index == 0) { | ||
| 236 | if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) { | ||
| 237 | // Immediately complete the iterator. | ||
| 238 | // The C runtime would return the name of the current executable here. | ||
| 239 | return strategy.eof; | ||
| 240 | } | ||
| 241 | |||
| 242 | var inside_quotes = false; | ||
| 243 | while (true) : (self.index += 1) { | ||
| 244 | const char = if (self.index != self.cmd_line.len) | ||
| 245 | std.mem.littleToNative(u16, self.cmd_line[self.index]) | ||
| 246 | else | ||
| 247 | 0; | ||
| 248 | switch (char) { | ||
| 249 | 0 => { | ||
| 250 | return strategy.yieldArg(self); | ||
| 251 | }, | ||
| 252 | '"' => { | ||
| 253 | inside_quotes = !inside_quotes; | ||
| 254 | }, | ||
| 255 | ' ', '\t' => { | ||
| 256 | if (inside_quotes) { | ||
| 257 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 258 | } else { | ||
| 259 | self.index += 1; | ||
| 260 | return strategy.yieldArg(self); | ||
| 261 | } | ||
| 262 | }, | ||
| 263 | else => { | ||
| 264 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 265 | }, | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | // Skip spaces and tabs. The iterator completes if we reach the end of the string here. | ||
| 271 | while (true) : (self.index += 1) { | ||
| 272 | const char = if (self.index != self.cmd_line.len) | ||
| 273 | std.mem.littleToNative(u16, self.cmd_line[self.index]) | ||
| 274 | else | ||
| 275 | 0; | ||
| 276 | switch (char) { | ||
| 277 | 0 => return strategy.eof, | ||
| 278 | ' ', '\t' => continue, | ||
| 279 | else => break, | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | // Parsing rules for subsequent arguments: | ||
| 284 | // | ||
| 285 | // - The end of the string always terminates the current argument. | ||
| 286 | // - When not in 'inside_quotes' mode, a space or tab terminates the current argument. | ||
| 287 | // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero). | ||
| 288 | // If in 'inside_quotes' and the quote is immediately followed by a second quote, | ||
| 289 | // one quote is emitted and the other is skipped, otherwise, the quote is skipped | ||
| 290 | // and 'inside_quotes' is toggled. | ||
| 291 | // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote. | ||
| 292 | // - n backslashes not followed by a quote emit n backslashes. | ||
| 293 | var backslash_count: usize = 0; | ||
| 294 | var inside_quotes = false; | ||
| 295 | while (true) : (self.index += 1) { | ||
| 296 | const char = if (self.index != self.cmd_line.len) | ||
| 297 | std.mem.littleToNative(u16, self.cmd_line[self.index]) | ||
| 298 | else | ||
| 299 | 0; | ||
| 300 | switch (char) { | ||
| 301 | 0 => { | ||
| 302 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); | ||
| 303 | return strategy.yieldArg(self); | ||
| 304 | }, | ||
| 305 | ' ', '\t' => { | ||
| 306 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); | ||
| 307 | backslash_count = 0; | ||
| 308 | if (inside_quotes) { | ||
| 309 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 310 | } else return strategy.yieldArg(self); | ||
| 311 | }, | ||
| 312 | '"' => { | ||
| 313 | const char_is_escaped_quote = backslash_count % 2 != 0; | ||
| 314 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit); | ||
| 315 | backslash_count = 0; | ||
| 316 | if (char_is_escaped_quote) { | ||
| 317 | last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); | ||
| 318 | } else { | ||
| 319 | if (inside_quotes and | ||
| 320 | self.index + 1 != self.cmd_line.len and | ||
| 321 | std.mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"') | ||
| 322 | { | ||
| 323 | last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); | ||
| 324 | self.index += 1; | ||
| 325 | } else { | ||
| 326 | inside_quotes = !inside_quotes; | ||
| 327 | } | ||
| 328 | } | ||
| 329 | }, | ||
| 330 | '\\' => { | ||
| 331 | backslash_count += 1; | ||
| 332 | }, | ||
| 333 | else => { | ||
| 334 | last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); | ||
| 335 | backslash_count = 0; | ||
| 336 | last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); | ||
| 337 | }, | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | |||
| 342 | /// Frees the iterator's copy of the command-line string and all previously returned | ||
| 343 | /// argument slices. | ||
| 344 | pub fn deinit(self: *Windows) void { | ||
| 345 | self.allocator.free(self.buffer); | ||
| 346 | } | ||
| 347 | }; | ||
| 348 | |||
| 349 | pub const Posix = struct { | ||
| 350 | remaining: Vector, | ||
| 351 | |||
| 352 | pub const InitError = error{}; | ||
| 353 | |||
| 354 | pub fn init(a: Args) Posix { | ||
| 355 | return .{ .remaining = a.vector }; | ||
| 356 | } | ||
| 357 | |||
| 358 | pub fn next(it: *Posix) ?[:0]const u8 { | ||
| 359 | if (it.remaining.len == 0) return null; | ||
| 360 | const arg = it.remaining[0]; | ||
| 361 | it.remaining = it.remaining[1..]; | ||
| 362 | return std.mem.sliceTo(arg, 0); | ||
| 363 | } | ||
| 364 | |||
| 365 | pub fn skip(it: *Posix) bool { | ||
| 366 | if (it.remaining.len == 0) return false; | ||
| 367 | it.remaining = it.remaining[1..]; | ||
| 368 | return true; | ||
| 369 | } | ||
| 370 | }; | ||
| 371 | |||
| 372 | pub const Wasi = struct { | ||
| 373 | allocator: Allocator, | ||
| 374 | index: usize, | ||
| 375 | args: [][:0]u8, | ||
| 376 | |||
| 377 | pub const InitError = error{OutOfMemory} || std.posix.UnexpectedError; | ||
| 378 | |||
| 379 | /// You must call deinit to free the internal buffer of the | ||
| 380 | /// iterator after you are done. | ||
| 381 | pub fn init(allocator: Allocator) Wasi.InitError!Wasi { | ||
| 382 | const fetched_args = try Wasi.internalInit(allocator); | ||
| 383 | return Wasi{ | ||
| 384 | .allocator = allocator, | ||
| 385 | .index = 0, | ||
| 386 | .args = fetched_args, | ||
| 387 | }; | ||
| 388 | } | ||
| 389 | |||
| 390 | fn internalInit(allocator: Allocator) Wasi.InitError![][:0]u8 { | ||
| 391 | var count: usize = undefined; | ||
| 392 | var buf_size: usize = undefined; | ||
| 393 | |||
| 394 | switch (std.os.wasi.args_sizes_get(&count, &buf_size)) { | ||
| 395 | .SUCCESS => {}, | ||
| 396 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 397 | } | ||
| 398 | |||
| 399 | if (count == 0) { | ||
| 400 | return &[_][:0]u8{}; | ||
| 401 | } | ||
| 402 | |||
| 403 | const argv = try allocator.alloc([*:0]u8, count); | ||
| 404 | defer allocator.free(argv); | ||
| 405 | |||
| 406 | const argv_buf = try allocator.alloc(u8, buf_size); | ||
| 407 | |||
| 408 | switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) { | ||
| 409 | .SUCCESS => {}, | ||
| 410 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 411 | } | ||
| 412 | |||
| 413 | var result_args = try allocator.alloc([:0]u8, count); | ||
| 414 | var i: usize = 0; | ||
| 415 | while (i < count) : (i += 1) { | ||
| 416 | result_args[i] = std.mem.sliceTo(argv[i], 0); | ||
| 417 | } | ||
| 418 | |||
| 419 | return result_args; | ||
| 420 | } | ||
| 421 | |||
| 422 | pub fn next(self: *Wasi) ?[:0]const u8 { | ||
| 423 | if (self.index == self.args.len) return null; | ||
| 424 | |||
| 425 | const arg = self.args[self.index]; | ||
| 426 | self.index += 1; | ||
| 427 | return arg; | ||
| 428 | } | ||
| 429 | |||
| 430 | pub fn skip(self: *Wasi) bool { | ||
| 431 | if (self.index == self.args.len) return false; | ||
| 432 | |||
| 433 | self.index += 1; | ||
| 434 | return true; | ||
| 435 | } | ||
| 436 | |||
| 437 | /// Call to free the internal buffer of the iterator. | ||
| 438 | pub fn deinit(self: *Wasi) void { | ||
| 439 | // Nothing is allocated when there are no args | ||
| 440 | if (self.args.len == 0) return; | ||
| 441 | |||
| 442 | const last_item = self.args[self.args.len - 1]; | ||
| 443 | const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated | ||
| 444 | const first_item_ptr = self.args[0].ptr; | ||
| 445 | const len = last_byte_addr - @intFromPtr(first_item_ptr); | ||
| 446 | self.allocator.free(first_item_ptr[0..len]); | ||
| 447 | self.allocator.free(self.args); | ||
| 448 | } | ||
| 449 | }; | ||
| 450 | }; | ||
| 451 | |||
| 452 | /// Holds the command-line arguments, with the program name as the first entry. | ||
| 453 | /// Use `iterateAllocator` for cross-platform code. | ||
| 454 | pub fn iterate(a: Args) Iterator { | ||
| 455 | return .init(a); | ||
| 456 | } | ||
| 457 | |||
| 458 | /// You must deinitialize iterator's internal buffers by calling `deinit` when | ||
| 459 | /// done. | ||
| 460 | pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator { | ||
| 461 | return .initAllocator(a, gpa); | ||
| 462 | } | ||
| 463 | |||
| 464 | /// Returned value may reference several allocations; call `freeSlice` to | ||
| 465 | /// release. | ||
| 466 | /// | ||
| 467 | /// * On Windows, the result is encoded as | ||
| 468 | /// [WTF-8](https://wtf-8.codeberg.page/). | ||
| 469 | /// * On other platforms, the result is an opaque sequence of bytes with no | ||
| 470 | /// particular encoding. | ||
| 471 | pub fn toSlice(a: Args, gpa: Allocator) Allocator.Error![][:0]u8 { | ||
| 472 | var it = try a.iterateAllocator(gpa); | ||
| 473 | defer it.deinit(); | ||
| 474 | |||
| 475 | var contents = std.array_list.Managed(u8).init(gpa); | ||
| 476 | defer contents.deinit(); | ||
| 477 | |||
| 478 | var slice_list = std.array_list.Managed(usize).init(gpa); | ||
| 479 | defer slice_list.deinit(); | ||
| 480 | |||
| 481 | while (it.next()) |arg| { | ||
| 482 | try contents.appendSlice(arg[0 .. arg.len + 1]); | ||
| 483 | try slice_list.append(arg.len); | ||
| 484 | } | ||
| 485 | |||
| 486 | const contents_slice = contents.items; | ||
| 487 | const slice_sizes = slice_list.items; | ||
| 488 | const slice_list_bytes = std.math.mul(usize, @sizeOf([]u8), slice_sizes.len) catch return error.OutOfMemory; | ||
| 489 | const total_bytes = std.math.add(usize, slice_list_bytes, contents_slice.len) catch return error.OutOfMemory; | ||
| 490 | const buf = try gpa.alignedAlloc(u8, .of([]u8), total_bytes); | ||
| 491 | errdefer gpa.free(buf); | ||
| 492 | |||
| 493 | const result_slice_list = std.mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]); | ||
| 494 | const result_contents = buf[slice_list_bytes..]; | ||
| 495 | @memcpy(result_contents[0..contents_slice.len], contents_slice); | ||
| 496 | |||
| 497 | var contents_index: usize = 0; | ||
| 498 | for (slice_sizes, 0..) |len, i| { | ||
| 499 | const new_index = contents_index + len; | ||
| 500 | result_slice_list[i] = result_contents[contents_index..new_index :0]; | ||
| 501 | contents_index = new_index + 1; | ||
| 502 | } | ||
| 503 | |||
| 504 | return result_slice_list; | ||
| 505 | } | ||
| 506 | |||
| 507 | /// Frees memory allocate by `toSlice`. | ||
| 508 | pub fn freeSlice(gpa: Allocator, to_slice_result: []const [:0]u8) void { | ||
| 509 | var total_bytes: usize = 0; | ||
| 510 | for (to_slice_result) |arg| { | ||
| 511 | total_bytes += @sizeOf([]u8) + arg.len + 1; | ||
| 512 | } | ||
| 513 | const unaligned_allocated_buf = @as([*]const u8, @ptrCast(to_slice_result.ptr))[0..total_bytes]; | ||
| 514 | const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf); | ||
| 515 | return gpa.free(aligned_allocated_buf); | ||
| 516 | } | ||
| 517 | |||
| 518 | test "Iterator.Windows" { | ||
| 519 | const t = testArgIteratorWindows; | ||
| 520 | |||
| 521 | try t( | ||
| 522 | \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" | ||
| 523 | , &.{ | ||
| 524 | \\C:\Program Files\zig\zig.exe | ||
| 525 | , | ||
| 526 | \\run | ||
| 527 | , | ||
| 528 | \\.\src\main.zig | ||
| 529 | , | ||
| 530 | \\-target | ||
| 531 | , | ||
| 532 | \\x86_64-windows-gnu | ||
| 533 | , | ||
| 534 | \\-O | ||
| 535 | , | ||
| 536 | \\ReleaseSafe | ||
| 537 | , | ||
| 538 | \\-- | ||
| 539 | , | ||
| 540 | \\--emoji=🗿 | ||
| 541 | , | ||
| 542 | \\--eval=new Regex("Dwayne \"The Rock\" Johnson") | ||
| 543 | , | ||
| 544 | }); | ||
| 545 | |||
| 546 | // Empty | ||
| 547 | try t("", &.{}); | ||
| 548 | |||
| 549 | // Separators | ||
| 550 | try t("aa bb cc", &.{ "aa", "bb", "cc" }); | ||
| 551 | try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" }); | ||
| 552 | try t("aa\nbb\ncc", &.{"aa\nbb\ncc"}); | ||
| 553 | try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"}); | ||
| 554 | try t("aa\rbb\rcc", &.{"aa\rbb\rcc"}); | ||
| 555 | try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"}); | ||
| 556 | try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"}); | ||
| 557 | try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"}); | ||
| 558 | |||
| 559 | // Leading/trailing whitespace | ||
| 560 | try t(" ", &.{""}); | ||
| 561 | try t(" aa bb ", &.{ "", "aa", "bb" }); | ||
| 562 | try t("\t\t", &.{""}); | ||
| 563 | try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" }); | ||
| 564 | try t("\n\n", &.{"\n\n"}); | ||
| 565 | try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"}); | ||
| 566 | |||
| 567 | // Executable name with quotes/backslashes | ||
| 568 | try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"}); | ||
| 569 | try t("\"", &.{""}); | ||
| 570 | try t("\"\"", &.{""}); | ||
| 571 | try t("\"\"\"", &.{""}); | ||
| 572 | try t("\"\"\"\"", &.{""}); | ||
| 573 | try t("\"\"\"\"\"", &.{""}); | ||
| 574 | try t("aa\"bb\"cc\"dd", &.{"aabbccdd"}); | ||
| 575 | try t("aa\"bb cc\"dd", &.{"aabb ccdd"}); | ||
| 576 | try t("\"aa\\\"bb\"", &.{"aa\\bb"}); | ||
| 577 | try t("\"aa\\\\\"", &.{"aa\\\\"}); | ||
| 578 | try t("aa\\\"bb", &.{"aa\\bb"}); | ||
| 579 | try t("aa\\\\\"bb", &.{"aa\\\\bb"}); | ||
| 580 | |||
| 581 | // Arguments with quotes/backslashes | ||
| 582 | try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" }); | ||
| 583 | try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" }); | ||
| 584 | try t(". ", &.{"."}); | ||
| 585 | try t(". \"", &.{ ".", "" }); | ||
| 586 | try t(". \"\"", &.{ ".", "" }); | ||
| 587 | try t(". \"\"\"", &.{ ".", "\"" }); | ||
| 588 | try t(". \"\"\"\"", &.{ ".", "\"" }); | ||
| 589 | try t(". \"\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 590 | try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 591 | try t(". \" \"", &.{ ".", " " }); | ||
| 592 | try t(". \" \"\"", &.{ ".", " \"" }); | ||
| 593 | try t(". \" \"\"\"", &.{ ".", " \"" }); | ||
| 594 | try t(". \" \"\"\"\"", &.{ ".", " \"\"" }); | ||
| 595 | try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" }); | ||
| 596 | try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" }); | ||
| 597 | try t(". \\\"", &.{ ".", "\"" }); | ||
| 598 | try t(". \\\"\"", &.{ ".", "\"" }); | ||
| 599 | try t(". \\\"\"\"", &.{ ".", "\"" }); | ||
| 600 | try t(". \\\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 601 | try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" }); | ||
| 602 | try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" }); | ||
| 603 | try t(". \" \\\"", &.{ ".", " \"" }); | ||
| 604 | try t(". \" \\\"\"", &.{ ".", " \"" }); | ||
| 605 | try t(". \" \\\"\"\"", &.{ ".", " \"\"" }); | ||
| 606 | try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" }); | ||
| 607 | try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" }); | ||
| 608 | try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" }); | ||
| 609 | try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" }); | ||
| 610 | try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" }); | ||
| 611 | try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" }); | ||
| 612 | |||
| 613 | // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines | ||
| 614 | try t( | ||
| 615 | \\foo.exe "abc" d e | ||
| 616 | , &.{ "foo.exe", "abc", "d", "e" }); | ||
| 617 | try t( | ||
| 618 | \\foo.exe a\\b d"e f"g h | ||
| 619 | , &.{ "foo.exe", "a\\\\b", "de fg", "h" }); | ||
| 620 | try t( | ||
| 621 | \\foo.exe a\\\"b c d | ||
| 622 | , &.{ "foo.exe", "a\\\"b", "c", "d" }); | ||
| 623 | try t( | ||
| 624 | \\foo.exe a\\\\"b c" d e | ||
| 625 | , &.{ "foo.exe", "a\\\\b c", "d", "e" }); | ||
| 626 | try t( | ||
| 627 | \\foo.exe a"b"" c d | ||
| 628 | , &.{ "foo.exe", "ab\" c d" }); | ||
| 629 | |||
| 630 | // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX | ||
| 631 | try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" }); | ||
| 632 | try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" }); | ||
| 633 | try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" }); | ||
| 634 | try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" }); | ||
| 635 | try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" }); | ||
| 636 | try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" }); | ||
| 637 | try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" }); | ||
| 638 | try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" }); | ||
| 639 | try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" }); | ||
| 640 | |||
| 641 | // Surrogate pair encoding of 𐐷 separated by quotes. | ||
| 642 | // Encoded as WTF-16: | ||
| 643 | // "<0xD801>"<0xDC37> | ||
| 644 | // Encoded as WTF-8: | ||
| 645 | // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7> | ||
| 646 | // During parsing, the quotes drop out and the surrogate pair | ||
| 647 | // should end up encoded as its normal UTF-8 representation. | ||
| 648 | try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" }); | ||
| 649 | } | ||
| 650 | |||
| 651 | fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { | ||
| 652 | const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line); | ||
| 653 | defer testing.allocator.free(cmd_line_w); | ||
| 654 | |||
| 655 | // next | ||
| 656 | { | ||
| 657 | var it = try Iterator.Windows.init(testing.allocator, cmd_line_w); | ||
| 658 | defer it.deinit(); | ||
| 659 | |||
| 660 | for (expected_args) |expected| { | ||
| 661 | if (it.next()) |actual| { | ||
| 662 | try testing.expectEqualStrings(expected, actual); | ||
| 663 | } else { | ||
| 664 | return error.TestUnexpectedResult; | ||
| 665 | } | ||
| 666 | } | ||
| 667 | try testing.expect(it.next() == null); | ||
| 668 | } | ||
| 669 | |||
| 670 | // skip | ||
| 671 | { | ||
| 672 | var it = try Iterator.Windows.init(testing.allocator, cmd_line_w); | ||
| 673 | defer it.deinit(); | ||
| 674 | |||
| 675 | for (0..expected_args.len) |_| { | ||
| 676 | try testing.expect(it.skip()); | ||
| 677 | } | ||
| 678 | try testing.expect(!it.skip()); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | |||
| 682 | test "general arg parsing" { | ||
| 683 | try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" }); | ||
| 684 | try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" }); | ||
| 685 | try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" }); | ||
| 686 | try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" }); | ||
| 687 | try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" }); | ||
| 688 | try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" }); | ||
| 689 | try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" }); | ||
| 690 | try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" }); | ||
| 691 | |||
| 692 | try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{ | ||
| 693 | ".\\..\\zig-cache\\build", | ||
| 694 | "bin\\zig.exe", | ||
| 695 | ".\\..", | ||
| 696 | ".\\..\\zig-cache", | ||
| 697 | "--help", | ||
| 698 | }); | ||
| 699 | |||
| 700 | try testGeneralCmdLine( | ||
| 701 | \\ 'foo' "bar" | ||
| 702 | , &.{ "'foo'", "bar" }); | ||
| 703 | } | ||
| 704 | |||
| 705 | fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { | ||
| 706 | var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line); | ||
| 707 | defer it.deinit(); | ||
| 708 | for (expected_args) |expected_arg| { | ||
| 709 | const arg = it.next().?; | ||
| 710 | try testing.expectEqualStrings(expected_arg, arg); | ||
| 711 | } | ||
| 712 | try testing.expect(it.next() == null); | ||
| 713 | } | ||
| 714 | |||
| 715 | /// Optional parameters for `ArgIteratorGeneral` | ||
| 716 | pub const ArgIteratorGeneralOptions = struct { | ||
| 717 | comments: bool = false, | ||
| 718 | single_quotes: bool = false, | ||
| 719 | }; | ||
| 720 | |||
| 721 | /// A general Iterator to parse a string into a set of arguments | ||
| 722 | pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { | ||
| 723 | return struct { | ||
| 724 | allocator: Allocator, | ||
| 725 | index: usize = 0, | ||
| 726 | cmd_line: []const u8, | ||
| 727 | |||
| 728 | /// Should the cmd_line field be free'd (using the allocator) on deinit()? | ||
| 729 | free_cmd_line_on_deinit: bool, | ||
| 730 | |||
| 731 | /// buffer MUST be long enough to hold the cmd_line plus a null terminator. | ||
| 732 | /// buffer will we free'd (using the allocator) on deinit() | ||
| 733 | buffer: []u8, | ||
| 734 | start: usize = 0, | ||
| 735 | end: usize = 0, | ||
| 736 | |||
| 737 | pub const Self = @This(); | ||
| 738 | |||
| 739 | pub const InitError = error{OutOfMemory}; | ||
| 740 | |||
| 741 | /// cmd_line_utf8 MUST remain valid and constant while using this instance | ||
| 742 | pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { | ||
| 743 | const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); | ||
| 744 | errdefer allocator.free(buffer); | ||
| 745 | |||
| 746 | return Self{ | ||
| 747 | .allocator = allocator, | ||
| 748 | .cmd_line = cmd_line_utf8, | ||
| 749 | .free_cmd_line_on_deinit = false, | ||
| 750 | .buffer = buffer, | ||
| 751 | }; | ||
| 752 | } | ||
| 753 | |||
| 754 | /// cmd_line_utf8 will be free'd (with the allocator) on deinit() | ||
| 755 | pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { | ||
| 756 | const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); | ||
| 757 | errdefer allocator.free(buffer); | ||
| 758 | |||
| 759 | return Self{ | ||
| 760 | .allocator = allocator, | ||
| 761 | .cmd_line = cmd_line_utf8, | ||
| 762 | .free_cmd_line_on_deinit = true, | ||
| 763 | .buffer = buffer, | ||
| 764 | }; | ||
| 765 | } | ||
| 766 | |||
| 767 | // Skips over whitespace in the cmd_line. | ||
| 768 | // Returns false if the terminating sentinel is reached, true otherwise. | ||
| 769 | // Also skips over comments (if supported). | ||
| 770 | fn skipWhitespace(self: *Self) bool { | ||
| 771 | while (true) : (self.index += 1) { | ||
| 772 | const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | ||
| 773 | switch (character) { | ||
| 774 | 0 => return false, | ||
| 775 | ' ', '\t', '\r', '\n' => continue, | ||
| 776 | '#' => { | ||
| 777 | if (options.comments) { | ||
| 778 | while (true) : (self.index += 1) { | ||
| 779 | switch (self.cmd_line[self.index]) { | ||
| 780 | '\n' => break, | ||
| 781 | 0 => return false, | ||
| 782 | else => continue, | ||
| 783 | } | ||
| 784 | } | ||
| 785 | continue; | ||
| 786 | } else { | ||
| 787 | break; | ||
| 788 | } | ||
| 789 | }, | ||
| 790 | else => break, | ||
| 791 | } | ||
| 792 | } | ||
| 793 | return true; | ||
| 794 | } | ||
| 795 | |||
| 796 | pub fn skip(self: *Self) bool { | ||
| 797 | if (!self.skipWhitespace()) { | ||
| 798 | return false; | ||
| 799 | } | ||
| 800 | |||
| 801 | var backslash_count: usize = 0; | ||
| 802 | var in_quote = false; | ||
| 803 | while (true) : (self.index += 1) { | ||
| 804 | const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | ||
| 805 | switch (character) { | ||
| 806 | 0 => return true, | ||
| 807 | '"', '\'' => { | ||
| 808 | if (!options.single_quotes and character == '\'') { | ||
| 809 | backslash_count = 0; | ||
| 810 | continue; | ||
| 811 | } | ||
| 812 | const quote_is_real = backslash_count % 2 == 0; | ||
| 813 | if (quote_is_real) { | ||
| 814 | in_quote = !in_quote; | ||
| 815 | } | ||
| 816 | }, | ||
| 817 | '\\' => { | ||
| 818 | backslash_count += 1; | ||
| 819 | }, | ||
| 820 | ' ', '\t', '\r', '\n' => { | ||
| 821 | if (!in_quote) { | ||
| 822 | return true; | ||
| 823 | } | ||
| 824 | backslash_count = 0; | ||
| 825 | }, | ||
| 826 | else => { | ||
| 827 | backslash_count = 0; | ||
| 828 | continue; | ||
| 829 | }, | ||
| 830 | } | ||
| 831 | } | ||
| 832 | } | ||
| 833 | |||
| 834 | /// Returns a slice of the internal buffer that contains the next argument. | ||
| 835 | /// Returns null when it reaches the end. | ||
| 836 | pub fn next(self: *Self) ?[:0]const u8 { | ||
| 837 | if (!self.skipWhitespace()) { | ||
| 838 | return null; | ||
| 839 | } | ||
| 840 | |||
| 841 | var backslash_count: usize = 0; | ||
| 842 | var in_quote = false; | ||
| 843 | while (true) : (self.index += 1) { | ||
| 844 | const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | ||
| 845 | switch (character) { | ||
| 846 | 0 => { | ||
| 847 | self.emitBackslashes(backslash_count); | ||
| 848 | self.buffer[self.end] = 0; | ||
| 849 | const token = self.buffer[self.start..self.end :0]; | ||
| 850 | self.end += 1; | ||
| 851 | self.start = self.end; | ||
| 852 | return token; | ||
| 853 | }, | ||
| 854 | '"', '\'' => { | ||
| 855 | if (!options.single_quotes and character == '\'') { | ||
| 856 | self.emitBackslashes(backslash_count); | ||
| 857 | backslash_count = 0; | ||
| 858 | self.emitCharacter(character); | ||
| 859 | continue; | ||
| 860 | } | ||
| 861 | const quote_is_real = backslash_count % 2 == 0; | ||
| 862 | self.emitBackslashes(backslash_count / 2); | ||
| 863 | backslash_count = 0; | ||
| 864 | |||
| 865 | if (quote_is_real) { | ||
| 866 | in_quote = !in_quote; | ||
| 867 | } else { | ||
| 868 | self.emitCharacter('"'); | ||
| 869 | } | ||
| 870 | }, | ||
| 871 | '\\' => { | ||
| 872 | backslash_count += 1; | ||
| 873 | }, | ||
| 874 | ' ', '\t', '\r', '\n' => { | ||
| 875 | self.emitBackslashes(backslash_count); | ||
| 876 | backslash_count = 0; | ||
| 877 | if (in_quote) { | ||
| 878 | self.emitCharacter(character); | ||
| 879 | } else { | ||
| 880 | self.buffer[self.end] = 0; | ||
| 881 | const token = self.buffer[self.start..self.end :0]; | ||
| 882 | self.end += 1; | ||
| 883 | self.start = self.end; | ||
| 884 | return token; | ||
| 885 | } | ||
| 886 | }, | ||
| 887 | else => { | ||
| 888 | self.emitBackslashes(backslash_count); | ||
| 889 | backslash_count = 0; | ||
| 890 | self.emitCharacter(character); | ||
| 891 | }, | ||
| 892 | } | ||
| 893 | } | ||
| 894 | } | ||
| 895 | |||
| 896 | fn emitBackslashes(self: *Self, emit_count: usize) void { | ||
| 897 | var i: usize = 0; | ||
| 898 | while (i < emit_count) : (i += 1) { | ||
| 899 | self.emitCharacter('\\'); | ||
| 900 | } | ||
| 901 | } | ||
| 902 | |||
| 903 | fn emitCharacter(self: *Self, char: u8) void { | ||
| 904 | self.buffer[self.end] = char; | ||
| 905 | self.end += 1; | ||
| 906 | } | ||
| 907 | |||
| 908 | /// Call to free the internal buffer of the iterator. | ||
| 909 | pub fn deinit(self: *Self) void { | ||
| 910 | self.allocator.free(self.buffer); | ||
| 911 | |||
| 912 | if (self.free_cmd_line_on_deinit) { | ||
| 913 | self.allocator.free(self.cmd_line); | ||
| 914 | } | ||
| 915 | } | ||
| 916 | }; | ||
| 917 | } | ||
| 918 | |||
| 919 | test "response file arg parsing" { | ||
| 920 | try testResponseFileCmdLine( | ||
| 921 | \\a b | ||
| 922 | \\c d\ | ||
| 923 | , &.{ "a", "b", "c", "d\\" }); | ||
| 924 | try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" }); | ||
| 925 | |||
| 926 | try testResponseFileCmdLine( | ||
| 927 | \\j | ||
| 928 | \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\" | ||
| 929 | \\ "m" #another comment | ||
| 930 | \\ | ||
| 931 | , &.{ "j", "k", "l", "m" }); | ||
| 932 | |||
| 933 | try testResponseFileCmdLine( | ||
| 934 | \\ "" q "" | ||
| 935 | \\ "r s # t" "u\" v" #another comment | ||
| 936 | \\ | ||
| 937 | , &.{ "", "q", "", "r s # t", "u\" v" }); | ||
| 938 | |||
| 939 | try testResponseFileCmdLine( | ||
| 940 | \\ -l"advapi32" a# b#c d# | ||
| 941 | \\e\\\ | ||
| 942 | , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" }); | ||
| 943 | |||
| 944 | try testResponseFileCmdLine( | ||
| 945 | \\ 'foo' "bar" | ||
| 946 | , &.{ "foo", "bar" }); | ||
| 947 | } | ||
| 948 | |||
| 949 | fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { | ||
| 950 | var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }) | ||
| 951 | .init(std.testing.allocator, input_cmd_line); | ||
| 952 | defer it.deinit(); | ||
| 953 | for (expected_args) |expected_arg| { | ||
| 954 | const arg = it.next().?; | ||
| 955 | try testing.expectEqualStrings(expected_arg, arg); | ||
| 956 | } | ||
| 957 | try testing.expect(it.next() == null); | ||
| 958 | } | ||
lib/std/process/Child.zig+34-34| ... | @@ -13,12 +13,17 @@ const windows = std.os.windows; | ... | @@ -13,12 +13,17 @@ const windows = std.os.windows; |
| 13 | const linux = std.os.linux; | 13 | const linux = std.os.linux; |
| 14 | const posix = std.posix; | 14 | const posix = std.posix; |
| 15 | const mem = std.mem; | 15 | const mem = std.mem; |
| 16 | const EnvMap = std.process.EnvMap; | ||
| 17 | const maxInt = std.math.maxInt; | 16 | const maxInt = std.math.maxInt; |
| 18 | const assert = std.debug.assert; | 17 | const assert = std.debug.assert; |
| 19 | const Allocator = std.mem.Allocator; | 18 | const Allocator = std.mem.Allocator; |
| 20 | const ArrayList = std.ArrayList; | 19 | const ArrayList = std.ArrayList; |
| 21 | 20 | ||
| 21 | /// Tells whether spawning child processes is supported. | ||
| 22 | pub const can_spawn = switch (native_os) { | ||
| 23 | .wasi, .ios, .tvos, .visionos, .watchos => false, | ||
| 24 | else => true, | ||
| 25 | }; | ||
| 26 | |||
| 22 | pub const Id = switch (native_os) { | 27 | pub const Id = switch (native_os) { |
| 23 | .windows => windows.HANDLE, | 28 | .windows => windows.HANDLE, |
| 24 | .wasi => void, | 29 | .wasi => void, |
| ... | @@ -54,9 +59,8 @@ term: ?(SpawnError!Term), | ... | @@ -54,9 +59,8 @@ term: ?(SpawnError!Term), |
| 54 | 59 | ||
| 55 | argv: []const []const u8, | 60 | argv: []const []const u8, |
| 56 | 61 | ||
| 57 | /// Leave as null to use the current env map using the supplied allocator. | 62 | parent_environ: process.Environ, |
| 58 | /// Required if unable to access the current env map (e.g. building a library on | 63 | /// `null` means to use `parent_environ` also for the spawned process. |
| 59 | /// some platforms). | ||
| 60 | env_map: ?*const EnvMap, | 64 | env_map: ?*const EnvMap, |
| 61 | 65 | ||
| 62 | stdin_behavior: StdIo, | 66 | stdin_behavior: StdIo, |
| ... | @@ -229,15 +233,15 @@ pub const StdIo = enum { | ... | @@ -229,15 +233,15 @@ pub const StdIo = enum { |
| 229 | }; | 233 | }; |
| 230 | 234 | ||
| 231 | /// First argument in argv is the executable. | 235 | /// First argument in argv is the executable. |
| 232 | pub fn init(argv: []const []const u8, allocator: Allocator) Child { | 236 | pub fn init(gpa: Allocator, argv: []const []const u8, environ: Environ) Child { |
| 233 | return .{ | 237 | return .{ |
| 234 | .allocator = allocator, | 238 | .allocator = gpa, |
| 235 | .argv = argv, | 239 | .argv = argv, |
| 240 | .environ = environ, | ||
| 236 | .id = undefined, | 241 | .id = undefined, |
| 237 | .thread_handle = undefined, | 242 | .thread_handle = undefined, |
| 238 | .err_pipe = if (native_os == .windows) {} else null, | 243 | .err_pipe = if (native_os == .windows) {} else null, |
| 239 | .term = null, | 244 | .term = null, |
| 240 | .env_map = null, | ||
| 241 | .cwd = null, | 245 | .cwd = null, |
| 242 | .uid = if (native_os == .windows or native_os == .wasi) {} else null, | 246 | .uid = if (native_os == .windows or native_os == .wasi) {} else null, |
| 243 | .gid = if (native_os == .windows or native_os == .wasi) {} else null, | 247 | .gid = if (native_os == .windows or native_os == .wasi) {} else null, |
| ... | @@ -435,41 +439,38 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix | ... | @@ -435,41 +439,38 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix |
| 435 | 439 | ||
| 436 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. | 440 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 437 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. | 441 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 438 | pub fn run(allocator: Allocator, io: Io, args: struct { | 442 | pub fn run(gpa: Allocator, io: Io, args: struct { |
| 439 | argv: []const []const u8, | 443 | argv: []const []const u8, |
| 444 | environ: Environ, | ||
| 440 | cwd: ?[]const u8 = null, | 445 | cwd: ?[]const u8 = null, |
| 441 | cwd_dir: ?Io.Dir = null, | 446 | cwd_dir: ?Io.Dir = null, |
| 442 | /// Required if unable to access the current env map (e.g. building a | ||
| 443 | /// library on some platforms). | ||
| 444 | env_map: ?*const EnvMap = null, | ||
| 445 | max_output_bytes: usize = 50 * 1024, | 447 | max_output_bytes: usize = 50 * 1024, |
| 446 | expand_arg0: Arg0Expand = .no_expand, | 448 | expand_arg0: Arg0Expand = .no_expand, |
| 447 | progress_node: std.Progress.Node = std.Progress.Node.none, | 449 | progress_node: std.Progress.Node = std.Progress.Node.none, |
| 448 | }) RunError!RunResult { | 450 | }) RunError!RunResult { |
| 449 | var child = Child.init(args.argv, allocator); | 451 | var child = Child.init(gpa, args.argv, args.environ); |
| 450 | child.stdin_behavior = .Ignore; | 452 | child.stdin_behavior = .Ignore; |
| 451 | child.stdout_behavior = .Pipe; | 453 | child.stdout_behavior = .Pipe; |
| 452 | child.stderr_behavior = .Pipe; | 454 | child.stderr_behavior = .Pipe; |
| 453 | child.cwd = args.cwd; | 455 | child.cwd = args.cwd; |
| 454 | child.cwd_dir = args.cwd_dir; | 456 | child.cwd_dir = args.cwd_dir; |
| 455 | child.env_map = args.env_map; | ||
| 456 | child.expand_arg0 = args.expand_arg0; | 457 | child.expand_arg0 = args.expand_arg0; |
| 457 | child.progress_node = args.progress_node; | 458 | child.progress_node = args.progress_node; |
| 458 | 459 | ||
| 459 | var stdout: ArrayList(u8) = .empty; | 460 | var stdout: ArrayList(u8) = .empty; |
| 460 | defer stdout.deinit(allocator); | 461 | defer stdout.deinit(gpa); |
| 461 | var stderr: ArrayList(u8) = .empty; | 462 | var stderr: ArrayList(u8) = .empty; |
| 462 | defer stderr.deinit(allocator); | 463 | defer stderr.deinit(gpa); |
| 463 | 464 | ||
| 464 | try child.spawn(io); | 465 | try child.spawn(io); |
| 465 | errdefer { | 466 | errdefer { |
| 466 | _ = child.kill(io) catch {}; | 467 | _ = child.kill(io) catch {}; |
| 467 | } | 468 | } |
| 468 | try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes); | 469 | try child.collectOutput(gpa, &stdout, &stderr, args.max_output_bytes); |
| 469 | 470 | ||
| 470 | return .{ | 471 | return .{ |
| 471 | .stdout = try stdout.toOwnedSlice(allocator), | 472 | .stdout = try stdout.toOwnedSlice(gpa), |
| 472 | .stderr = try stderr.toOwnedSlice(allocator), | 473 | .stderr = try stderr.toOwnedSlice(gpa), |
| 473 | .term = try child.wait(io), | 474 | .term = try child.wait(io), |
| 474 | }; | 475 | }; |
| 475 | } | 476 | } |
| ... | @@ -643,23 +644,16 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { | ... | @@ -643,23 +644,16 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { |
| 643 | 644 | ||
| 644 | const envp: [*:null]const ?[*:0]const u8 = m: { | 645 | const envp: [*:null]const ?[*:0]const u8 = m: { |
| 645 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; | 646 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; |
| 646 | if (self.env_map) |env_map| { | 647 | switch (self.environ) { |
| 647 | break :m (try process.createEnvironFromMap(arena, env_map, .{ | 648 | .empty => break :m (try process.Environ.createBlock(.{ .block = &.{} }, arena, .{ |
| 648 | .zig_progress_fd = prog_fd, | 649 | .zig_progress_fd = prog_fd, |
| 649 | })).ptr; | 650 | })).ptr, |
| 650 | } else if (builtin.link_libc) { | 651 | .inherit => |b| break :m (try b.createBlock(arena, .{ |
| 651 | break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{ | ||
| 652 | .zig_progress_fd = prog_fd, | 652 | .zig_progress_fd = prog_fd, |
| 653 | })).ptr; | 653 | })).ptr, |
| 654 | } else if (builtin.output_mode == .Exe) { | 654 | .map => |m| break :m (try m.createBlock(arena, .{ |
| 655 | // Then we have Zig start code and this works. | ||
| 656 | // TODO type-safety for null-termination of `os.environ`. | ||
| 657 | break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{ | ||
| 658 | .zig_progress_fd = prog_fd, | 655 | .zig_progress_fd = prog_fd, |
| 659 | })).ptr; | 656 | })).ptr, |
| 660 | } else { | ||
| 661 | // TODO come up with a solution for this. | ||
| 662 | @panic("missing std lib enhancement: std.process.Child implementation has no way to collect the environment variables to forward to the child process"); | ||
| 663 | } | 657 | } |
| 664 | }; | 658 | }; |
| 665 | 659 | ||
| ... | @@ -701,9 +695,15 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { | ... | @@ -701,9 +695,15 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { |
| 701 | posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err); | 695 | posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err); |
| 702 | } | 696 | } |
| 703 | 697 | ||
| 698 | const parent_PATH: ?[]const u8 = switch(self.environ) { | ||
| 699 | .empty => null, | ||
| 700 | .inherit => | ||
| 701 | .map => |m| m.get("PATH"), | ||
| 702 | }; | ||
| 703 | |||
| 704 | const err = switch (self.expand_arg0) { | 704 | const err = switch (self.expand_arg0) { |
| 705 | .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), | 705 | .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH), |
| 706 | .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), | 706 | .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH), |
| 707 | }; | 707 | }; |
| 708 | forkChildErrReport(io, err_pipe[1], err); | 708 | forkChildErrReport(io, err_pipe[1], err); |
| 709 | } | 709 | } |
lib/std/process/Environ.zig created+764| ... | @@ -0,0 +1,764 @@ | ||
| 1 | const Environ = @This(); | ||
| 2 | |||
| 3 | const builtin = @import("builtin"); | ||
| 4 | const native_os = builtin.os.tag; | ||
| 5 | |||
| 6 | const std = @import("../std.zig"); | ||
| 7 | const Allocator = std.mem.Allocator; | ||
| 8 | const assert = std.debug.assert; | ||
| 9 | const testing = std.debug.testing; | ||
| 10 | const unicode = std.unicode; | ||
| 11 | const posix = std.posix; | ||
| 12 | const mem = std.mem; | ||
| 13 | |||
| 14 | block: Block, | ||
| 15 | |||
| 16 | pub const Block = switch (native_os) { | ||
| 17 | .windows => []const u16, | ||
| 18 | else => []const [*:0]const u8, | ||
| 19 | }; | ||
| 20 | |||
| 21 | pub const Map = struct { | ||
| 22 | array_hash_map: ArrayHashMap, | ||
| 23 | allocator: Allocator, | ||
| 24 | |||
| 25 | const ArrayHashMap = std.ArrayHashMapUnmanaged([]const u8, []const u8, EnvNameHashContext, false); | ||
| 26 | |||
| 27 | pub const Size = usize; | ||
| 28 | |||
| 29 | pub const EnvNameHashContext = struct { | ||
| 30 | fn upcase(c: u21) u21 { | ||
| 31 | if (c <= std.math.maxInt(u16)) | ||
| 32 | return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); | ||
| 33 | return c; | ||
| 34 | } | ||
| 35 | |||
| 36 | pub fn hash(self: @This(), s: []const u8) u32 { | ||
| 37 | _ = self; | ||
| 38 | if (native_os == .windows) { | ||
| 39 | var h = std.hash.Wyhash.init(0); | ||
| 40 | var it = unicode.Wtf8View.initUnchecked(s).iterator(); | ||
| 41 | while (it.nextCodepoint()) |cp| { | ||
| 42 | const cp_upper = upcase(cp); | ||
| 43 | h.update(&[_]u8{ | ||
| 44 | @as(u8, @intCast((cp_upper >> 16) & 0xff)), | ||
| 45 | @as(u8, @intCast((cp_upper >> 8) & 0xff)), | ||
| 46 | @as(u8, @intCast((cp_upper >> 0) & 0xff)), | ||
| 47 | }); | ||
| 48 | } | ||
| 49 | return h.final(); | ||
| 50 | } | ||
| 51 | return std.array_hash_map.hashString(s); | ||
| 52 | } | ||
| 53 | |||
| 54 | pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { | ||
| 55 | _ = self; | ||
| 56 | _ = b_index; | ||
| 57 | if (native_os == .windows) { | ||
| 58 | var it_a = unicode.Wtf8View.initUnchecked(a).iterator(); | ||
| 59 | var it_b = unicode.Wtf8View.initUnchecked(b).iterator(); | ||
| 60 | while (true) { | ||
| 61 | const c_a = it_a.nextCodepoint() orelse break; | ||
| 62 | const c_b = it_b.nextCodepoint() orelse return false; | ||
| 63 | if (upcase(c_a) != upcase(c_b)) | ||
| 64 | return false; | ||
| 65 | } | ||
| 66 | return if (it_b.nextCodepoint()) |_| false else true; | ||
| 67 | } | ||
| 68 | return std.array_hash_map.eqlString(a, b); | ||
| 69 | } | ||
| 70 | }; | ||
| 71 | |||
| 72 | /// Create a Map backed by a specific allocator. | ||
| 73 | /// That allocator will be used for both backing allocations | ||
| 74 | /// and string deduplication. | ||
| 75 | pub fn init(allocator: Allocator) Map { | ||
| 76 | return .{ .array_hash_map = .empty, .allocator = allocator }; | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Free the backing storage of the map, as well as all | ||
| 80 | /// of the stored keys and values. | ||
| 81 | pub fn deinit(self: *Map) void { | ||
| 82 | const gpa = self.allocator; | ||
| 83 | var it = self.array_hash_map.iterator(); | ||
| 84 | while (it.next()) |entry| { | ||
| 85 | gpa.free(entry.key_ptr.*); | ||
| 86 | gpa.free(entry.value_ptr.*); | ||
| 87 | } | ||
| 88 | self.array_hash_map.deinit(gpa); | ||
| 89 | self.* = undefined; | ||
| 90 | } | ||
| 91 | |||
| 92 | pub fn keys(m: *Map) [][]const u8 { | ||
| 93 | return m.array_hash_map.keys(); | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn values(m: *Map) [][]const u8 { | ||
| 97 | return m.array_hash_map.values(); | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Same as `put` but the key and value become owned by the Map rather | ||
| 101 | /// than being copied. | ||
| 102 | /// If `putMove` fails, the ownership of key and value does not transfer. | ||
| 103 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 104 | pub fn putMove(self: *Map, key: []u8, value: []u8) !void { | ||
| 105 | const gpa = self.allocator; | ||
| 106 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 107 | const get_or_put = try self.array_hash_map.getOrPut(gpa, key); | ||
| 108 | if (get_or_put.found_existing) { | ||
| 109 | gpa.free(get_or_put.key_ptr.*); | ||
| 110 | gpa.free(get_or_put.value_ptr.*); | ||
| 111 | get_or_put.key_ptr.* = key; | ||
| 112 | } | ||
| 113 | get_or_put.value_ptr.* = value; | ||
| 114 | } | ||
| 115 | |||
| 116 | /// `key` and `value` are copied into the Map. | ||
| 117 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 118 | pub fn put(self: *Map, key: []const u8, value: []const u8) !void { | ||
| 119 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 120 | const gpa = self.allocator; | ||
| 121 | const value_copy = try gpa.dupe(u8, value); | ||
| 122 | errdefer gpa.free(value_copy); | ||
| 123 | const get_or_put = try self.array_hash_map.getOrPut(gpa, key); | ||
| 124 | errdefer { | ||
| 125 | if (!get_or_put.found_existing) assert(self.array_hash_map.pop() != null); | ||
| 126 | } | ||
| 127 | if (get_or_put.found_existing) { | ||
| 128 | gpa.free(get_or_put.value_ptr.*); | ||
| 129 | } else { | ||
| 130 | get_or_put.key_ptr.* = try gpa.dupe(u8, key); | ||
| 131 | } | ||
| 132 | get_or_put.value_ptr.* = value_copy; | ||
| 133 | } | ||
| 134 | |||
| 135 | /// Find the address of the value associated with a key. | ||
| 136 | /// The returned pointer is invalidated if the map resizes. | ||
| 137 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 138 | pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { | ||
| 139 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 140 | return self.array_hash_map.getPtr(key); | ||
| 141 | } | ||
| 142 | |||
| 143 | /// Return the map's copy of the value associated with | ||
| 144 | /// a key. The returned string is invalidated if this | ||
| 145 | /// key is removed from the map. | ||
| 146 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 147 | pub fn get(self: Map, key: []const u8) ?[]const u8 { | ||
| 148 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 149 | return self.array_hash_map.get(key); | ||
| 150 | } | ||
| 151 | |||
| 152 | pub fn contains(m: *const Map, key: []const u8) bool { | ||
| 153 | return m.contains(key); | ||
| 154 | } | ||
| 155 | |||
| 156 | /// If there is an entry with a matching key, it is deleted from the hash | ||
| 157 | /// map. The entry is removed from the underlying array by swapping it with | ||
| 158 | /// the last element. | ||
| 159 | /// | ||
| 160 | /// Returns true if an entry was removed, false otherwise. | ||
| 161 | /// | ||
| 162 | /// This invalidates the value returned by get() for this key. | ||
| 163 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 164 | pub fn swapRemove(self: *Map, key: []const u8) bool { | ||
| 165 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 166 | const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; | ||
| 167 | const gpa = self.allocator; | ||
| 168 | gpa.free(kv.key); | ||
| 169 | gpa.free(kv.value); | ||
| 170 | return true; | ||
| 171 | } | ||
| 172 | |||
| 173 | /// If there is an entry with a matching key, it is deleted from the map. | ||
| 174 | /// The entry is removed from the underlying array by shifting all elements | ||
| 175 | /// forward, thereby maintaining the current ordering. | ||
| 176 | /// | ||
| 177 | /// Returns true if an entry was removed, false otherwise. | ||
| 178 | /// | ||
| 179 | /// This invalidates the value returned by get() for this key. | ||
| 180 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | ||
| 181 | pub fn orderedRemove(self: *Map, key: []const u8) bool { | ||
| 182 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 183 | const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; | ||
| 184 | const gpa = self.allocator; | ||
| 185 | gpa.free(kv.key); | ||
| 186 | gpa.free(kv.value); | ||
| 187 | return true; | ||
| 188 | } | ||
| 189 | |||
| 190 | /// Returns the number of KV pairs stored in the map. | ||
| 191 | pub fn count(self: Map) Size { | ||
| 192 | return self.array_hash_map.count(); | ||
| 193 | } | ||
| 194 | |||
| 195 | /// Returns an iterator over entries in the map. | ||
| 196 | pub fn iterator(self: *const Map) ArrayHashMap.Iterator { | ||
| 197 | return self.array_hash_map.iterator(); | ||
| 198 | } | ||
| 199 | |||
| 200 | /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily | ||
| 201 | /// the same allocator used to allocate `em`. | ||
| 202 | pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map { | ||
| 203 | // Since we need to dupe the keys and values, the only way for error handling to not be a | ||
| 204 | // nightmare is to add keys to an empty map one-by-one. This could be avoided if this | ||
| 205 | // abstraction were a bit less... OOP-esque. | ||
| 206 | var new: Map = .init(gpa); | ||
| 207 | errdefer new.deinit(); | ||
| 208 | try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count()); | ||
| 209 | for (m.array_hash_map.keys(), m.array_hash_map.values()) |key, value| { | ||
| 210 | try new.put(key, value); | ||
| 211 | } | ||
| 212 | return new; | ||
| 213 | } | ||
| 214 | |||
| 215 | /// Creates a null-delimited environment variable block in the format | ||
| 216 | /// expected by POSIX, from a hash map plus options. | ||
| 217 | pub fn createBlock( | ||
| 218 | map: *const Map, | ||
| 219 | arena: Allocator, | ||
| 220 | options: CreateBlockOptions, | ||
| 221 | ) Allocator.Error![:null]?[*:0]u8 { | ||
| 222 | const ZigProgressAction = enum { nothing, edit, delete, add }; | ||
| 223 | const zig_progress_action: ZigProgressAction = a: { | ||
| 224 | const fd = options.zig_progress_fd orelse break :a .nothing; | ||
| 225 | const exists = map.get("ZIG_PROGRESS") != null; | ||
| 226 | if (fd >= 0) { | ||
| 227 | break :a if (exists) .edit else .add; | ||
| 228 | } else { | ||
| 229 | if (exists) break :a .delete; | ||
| 230 | } | ||
| 231 | break :a .nothing; | ||
| 232 | }; | ||
| 233 | |||
| 234 | const envp_count: usize = c: { | ||
| 235 | var c: usize = map.count(); | ||
| 236 | switch (zig_progress_action) { | ||
| 237 | .add => c += 1, | ||
| 238 | .delete => c -= 1, | ||
| 239 | .nothing, .edit => {}, | ||
| 240 | } | ||
| 241 | break :c c; | ||
| 242 | }; | ||
| 243 | |||
| 244 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | ||
| 245 | var i: usize = 0; | ||
| 246 | |||
| 247 | if (zig_progress_action == .add) { | ||
| 248 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | ||
| 249 | i += 1; | ||
| 250 | } | ||
| 251 | |||
| 252 | { | ||
| 253 | var it = map.iterator(); | ||
| 254 | while (it.next()) |pair| { | ||
| 255 | if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { | ||
| 256 | .add => unreachable, | ||
| 257 | .delete => continue, | ||
| 258 | .edit => { | ||
| 259 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ | ||
| 260 | pair.key_ptr.*, options.zig_progress_fd.?, | ||
| 261 | }, 0); | ||
| 262 | i += 1; | ||
| 263 | continue; | ||
| 264 | }, | ||
| 265 | .nothing => {}, | ||
| 266 | }; | ||
| 267 | |||
| 268 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); | ||
| 269 | i += 1; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | assert(i == envp_count); | ||
| 274 | return envp_buf; | ||
| 275 | } | ||
| 276 | }; | ||
| 277 | |||
| 278 | pub const CreateMapError = error{ | ||
| 279 | OutOfMemory, | ||
| 280 | /// WASI-only. `environ_sizes_get` or `environ_get` failed for an | ||
| 281 | /// unexpected reason. | ||
| 282 | Unexpected, | ||
| 283 | }; | ||
| 284 | |||
| 285 | /// Allocates a `Map` and copies environment block into it. | ||
| 286 | pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { | ||
| 287 | var result = Map.init(allocator); | ||
| 288 | errdefer result.deinit(); | ||
| 289 | |||
| 290 | if (native_os == .windows) { | ||
| 291 | const ptr = env.block; | ||
| 292 | |||
| 293 | var i: usize = 0; | ||
| 294 | while (ptr[i] != 0) { | ||
| 295 | const key_start = i; | ||
| 296 | |||
| 297 | // There are some special environment variables that start with =, | ||
| 298 | // so we need a special case to not treat = as a key/value separator | ||
| 299 | // if it's the first character. | ||
| 300 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | ||
| 301 | if (ptr[key_start] == '=') i += 1; | ||
| 302 | |||
| 303 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} | ||
| 304 | const key_w = ptr[key_start..i]; | ||
| 305 | const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w); | ||
| 306 | errdefer allocator.free(key); | ||
| 307 | |||
| 308 | if (ptr[i] == '=') i += 1; | ||
| 309 | |||
| 310 | const value_start = i; | ||
| 311 | while (ptr[i] != 0) : (i += 1) {} | ||
| 312 | const value_w = ptr[value_start..i]; | ||
| 313 | const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w); | ||
| 314 | errdefer allocator.free(value); | ||
| 315 | |||
| 316 | i += 1; // skip over null byte | ||
| 317 | |||
| 318 | try result.putMove(key, value); | ||
| 319 | } | ||
| 320 | return result; | ||
| 321 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 322 | var environ_count: usize = undefined; | ||
| 323 | var environ_buf_size: usize = undefined; | ||
| 324 | |||
| 325 | const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); | ||
| 326 | if (environ_sizes_get_ret != .SUCCESS) { | ||
| 327 | return posix.unexpectedErrno(environ_sizes_get_ret); | ||
| 328 | } | ||
| 329 | |||
| 330 | if (environ_count == 0) { | ||
| 331 | return result; | ||
| 332 | } | ||
| 333 | |||
| 334 | const environ = try allocator.alloc([*:0]u8, environ_count); | ||
| 335 | defer allocator.free(environ); | ||
| 336 | const environ_buf = try allocator.alloc(u8, environ_buf_size); | ||
| 337 | defer allocator.free(environ_buf); | ||
| 338 | |||
| 339 | const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); | ||
| 340 | if (environ_get_ret != .SUCCESS) { | ||
| 341 | return posix.unexpectedErrno(environ_get_ret); | ||
| 342 | } | ||
| 343 | |||
| 344 | for (environ) |line| { | ||
| 345 | const pair = mem.sliceTo(line, 0); | ||
| 346 | var parts = mem.splitScalar(u8, pair, '='); | ||
| 347 | const key = parts.first(); | ||
| 348 | const value = parts.rest(); | ||
| 349 | try result.put(key, value); | ||
| 350 | } | ||
| 351 | return result; | ||
| 352 | } else if (builtin.link_libc) { | ||
| 353 | var ptr = env.block; | ||
| 354 | while (ptr[0]) |line| : (ptr += 1) { | ||
| 355 | var line_i: usize = 0; | ||
| 356 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | ||
| 357 | const key = line[0..line_i]; | ||
| 358 | |||
| 359 | var end_i: usize = line_i; | ||
| 360 | while (line[end_i] != 0) : (end_i += 1) {} | ||
| 361 | const value = line[line_i + 1 .. end_i]; | ||
| 362 | |||
| 363 | try result.put(key, value); | ||
| 364 | } | ||
| 365 | return result; | ||
| 366 | } else { | ||
| 367 | for (env.block) |line| { | ||
| 368 | var line_i: usize = 0; | ||
| 369 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | ||
| 370 | const key = line[0..line_i]; | ||
| 371 | |||
| 372 | var end_i: usize = line_i; | ||
| 373 | while (line[end_i] != 0) : (end_i += 1) {} | ||
| 374 | const value = line[line_i + 1 .. end_i]; | ||
| 375 | |||
| 376 | try result.put(key, value); | ||
| 377 | } | ||
| 378 | return result; | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | test createMap { | ||
| 383 | var env = try createMap(testing.allocator); | ||
| 384 | defer env.deinit(); | ||
| 385 | } | ||
| 386 | |||
| 387 | pub const GetEnvVarOwnedError = error{ | ||
| 388 | OutOfMemory, | ||
| 389 | EnvironmentVariableNotFound, | ||
| 390 | |||
| 391 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | ||
| 392 | /// https://wtf-8.codeberg.page/ | ||
| 393 | InvalidWtf8, | ||
| 394 | }; | ||
| 395 | |||
| 396 | /// Caller must free returned memory. | ||
| 397 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | ||
| 398 | /// then `error.InvalidWtf8` is returned. | ||
| 399 | /// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 400 | /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. | ||
| 401 | pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { | ||
| 402 | if (native_os == .windows) { | ||
| 403 | const result_w = blk: { | ||
| 404 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | ||
| 405 | const stack_allocator = stack_alloc.get(); | ||
| 406 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | ||
| 407 | defer stack_allocator.free(key_w); | ||
| 408 | |||
| 409 | break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; | ||
| 410 | }; | ||
| 411 | // wtf16LeToWtf8Alloc can only fail with OutOfMemory | ||
| 412 | return unicode.wtf16LeToWtf8Alloc(allocator, result_w); | ||
| 413 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 414 | var envmap = createMap(allocator) catch return error.OutOfMemory; | ||
| 415 | defer envmap.deinit(); | ||
| 416 | const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; | ||
| 417 | return allocator.dupe(u8, val); | ||
| 418 | } else { | ||
| 419 | const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; | ||
| 420 | return allocator.dupe(u8, result); | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | /// On Windows, `key` must be valid WTF-8. | ||
| 425 | pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { | ||
| 426 | if (native_os == .windows) { | ||
| 427 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); | ||
| 428 | return getenvW(key_w) != null; | ||
| 429 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 430 | return false; | ||
| 431 | } else { | ||
| 432 | return posix.getenv(key) != null; | ||
| 433 | } | ||
| 434 | } | ||
| 435 | |||
| 436 | /// On Windows, `key` must be valid WTF-8. | ||
| 437 | pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { | ||
| 438 | if (native_os == .windows) { | ||
| 439 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); | ||
| 440 | const value = getenvW(key_w) orelse return false; | ||
| 441 | return value.len != 0; | ||
| 442 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 443 | return false; | ||
| 444 | } else { | ||
| 445 | const value = posix.getenv(key) orelse return false; | ||
| 446 | return value.len != 0; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | |||
| 450 | pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; | ||
| 451 | |||
| 452 | /// Parses an environment variable as an integer. | ||
| 453 | /// | ||
| 454 | /// On Windows, `key` must be valid WTF-8. | ||
| 455 | pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I { | ||
| 456 | const text = io.environ(key) orelse return error.EnvironmentVariableNotFound; | ||
| 457 | return std.fmt.parseInt(I, text, base); | ||
| 458 | } | ||
| 459 | |||
| 460 | pub const HasEnvVarError = error{ | ||
| 461 | OutOfMemory, | ||
| 462 | |||
| 463 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | ||
| 464 | /// https://wtf-8.codeberg.page/ | ||
| 465 | InvalidWtf8, | ||
| 466 | }; | ||
| 467 | |||
| 468 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | ||
| 469 | /// then `error.InvalidWtf8` is returned. | ||
| 470 | pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | ||
| 471 | if (native_os == .windows) { | ||
| 472 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | ||
| 473 | const stack_allocator = stack_alloc.get(); | ||
| 474 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | ||
| 475 | defer stack_allocator.free(key_w); | ||
| 476 | return getenvW(key_w) != null; | ||
| 477 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 478 | var envmap = createMap(allocator) catch return error.OutOfMemory; | ||
| 479 | defer envmap.deinit(); | ||
| 480 | return envmap.getPtr(key) != null; | ||
| 481 | } else { | ||
| 482 | return posix.getenv(key) != null; | ||
| 483 | } | ||
| 484 | } | ||
| 485 | |||
| 486 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | ||
| 487 | /// then `error.InvalidWtf8` is returned. | ||
| 488 | pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | ||
| 489 | if (native_os == .windows) { | ||
| 490 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | ||
| 491 | const stack_allocator = stack_alloc.get(); | ||
| 492 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | ||
| 493 | defer stack_allocator.free(key_w); | ||
| 494 | const value = getenvW(key_w) orelse return false; | ||
| 495 | return value.len != 0; | ||
| 496 | } else if (native_os == .wasi and !builtin.link_libc) { | ||
| 497 | var envmap = createMap(allocator) catch return error.OutOfMemory; | ||
| 498 | defer envmap.deinit(); | ||
| 499 | const value = envmap.getPtr(key) orelse return false; | ||
| 500 | return value.len != 0; | ||
| 501 | } else { | ||
| 502 | const value = posix.getenv(key) orelse return false; | ||
| 503 | return value.len != 0; | ||
| 504 | } | ||
| 505 | } | ||
| 506 | |||
| 507 | /// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. | ||
| 508 | /// The returned slice points to memory in the PEB. | ||
| 509 | /// | ||
| 510 | /// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. | ||
| 511 | /// | ||
| 512 | /// See also: | ||
| 513 | /// * `std.posix.getenv` | ||
| 514 | /// * `createMap` | ||
| 515 | /// * `getEnvVarOwned` | ||
| 516 | /// * `hasEnvVarConstant` | ||
| 517 | /// * `hasEnvVar` | ||
| 518 | pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { | ||
| 519 | if (native_os != .windows) { | ||
| 520 | @compileError("Windows-only"); | ||
| 521 | } | ||
| 522 | const key_slice = mem.sliceTo(key, 0); | ||
| 523 | // '=' anywhere but the start makes this an invalid environment variable name | ||
| 524 | if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { | ||
| 525 | return null; | ||
| 526 | } | ||
| 527 | const ptr = std.os.windows.peb().ProcessParameters.Environment; | ||
| 528 | var i: usize = 0; | ||
| 529 | while (ptr[i] != 0) { | ||
| 530 | const key_value = mem.sliceTo(ptr[i..], 0); | ||
| 531 | |||
| 532 | // There are some special environment variables that start with =, | ||
| 533 | // so we need a special case to not treat = as a key/value separator | ||
| 534 | // if it's the first character. | ||
| 535 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | ||
| 536 | const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; | ||
| 537 | const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { | ||
| 538 | // This is enforced by CreateProcess. | ||
| 539 | // If violated, CreateProcess will fail with INVALID_PARAMETER. | ||
| 540 | unreachable; // must contain a = | ||
| 541 | }; | ||
| 542 | |||
| 543 | const this_key = key_value[0..equal_index]; | ||
| 544 | if (std.os.windows.eqlIgnoreCaseWtf16(key_slice, this_key)) { | ||
| 545 | return key_value[equal_index + 1 ..]; | ||
| 546 | } | ||
| 547 | |||
| 548 | // skip past the NUL terminator | ||
| 549 | i += key_value.len + 1; | ||
| 550 | } | ||
| 551 | return null; | ||
| 552 | } | ||
| 553 | |||
| 554 | test getEnvVarOwned { | ||
| 555 | try testing.expectError( | ||
| 556 | error.EnvironmentVariableNotFound, | ||
| 557 | getEnvVarOwned(std.testing.allocator, "BADENV"), | ||
| 558 | ); | ||
| 559 | } | ||
| 560 | |||
| 561 | test hasEnvVarConstant { | ||
| 562 | if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; | ||
| 563 | |||
| 564 | try testing.expect(!hasEnvVarConstant("BADENV")); | ||
| 565 | } | ||
| 566 | |||
| 567 | test hasEnvVar { | ||
| 568 | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); | ||
| 569 | try testing.expect(!has_env); | ||
| 570 | } | ||
| 571 | |||
| 572 | pub const CreateBlockOptions = struct { | ||
| 573 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. | ||
| 574 | /// If non-null, negative means to remove the environment variable, and >= 0 | ||
| 575 | /// means to provide it with the given integer. | ||
| 576 | zig_progress_fd: ?i32 = null, | ||
| 577 | }; | ||
| 578 | |||
| 579 | /// Creates a null-delimited environment variable block in the format expected | ||
| 580 | /// by POSIX, from a different one. | ||
| 581 | pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { | ||
| 582 | const existing_count, const contains_zig_progress = c: { | ||
| 583 | var count: usize = 0; | ||
| 584 | var contains = false; | ||
| 585 | while (existing.block[count]) |line| : (count += 1) { | ||
| 586 | contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS"); | ||
| 587 | } | ||
| 588 | break :c .{ count, contains }; | ||
| 589 | }; | ||
| 590 | const ZigProgressAction = enum { nothing, edit, delete, add }; | ||
| 591 | const zig_progress_action: ZigProgressAction = a: { | ||
| 592 | const fd = options.zig_progress_fd orelse break :a .nothing; | ||
| 593 | if (fd >= 0) { | ||
| 594 | break :a if (contains_zig_progress) .edit else .add; | ||
| 595 | } else { | ||
| 596 | if (contains_zig_progress) break :a .delete; | ||
| 597 | } | ||
| 598 | break :a .nothing; | ||
| 599 | }; | ||
| 600 | |||
| 601 | const envp_count: usize = c: { | ||
| 602 | var count: usize = existing_count; | ||
| 603 | switch (zig_progress_action) { | ||
| 604 | .add => count += 1, | ||
| 605 | .delete => count -= 1, | ||
| 606 | .nothing, .edit => {}, | ||
| 607 | } | ||
| 608 | break :c count; | ||
| 609 | }; | ||
| 610 | |||
| 611 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | ||
| 612 | var i: usize = 0; | ||
| 613 | var existing_index: usize = 0; | ||
| 614 | |||
| 615 | if (zig_progress_action == .add) { | ||
| 616 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | ||
| 617 | i += 1; | ||
| 618 | } | ||
| 619 | |||
| 620 | while (existing.block[existing_index]) |line| : (existing_index += 1) { | ||
| 621 | if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { | ||
| 622 | .add => unreachable, | ||
| 623 | .delete => continue, | ||
| 624 | .edit => { | ||
| 625 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | ||
| 626 | i += 1; | ||
| 627 | continue; | ||
| 628 | }, | ||
| 629 | .nothing => {}, | ||
| 630 | }; | ||
| 631 | envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); | ||
| 632 | i += 1; | ||
| 633 | } | ||
| 634 | |||
| 635 | assert(i == envp_count); | ||
| 636 | return envp_buf; | ||
| 637 | } | ||
| 638 | |||
| 639 | test "Map.createBlock" { | ||
| 640 | const allocator = testing.allocator; | ||
| 641 | var envmap = Map.init(allocator); | ||
| 642 | defer envmap.deinit(); | ||
| 643 | |||
| 644 | try envmap.put("HOME", "/home/ifreund"); | ||
| 645 | try envmap.put("WAYLAND_DISPLAY", "wayland-1"); | ||
| 646 | try envmap.put("DISPLAY", ":1"); | ||
| 647 | try envmap.put("DEBUGINFOD_URLS", " "); | ||
| 648 | try envmap.put("XCURSOR_SIZE", "24"); | ||
| 649 | |||
| 650 | var arena = std.heap.ArenaAllocator.init(allocator); | ||
| 651 | defer arena.deinit(); | ||
| 652 | const environ = try envmap.createBlock(arena.allocator(), .{}); | ||
| 653 | |||
| 654 | try testing.expectEqual(@as(usize, 5), environ.len); | ||
| 655 | |||
| 656 | inline for (.{ | ||
| 657 | "HOME=/home/ifreund", | ||
| 658 | "WAYLAND_DISPLAY=wayland-1", | ||
| 659 | "DISPLAY=:1", | ||
| 660 | "DEBUGINFOD_URLS= ", | ||
| 661 | "XCURSOR_SIZE=24", | ||
| 662 | }) |target| { | ||
| 663 | for (environ) |variable| { | ||
| 664 | if (mem.eql(u8, mem.span(variable orelse continue), target)) break; | ||
| 665 | } else { | ||
| 666 | try testing.expect(false); // Environment variable not found | ||
| 667 | } | ||
| 668 | } | ||
| 669 | } | ||
| 670 | |||
| 671 | /// Caller must free result. | ||
| 672 | pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 { | ||
| 673 | // count bytes needed | ||
| 674 | const max_chars_needed = x: { | ||
| 675 | // Only need 2 trailing NUL code units for an empty environment | ||
| 676 | var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; | ||
| 677 | var it = env_map.iterator(); | ||
| 678 | while (it.next()) |pair| { | ||
| 679 | // +1 for '=' | ||
| 680 | // +1 for null byte | ||
| 681 | max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; | ||
| 682 | } | ||
| 683 | break :x max_chars_needed; | ||
| 684 | }; | ||
| 685 | const result = try allocator.alloc(u16, max_chars_needed); | ||
| 686 | errdefer allocator.free(result); | ||
| 687 | |||
| 688 | var it = env_map.iterator(); | ||
| 689 | var i: usize = 0; | ||
| 690 | while (it.next()) |pair| { | ||
| 691 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | ||
| 692 | result[i] = '='; | ||
| 693 | i += 1; | ||
| 694 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | ||
| 695 | result[i] = 0; | ||
| 696 | i += 1; | ||
| 697 | } | ||
| 698 | result[i] = 0; | ||
| 699 | i += 1; | ||
| 700 | // An empty environment is a special case that requires a redundant | ||
| 701 | // NUL terminator. CreateProcess will read the second code unit even | ||
| 702 | // though theoretically the first should be enough to recognize that the | ||
| 703 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) | ||
| 704 | if (env_map.count() == 0) { | ||
| 705 | result[i] = 0; | ||
| 706 | i += 1; | ||
| 707 | } | ||
| 708 | return try allocator.realloc(result, i); | ||
| 709 | } | ||
| 710 | |||
| 711 | test Map { | ||
| 712 | var env = Map.init(testing.allocator); | ||
| 713 | defer env.deinit(); | ||
| 714 | |||
| 715 | try env.put("SOMETHING_NEW", "hello"); | ||
| 716 | try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?); | ||
| 717 | try testing.expectEqual(@as(Map.Size, 1), env.count()); | ||
| 718 | |||
| 719 | // overwrite | ||
| 720 | try env.put("SOMETHING_NEW", "something"); | ||
| 721 | try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?); | ||
| 722 | try testing.expectEqual(@as(Map.Size, 1), env.count()); | ||
| 723 | |||
| 724 | // a new longer name to test the Windows-specific conversion buffer | ||
| 725 | try env.put("SOMETHING_NEW_AND_LONGER", "1"); | ||
| 726 | try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?); | ||
| 727 | try testing.expectEqual(@as(Map.Size, 2), env.count()); | ||
| 728 | |||
| 729 | // case insensitivity on Windows only | ||
| 730 | if (native_os == .windows) { | ||
| 731 | try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?); | ||
| 732 | } else { | ||
| 733 | try testing.expect(null == env.get("something_New_aNd_LONGER")); | ||
| 734 | } | ||
| 735 | |||
| 736 | var it = env.iterator(); | ||
| 737 | var count: Map.Size = 0; | ||
| 738 | while (it.next()) |entry| { | ||
| 739 | const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); | ||
| 740 | try testing.expect(is_an_expected_name); | ||
| 741 | count += 1; | ||
| 742 | } | ||
| 743 | try testing.expectEqual(@as(Map.Size, 2), count); | ||
| 744 | |||
| 745 | env.remove("SOMETHING_NEW"); | ||
| 746 | try testing.expect(env.get("SOMETHING_NEW") == null); | ||
| 747 | |||
| 748 | try testing.expectEqual(@as(Map.Size, 1), env.count()); | ||
| 749 | |||
| 750 | if (native_os == .windows) { | ||
| 751 | // test Unicode case-insensitivity on Windows | ||
| 752 | try env.put("КИРиллИЦА", "something else"); | ||
| 753 | try testing.expectEqualStrings("something else", env.get("кириллица").?); | ||
| 754 | |||
| 755 | // and WTF-8 that's not valid UTF-8 | ||
| 756 | const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ | ||
| 757 | std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate | ||
| 758 | }); | ||
| 759 | defer testing.allocator.free(wtf8_with_surrogate_pair); | ||
| 760 | |||
| 761 | try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); | ||
| 762 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); | ||
| 763 | } | ||
| 764 | } | ||
lib/std/start.zig+81-51| ... | @@ -524,7 +524,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { | ... | @@ -524,7 +524,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| 524 | 524 | ||
| 525 | std.debug.maybeEnableSegfaultHandler(); | 525 | std.debug.maybeEnableSegfaultHandler(); |
| 526 | 526 | ||
| 527 | std.os.windows.ntdll.RtlExitUserProcess(callMain()); | 527 | std.os.windows.ntdll.RtlExitUserProcess(callMain( |
| 528 | std.os.windows.peb().ProcessParameters.CommandLine, | ||
| 529 | std.os.windows.peb().ProcessParameters.Environment, | ||
| 530 | )); | ||
| 528 | } | 531 | } |
| 529 | 532 | ||
| 530 | fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { | 533 | fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| ... | @@ -666,17 +669,12 @@ fn expandStackSize(phdrs: []elf.Phdr) void { | ... | @@ -666,17 +669,12 @@ fn expandStackSize(phdrs: []elf.Phdr) void { |
| 666 | } | 669 | } |
| 667 | 670 | ||
| 668 | inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { | 671 | inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { |
| 669 | std.os.argv = argv[0..argc]; | ||
| 670 | std.os.environ = envp; | ||
| 671 | |||
| 672 | if (std.Options.debug_threaded_io) |t| { | 672 | if (std.Options.debug_threaded_io) |t| { |
| 673 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; | 673 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; |
| 674 | t.environ = .{ .block = envp }; | 674 | t.environ = .{ .block = envp }; |
| 675 | } | 675 | } |
| 676 | |||
| 677 | std.debug.maybeEnableSegfaultHandler(); | 676 | std.debug.maybeEnableSegfaultHandler(); |
| 678 | 677 | return callMain(argv[0..argc], envp); | |
| 679 | return callMain(); | ||
| 680 | } | 678 | } |
| 681 | 679 | ||
| 682 | fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { | 680 | fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { |
| ... | @@ -695,62 +693,94 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal | ... | @@ -695,62 +693,94 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal |
| 695 | } | 693 | } |
| 696 | 694 | ||
| 697 | fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { | 695 | fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { |
| 698 | std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; | 696 | const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; |
| 699 | |||
| 700 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) { | 697 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) { |
| 701 | if (std.Options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0]; | 698 | if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; |
| 702 | } | 699 | } |
| 703 | 700 | return callMain(argv, &.{}); | |
| 704 | return callMain(); | ||
| 705 | } | 701 | } |
| 706 | 702 | ||
| 707 | // General error message for a malformed return type | 703 | /// General error message for a malformed return type |
| 708 | const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; | 704 | const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; |
| 709 | 705 | ||
| 710 | pub inline fn callMain() u8 { | 706 | const use_debug_allocator = !builtin.link_libc and !native_arch.isWasm() and builtin.mode == .Debug; |
| 711 | const ReturnType = @typeInfo(@TypeOf(root.main)).@"fn".return_type.?; | 707 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init; |
| 708 | |||
| 709 | inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 { | ||
| 710 | const fn_info = @typeInfo(@TypeOf(root.main)).@"fn"; | ||
| 711 | if (fn_info.params.len == 0) return wrapMain(root.main()); | ||
| 712 | if (fn_info.params[0].type.? == std.process.Init.Minimal) return wrapMain(root.main(.{ | ||
| 713 | .args = .{ .vector = args }, | ||
| 714 | .environ = .{ .block = environ }, | ||
| 715 | })); | ||
| 716 | |||
| 717 | const gpa = if (builtin.link_libc) | ||
| 718 | std.heap.c_allocator | ||
| 719 | else if (native_arch.isWasm()) | ||
| 720 | std.heap.wasm_allocator | ||
| 721 | else if (use_debug_allocator) | ||
| 722 | debug_allocator.allocator() | ||
| 723 | else | ||
| 724 | std.heap.smp_allocator; | ||
| 725 | |||
| 726 | defer if (use_debug_allocator) switch (debug_allocator.deinit()) { | ||
| 727 | .leak => std.process.exit(1), | ||
| 728 | .ok => {}, | ||
| 729 | }; | ||
| 712 | 730 | ||
| 713 | switch (ReturnType) { | 731 | var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 714 | void => { | 732 | defer arena_allocator.deinit(); |
| 715 | root.main(); | ||
| 716 | return 0; | ||
| 717 | }, | ||
| 718 | noreturn, u8 => { | ||
| 719 | return root.main(); | ||
| 720 | }, | ||
| 721 | else => { | ||
| 722 | if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret); | ||
| 723 | |||
| 724 | const result = root.main() catch |err| { | ||
| 725 | switch (builtin.zig_backend) { | ||
| 726 | .stage2_powerpc, | ||
| 727 | .stage2_riscv64, | ||
| 728 | => { | ||
| 729 | _ = std.posix.write(std.posix.STDERR_FILENO, "error: failed with error\n") catch {}; | ||
| 730 | return 1; | ||
| 731 | }, | ||
| 732 | else => {}, | ||
| 733 | } | ||
| 734 | std.log.err("{s}", .{@errorName(err)}); | ||
| 735 | switch (native_os) { | ||
| 736 | .freestanding, .other => {}, | ||
| 737 | else => if (@errorReturnTrace()) |trace| { | ||
| 738 | std.debug.dumpStackTrace(trace); | ||
| 739 | }, | ||
| 740 | } | ||
| 741 | return 1; | ||
| 742 | }; | ||
| 743 | 733 | ||
| 744 | return switch (@TypeOf(result)) { | 734 | var threaded: std.Io.Threaded = .init(gpa, .{ |
| 745 | void => 0, | 735 | .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{}, |
| 746 | u8 => result, | 736 | .environ = environ, |
| 747 | else => @compileError(bad_main_ret), | 737 | }); |
| 748 | }; | 738 | defer threaded.deinit(); |
| 739 | |||
| 740 | var env_map = environ.getEnvMap(gpa) catch |err| | ||
| 741 | std.process.fatal("failed to parse environment variables: {t}", .{err}); | ||
| 742 | defer env_map.deinit(); | ||
| 743 | |||
| 744 | return wrapMain(root.main(.{ | ||
| 745 | .minimal = .{ | ||
| 746 | .args = .{ .vector = args }, | ||
| 747 | .environ = .{ .block = environ }, | ||
| 749 | }, | 748 | }, |
| 749 | .arena = &arena_allocator, | ||
| 750 | .gpa = gpa, | ||
| 751 | .io = threaded.io(), | ||
| 752 | .env_map = env_map, | ||
| 753 | })); | ||
| 754 | } | ||
| 755 | |||
| 756 | inline fn wrapMain(result: anytype) u8 { | ||
| 757 | const ReturnType = @TypeOf(result); | ||
| 758 | switch (ReturnType) { | ||
| 759 | void => return 0, | ||
| 760 | noreturn => unreachable, | ||
| 761 | u8 => return result, | ||
| 762 | else => {}, | ||
| 750 | } | 763 | } |
| 764 | if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret); | ||
| 765 | |||
| 766 | const unwrapped_result = result catch |err| { | ||
| 767 | std.log.err("{t}", .{err}); | ||
| 768 | switch (native_os) { | ||
| 769 | .freestanding, .other => {}, | ||
| 770 | else => if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace), | ||
| 771 | } | ||
| 772 | return 1; | ||
| 773 | }; | ||
| 774 | |||
| 775 | return switch (@TypeOf(unwrapped_result)) { | ||
| 776 | noreturn => unreachable, | ||
| 777 | void => 0, | ||
| 778 | u8 => unwrapped_result, | ||
| 779 | else => @compileError(bad_main_ret), | ||
| 780 | }; | ||
| 751 | } | 781 | } |
| 752 | 782 | ||
| 753 | pub fn call_wWinMain() std.os.windows.INT { | 783 | fn call_wWinMain() std.os.windows.INT { |
| 754 | const peb = std.os.windows.peb(); | 784 | const peb = std.os.windows.peb(); |
| 755 | const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?; | 785 | const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?; |
| 756 | const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress); | 786 | const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress); |
lib/std/std.zig+3-3| ... | @@ -114,9 +114,6 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options | ... | @@ -114,9 +114,6 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options |
| 114 | pub const Options = struct { | 114 | pub const Options = struct { |
| 115 | enable_segfault_handler: bool = debug.default_enable_segfault_handler, | 115 | enable_segfault_handler: bool = debug.default_enable_segfault_handler, |
| 116 | 116 | ||
| 117 | /// Function used to implement `std.Io.Dir.cwd` for WASI. | ||
| 118 | wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd, | ||
| 119 | |||
| 120 | /// The current log level. | 117 | /// The current log level. |
| 121 | log_level: log.Level = log.default_level, | 118 | log_level: log.Level = log.default_level, |
| 122 | 119 | ||
| ... | @@ -193,6 +190,9 @@ pub const Options = struct { | ... | @@ -193,6 +190,9 @@ pub const Options = struct { |
| 193 | 190 | ||
| 194 | /// Overrides `std.Io.File.Permissions`. | 191 | /// Overrides `std.Io.File.Permissions`. |
| 195 | pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null; | 192 | pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null; |
| 193 | |||
| 194 | /// Overrides `std.Io.Dir.cwd`. | ||
| 195 | pub const cwd: ?fn () Io.Dir = if (@hasDecl(root, "std_options_cwd")) root.std_options_cwd else null; | ||
| 196 | }; | 196 | }; |
| 197 | 197 | ||
| 198 | // This forces the start.zig file to be imported, and the comptime logic inside that | 198 | // This forces the start.zig file to be imported, and the comptime logic inside that |
lib/std/zig.zig+4-13| ... | @@ -746,21 +746,12 @@ pub const EnvVar = enum { | ... | @@ -746,21 +746,12 @@ pub const EnvVar = enum { |
| 746 | LOCALAPPDATA, | 746 | LOCALAPPDATA, |
| 747 | HOME, | 747 | HOME, |
| 748 | 748 | ||
| 749 | pub fn isSet(comptime ev: EnvVar) bool { | 749 | pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { |
| 750 | return std.process.hasNonEmptyEnvVarConstant(@tagName(ev)); | 750 | return map.contains(@tagName(ev)); |
| 751 | } | 751 | } |
| 752 | 752 | ||
| 753 | pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 { | 753 | pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 { |
| 754 | if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| { | 754 | return map.get(@tagName(ev)); |
| 755 | return value; | ||
| 756 | } else |err| switch (err) { | ||
| 757 | error.EnvironmentVariableNotFound => return null, | ||
| 758 | else => |e| return e, | ||
| 759 | } | ||
| 760 | } | ||
| 761 | |||
| 762 | pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 { | ||
| 763 | return std.posix.getenvZ(@tagName(ev)); | ||
| 764 | } | 755 | } |
| 765 | }; | 756 | }; |
| 766 | 757 |
lib/std/zig/system/darwin.zig+5-2| ... | @@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { | ... | @@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { |
| 35 | /// Caller owns the memory. | 35 | /// Caller owns the memory. |
| 36 | /// stderr from xcrun is ignored. | 36 | /// stderr from xcrun is ignored. |
| 37 | /// If error.OutOfMemory occurs in Allocator, this function returns null. | 37 | /// If error.OutOfMemory occurs in Allocator, this function returns null. |
| 38 | pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { | 38 | pub fn getSdk(gpa: Allocator, io: Io, environ: std.process.Child.Environ, target: *const Target) ?[]const u8 { |
| 39 | const is_simulator_abi = target.abi == .simulator; | 39 | const is_simulator_abi = target.abi == .simulator; |
| 40 | const sdk = switch (target.os.tag) { | 40 | const sdk = switch (target.os.tag) { |
| 41 | .driverkit => "driverkit", | 41 | .driverkit => "driverkit", |
| ... | @@ -47,7 +47,10 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { | ... | @@ -47,7 +47,10 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { |
| 47 | else => return null, | 47 | else => return null, |
| 48 | }; | 48 | }; |
| 49 | const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" }; | 49 | const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" }; |
| 50 | const result = std.process.Child.run(gpa, io, .{ .argv = argv }) catch return null; | 50 | const result = std.process.Child.run(gpa, io, .{ |
| 51 | .argv = argv, | ||
| 52 | .environ = environ, | ||
| 53 | }) catch return null; | ||
| 51 | defer { | 54 | defer { |
| 52 | gpa.free(result.stderr); | 55 | gpa.free(result.stderr); |
| 53 | gpa.free(result.stdout); | 56 | gpa.free(result.stdout); |
test/link/macho.zig+1-1| ... | @@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { | ... | @@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { |
| 871 | const io = b.graph.io; | 871 | const io = b.graph.io; |
| 872 | const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); | 872 | const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); |
| 873 | 873 | ||
| 874 | const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse | 874 | const sdk = std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &opts.target.result) orelse |
| 875 | @panic("macOS SDK is required to run the test"); | 875 | @panic("macOS SDK is required to run the test"); |
| 876 | 876 | ||
| 877 | const exe = addExecutable(b, opts, .{ | 877 | const exe = addExecutable(b, opts, .{ |
test/standalone/ios/build.zig+1-1| ... | @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { | ... | @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { |
| 25 | 25 | ||
| 26 | const io = b.graph.io; | 26 | const io = b.graph.io; |
| 27 | 27 | ||
| 28 | if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| { | 28 | if (std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &target.result)) |sdk| { |
| 29 | b.sysroot = sdk; | 29 | b.sysroot = sdk; |
| 30 | exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); | 30 | exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); |
| 31 | exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); | 31 | exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); |
test/standalone/windows_bat_args/fuzz.zig+2-2| ... | @@ -84,13 +84,13 @@ pub fn main() anyerror!void { | ... | @@ -84,13 +84,13 @@ pub fn main() anyerror!void { |
| 84 | } | 84 | } |
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | 87 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void { |
| 88 | try testExecBat(gpa, io, "args1.bat", args, env); | 88 | try testExecBat(gpa, io, "args1.bat", args, env); |
| 89 | try testExecBat(gpa, io, "args2.bat", args, env); | 89 | try testExecBat(gpa, io, "args2.bat", args, env); |
| 90 | try testExecBat(gpa, io, "args3.bat", args, env); | 90 | try testExecBat(gpa, io, "args3.bat", args, env); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | 93 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void { |
| 94 | const argv = try gpa.alloc([]const u8, 1 + args.len); | 94 | const argv = try gpa.alloc([]const u8, 1 + args.len); |
| 95 | defer gpa.free(argv); | 95 | defer gpa.free(argv); |
| 96 | argv[0] = bat; | 96 | argv[0] = bat; |
test/standalone/windows_bat_args/test.zig+2-2| ... | @@ -130,13 +130,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8 | ... | @@ -130,13 +130,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8 |
| 130 | return std.testing.expectError(err, testExec(gpa, io, args, null)); | 130 | return std.testing.expectError(err, testExec(gpa, io, args, null)); |
| 131 | } | 131 | } |
| 132 | 132 | ||
| 133 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | 133 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void { |
| 134 | try testExecBat(gpa, io, "args1.bat", args, env); | 134 | try testExecBat(gpa, io, "args1.bat", args, env); |
| 135 | try testExecBat(gpa, io, "args2.bat", args, env); | 135 | try testExecBat(gpa, io, "args2.bat", args, env); |
| 136 | try testExecBat(gpa, io, "args3.bat", args, env); | 136 | try testExecBat(gpa, io, "args3.bat", args, env); |
| 137 | } | 137 | } |
| 138 | 138 | ||
| 139 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | 139 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void { |
| 140 | const argv = try gpa.alloc([]const u8, 1 + args.len); | 140 | const argv = try gpa.alloc([]const u8, 1 + args.len); |
| 141 | defer gpa.free(argv); | 141 | defer gpa.free(argv); |
| 142 | argv[0] = bat; | 142 | argv[0] = bat; |
test/standalone/windows_paths/test.zig+3-3| ... | @@ -25,10 +25,10 @@ pub fn main() anyerror!void { | ... | @@ -25,10 +25,10 @@ pub fn main() anyerror!void { |
| 25 | const alt_drive_letter = try getAltDriveLetter(cwd_path); | 25 | const alt_drive_letter = try getAltDriveLetter(cwd_path); |
| 26 | const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter}); | 26 | const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter}); |
| 27 | const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter}); | 27 | const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter}); |
| 28 | var alt_drive_env_map = std.process.EnvMap.init(arena); | 28 | var alt_drive_env_map = std.process.Environ.Map.init(arena); |
| 29 | try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd); | 29 | try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd); |
| 30 | 30 | ||
| 31 | const empty_env = std.process.EnvMap.init(arena); | 31 | const empty_env = std.process.Environ.Map.init(arena); |
| 32 | 32 | ||
| 33 | { | 33 | { |
| 34 | const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter}); | 34 | const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter}); |
| ... | @@ -96,7 +96,7 @@ fn checkRelative( | ... | @@ -96,7 +96,7 @@ fn checkRelative( |
| 96 | expected_stdout: []const u8, | 96 | expected_stdout: []const u8, |
| 97 | argv: []const []const u8, | 97 | argv: []const []const u8, |
| 98 | cwd: ?[]const u8, | 98 | cwd: ?[]const u8, |
| 99 | env_map: ?*const std.process.EnvMap, | 99 | env_map: ?*const std.process.Environ.Map, |
| 100 | ) !void { | 100 | ) !void { |
| 101 | const result = try std.process.Child.run(allocator, io, .{ | 101 | const result = try std.process.Child.run(allocator, io, .{ |
| 102 | .argv = argv, | 102 | .argv = argv, |
tools/doctest.zig+1-1| ... | @@ -1128,7 +1128,7 @@ fn in(slice: []const u8, number: u8) bool { | ... | @@ -1128,7 +1128,7 @@ fn in(slice: []const u8, number: u8) bool { |
| 1128 | fn run( | 1128 | fn run( |
| 1129 | allocator: Allocator, | 1129 | allocator: Allocator, |
| 1130 | io: Io, | 1130 | io: Io, |
| 1131 | env_map: *process.EnvMap, | 1131 | env_map: *process.Environ.Map, |
| 1132 | cwd: []const u8, | 1132 | cwd: []const u8, |
| 1133 | args: []const []const u8, | 1133 | args: []const []const u8, |
| 1134 | ) !process.Child.RunResult { | 1134 | ) !process.Child.RunResult { |