authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-22 14:37:41-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:12-08:00
log3c2f5adf41f0e75fd5e8f6661891dd7d4fa770a9
tree5ae7765b93dfde9f5638b83ba2436fbbdd0da24d
parent86e9e32cf0d5a028d6ebb32f8d0f3d0a23e717b6

std: integrate Io.Threaded with environment variables

* std.option allows overriding the debug Io instance * if the default is used, start code initializes environ and argv0 also fix some places that needed recancel(), thanks mlugg! See #30562

9 files changed, 111 insertions(+), 62 deletions(-)

lib/compiler/build_runner.zig+4-1
...@@ -429,8 +429,11 @@ pub fn main() !void {...@@ -429,8 +429,11 @@ pub fn main() !void {
429 }429 }
430 }430 }
431431
432 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();
433 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();
434
432 graph.stderr_mode = switch (color) {435 graph.stderr_mode = switch (color) {
433 .auto => try .detect(io, .stderr()),436 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
434 .on => .escape_codes,437 .on => .escape_codes,
435 .off => .no_color,438 .off => .no_color,
436 };439 };
lib/std/Io/File.zig+1-1
...@@ -391,7 +391,7 @@ pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void...@@ -391,7 +391,7 @@ pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void
391/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes".391/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes".
392///392///
393/// Overridable via `std.options`.393/// Overridable via `std.options`.
394pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) {394pub const Permissions = std.io_options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) {
395 default_file = 0,395 default_file = 0,
396 _,396 _,
397397
lib/std/Io/Terminal.zig+10-4
...@@ -48,7 +48,15 @@ pub const Mode = union(enum) {...@@ -48,7 +48,15 @@ pub const Mode = union(enum) {
48 /// stdout/stderr).48 /// stdout/stderr).
49 ///49 ///
50 /// Will attempt to enable ANSI escape code support if necessary/possible.50 /// Will attempt to enable ANSI escape code support if necessary/possible.
51 pub fn detect(io: Io, file: File) Io.Cancelable!Mode {51 ///
52 /// * `NO_COLOR` indicates whether "NO_COLOR" environment variable is
53 /// present and non-empty.
54 /// * `CLICOLOR_FORCE` indicates whether "CLICOLOR_FORCE" environment
55 /// variable is present and non-empty.
56 pub fn detect(io: Io, file: File, NO_COLOR: bool, CLICOLOR_FORCE: bool) Io.Cancelable!Mode {
57 const force_color: ?bool = if (NO_COLOR) false else if (CLICOLOR_FORCE) true else null;
58 if (force_color == false) return .no_color;
59
52 if (file.enableAnsiEscapeCodes(io)) |_| {60 if (file.enableAnsiEscapeCodes(io)) |_| {
53 return .escape_codes;61 return .escape_codes;
54 } else |err| switch (err) {62 } else |err| switch (err) {
...@@ -65,10 +73,8 @@ pub const Mode = union(enum) {...@@ -65,10 +73,8 @@ pub const Mode = union(enum) {
65 .reset_attributes = info.wAttributes,73 .reset_attributes = info.wAttributes,
66 } };74 } };
67 }75 }
68 return .escape_codes;
69 }76 }
7077 return if (force_color == true) .escape_codes else .no_color;
71 return .no_color;
72 }78 }
73};79};
7480
lib/std/Io/Threaded.zig+58-41
...@@ -60,30 +60,34 @@ stderr_writer: File.Writer = .{...@@ -60,30 +60,34 @@ stderr_writer: File.Writer = .{
60stderr_mode: Io.Terminal.Mode = .no_color,60stderr_mode: Io.Terminal.Mode = .no_color,
61stderr_writer_initialized: bool = false,61stderr_writer_initialized: bool = false,
6262
63argv0: Argv0,
63environ: Environ,64environ: Environ,
64args: Args,
6565
66pub const Environ = switch (native_os) {66pub const Argv0 = switch (native_os) {
67 .openbsd, .haiku => struct {67 .openbsd, .haiku => struct {
68 PATH: ?[]const u8,68 value: ?[*:0]const u8 = null,
69
70 pub const empty: @This() = .{
71 .PATH = null,
72 };
73 },
74 else => struct {
75 pub const empty: @This() = .{};
76 },69 },
70 else => struct {},
77};71};
7872
79pub const Args = switch (native_os) {73pub const Environ = struct {
80 .openbsd, .haiku => struct {74 /// Unmodified data directly from the OS.
81 list: []const []const u8,75 block: Block = &.{},
82 pub const empty: @This() = .{ .list = &.{} };76 /// Protected by `mutex`. Determines whether the other fields have been
83 },77 /// memoized based on `block`.
84 else => struct {78 initialized: bool = false,
85 pub const empty: @This() = .{};79 /// Protected by `mutex`. Memoized based on `block`. Tracks whether the
86 },80 /// environment variables are present and non-empty.
81 exist: struct {
82 NO_COLOR: bool = false,
83 CLICOLOR_FORCE: bool = false,
84 } = .{},
85 /// Protected by `mutex`. Memoized based on `block`.
86 string: struct {
87 PATH: ?[:0]const u8 = null,
88 } = .{},
89
90 pub const Block = []const [*:0]const u8;
87};91};
8892
89pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {93pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
...@@ -591,18 +595,11 @@ pub const InitOptions = struct {...@@ -591,18 +595,11 @@ pub const InitOptions = struct {
591 robust_cancel: RobustCancel = .disabled,595 robust_cancel: RobustCancel = .disabled,
592 /// Affects the following operations:596 /// Affects the following operations:
593 /// * `processExecutablePath` on OpenBSD and Haiku.597 /// * `processExecutablePath` on OpenBSD and Haiku.
594 ///598 argv0: Argv0 = .{},
595 /// The default value causes this to be a compile error on systems that need to
596 /// initialize this field. `Environ.empty` can be used to omit this field on
597 /// all targets.
598 environ: Environ = .{},
599 /// Affects the following operations:599 /// Affects the following operations:
600 /// * `processExecutablePath` on OpenBSD and Haiku.600 /// * `fileIsTty`
601 ///601 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
602 /// The default value causes this to be a compile error on systems that need to602 environ: Environ = .{},
603 /// initialize this field. `Args.empty` can be used to omit this field on all
604 /// targets.
605 args: Args = .{},
606};603};
607604
608/// Related:605/// Related:
...@@ -636,8 +633,8 @@ pub fn init(...@@ -636,8 +633,8 @@ pub fn init(
636 .current_closure = null,633 .current_closure = null,
637 .cancel_protection = undefined,634 .cancel_protection = undefined,
638 },635 },
636 .argv0 = options.argv0,
639 .environ = options.environ,637 .environ = options.environ,
640 .args = options.args,
641 .robust_cancel = options.robust_cancel,638 .robust_cancel = options.robust_cancel,
642 };639 };
643640
...@@ -678,8 +675,8 @@ pub const init_single_threaded: Threaded = .{...@@ -678,8 +675,8 @@ pub const init_single_threaded: Threaded = .{
678 .cancel_protection = undefined,675 .cancel_protection = undefined,
679 },676 },
680 .robust_cancel = .disabled,677 .robust_cancel = .disabled,
681 .environ = .empty,678 .argv0 = .{},
682 .args = .empty,679 .environ = .{},
683};680};
684681
685var global_single_threaded_instance: Threaded = .init_single_threaded;682var global_single_threaded_instance: Threaded = .init_single_threaded;
...@@ -7242,17 +7239,16 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex...@@ -7242,17 +7239,16 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
7242 }7239 }
7243 },7240 },
7244 .openbsd, .haiku => {7241 .openbsd, .haiku => {
7245 // The best we can do on these operating systems is check based on CLI args.7242 // The best we can do on these operating systems is check based on
7246 const argv = t.args.list;7243 // the first process argument.
7247 if (argv.len == 0) return error.OperationUnsupported;7244 const argv0 = t.argv0.value orelse return error.OperationUnsupported;
7248 const argv0 = argv[0];
7249 if (std.mem.findScalar(u8, argv0, '/') != null) {7245 if (std.mem.findScalar(u8, argv0, '/') != null) {
7250 // argv[0] is a path (relative or absolute): use realpath(3) directly7246 // argv[0] is a path (relative or absolute): use realpath(3) directly
7251 const current_thread = Thread.getCurrent(t);7247 const current_thread = Thread.getCurrent(t);
7252 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;7248 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
7253 try current_thread.beginSyscall();7249 try current_thread.beginSyscall();
7254 while (true) {7250 while (true) {
7255 if (std.c.realpath(argv[0], &resolved_buf)) |p| {7251 if (std.c.realpath(argv0, &resolved_buf)) |p| {
7256 assert(p == &resolved_buf);7252 assert(p == &resolved_buf);
7257 break current_thread.endSyscall();7253 break current_thread.endSyscall();
7258 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {7254 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
...@@ -7283,13 +7279,14 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex...@@ -7283,13 +7279,14 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
7283 return resolved.len;7279 return resolved.len;
7284 } else if (argv0.len != 0) {7280 } else if (argv0.len != 0) {
7285 // argv[0] is not empty (and not a path): search PATH7281 // argv[0] is not empty (and not a path): search PATH
7282 t.scanEnviron();
7283 const PATH = t.environ.string.PATH orelse return error.FileNotFound;
7286 const current_thread = Thread.getCurrent(t);7284 const current_thread = Thread.getCurrent(t);
7287 const PATH = t.environ.PATH orelse return error.FileNotFound;
7288 var it = std.mem.tokenizeScalar(u8, PATH, ':');7285 var it = std.mem.tokenizeScalar(u8, PATH, ':');
7289 it: while (it.next()) |dir| {7286 it: while (it.next()) |dir| {
7290 var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined;7287 var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined;
7291 const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{7288 const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{
7292 dir, argv[0],7289 dir, argv0,
7293 }, 0) catch continue;7290 }, 0) catch continue;
72947291
7295 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;7292 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
...@@ -10752,7 +10749,10 @@ fn initLockedStderr(...@@ -10752,7 +10749,10 @@ fn initLockedStderr(
10752 if (is_windows) t.stderr_writer.file = .stderr();10749 if (is_windows) t.stderr_writer.file = .stderr();
10753 t.stderr_writer.io = io_t;10750 t.stderr_writer.io = io_t;
10754 t.stderr_writer_initialized = true;10751 t.stderr_writer_initialized = true;
10755 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file);10752 t.scanEnviron();
10753 const NO_COLOR = t.environ.exist.NO_COLOR;
10754 const CLICOLOR_FORCE = t.environ.exist.CLICOLOR_FORCE;
10755 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file, NO_COLOR, CLICOLOR_FORCE);
10756 }10756 }
10757 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) {10757 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) {
10758 error.WriteFailed => switch (t.stderr_writer.err.?) {10758 error.WriteFailed => switch (t.stderr_writer.err.?) {
...@@ -10777,7 +10777,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {...@@ -10777,7 +10777,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {
10777 const t: *Threaded = @ptrCast(@alignCast(userdata));10777 const t: *Threaded = @ptrCast(@alignCast(userdata));
10778 t.stderr_writer.interface.flush() catch |err| switch (err) {10778 t.stderr_writer.interface.flush() catch |err| switch (err) {
10779 error.WriteFailed => switch (t.stderr_writer.err.?) {10779 error.WriteFailed => switch (t.stderr_writer.err.?) {
10780 error.Canceled => @panic("TODO make this uncancelable"),10780 error.Canceled => recancel(t),
10781 else => {},10781 else => {},
10782 },10782 },
10783 };10783 };
...@@ -11910,6 +11910,23 @@ const pthreads_futex = struct {...@@ -11910,6 +11910,23 @@ const pthreads_futex = struct {
11910 }11910 }
11911};11911};
1191211912
11913fn scanEnviron(t: *Threaded) void {
11914 t.mutex.lock();
11915 defer t.mutex.unlock();
11916
11917 if (t.environ.initialized) return;
11918 t.environ.initialized = true;
11919
11920 if (native_os == .wasi) {
11921 @panic("TODO");
11922 }
11923
11924 for (t.environ.block) |kv| {
11925 _ = kv;
11926 @panic("TODO");
11927 }
11928}
11929
11913test {11930test {
11914 _ = @import("Threaded/test.zig");11931 _ = @import("Threaded/test.zig");
11915}11932}
lib/std/Thread.zig+1-1
...@@ -322,7 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co...@@ -322,7 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
322 var buf: [32]u8 = undefined;322 var buf: [32]u8 = undefined;
323 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});323 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
324324
325 const io = Io.Threaded.global_single_threaded.ioBasic();325 const io = std.options.debug_io;
326326
327 const file = try Io.Dir.cwd().openFile(io, path, .{});327 const file = try Io.Dir.cwd().openFile(io, path, .{});
328 defer file.close(io);328 defer file.close(io);
lib/std/debug.zig+8-12
...@@ -261,10 +261,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -261,10 +261,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
261 else => true,261 else => true,
262};262};
263263
264/// This is used for debug information and debug printing. It is intentionally
265/// separate from the application's `Io` instance.
266const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic();
267
268/// Allows the caller to freely write to stderr until `unlockStderr` is called.264/// Allows the caller to freely write to stderr until `unlockStderr` is called.
269///265///
270/// During the lock, any `std.Progress` information is cleared from the terminal.266/// During the lock, any `std.Progress` information is cleared from the terminal.
...@@ -284,15 +280,15 @@ const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic();...@@ -284,15 +280,15 @@ const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic();
284/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the280/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
285/// application's chosen `Io` implementation.281/// application's chosen `Io` implementation.
286pub fn lockStderr(buffer: []u8) Io.LockedStderr {282pub fn lockStderr(buffer: []u8) Io.LockedStderr {
287 return static_single_threaded_io.lockStderr(buffer, null) catch |err| switch (err) {283 const io = std.options.debug_io;
288 // Impossible to cancel because no calls to cancel using284 return io.lockStderr(buffer, null) catch |err| switch (err) {
289 // `static_single_threaded_io` exist.285 error.Canceled => io.recancel(),
290 error.Canceled => unreachable,
291 };286 };
292}287}
293288
294pub fn unlockStderr() void {289pub fn unlockStderr() void {
295 static_single_threaded_io.unlockStderr();290 const io = std.options.debug_io;
291 io.unlockStderr();
296}292}
297293
298/// Writes to stderr, ignoring errors.294/// Writes to stderr, ignoring errors.
...@@ -627,7 +623,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -627,7 +623,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
627 defer it.deinit();623 defer it.deinit();
628 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;624 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
629625
630 const io = static_single_threaded_io;626 const io = std.options.debug_io;
631627
632 var total_frames: usize = 0;628 var total_frames: usize = 0;
633 var index: usize = 0;629 var index: usize = 0;
...@@ -689,7 +685,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin...@@ -689,7 +685,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin
689 var total_frames: usize = 0;685 var total_frames: usize = 0;
690 var wait_for = options.first_address;686 var wait_for = options.first_address;
691 var printed_any_frame = false;687 var printed_any_frame = false;
692 const io = static_single_threaded_io;688 const io = std.options.debug_io;
693 while (true) switch (it.next(io)) {689 while (true) switch (it.next(io)) {
694 .switch_to_fp => |unwind_error| {690 .switch_to_fp => |unwind_error| {
695 switch (StackIterator.fp_usability) {691 switch (StackIterator.fp_usability) {
...@@ -797,7 +793,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void...@@ -797,7 +793,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
797 return;793 return;
798 },794 },
799 };795 };
800 const io = static_single_threaded_io;796 const io = std.options.debug_io;
801 const captured_frames = @min(n_frames, st.instruction_addresses.len);797 const captured_frames = @min(n_frames, st.instruction_addresses.len);
802 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {798 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
803 // `ret_addr` is the return address, which is *after* the function call.799 // `ret_addr` is the return address, which is *after* the function call.
lib/std/dynamic_library.zig+1-1
...@@ -222,7 +222,7 @@ pub const ElfDynLib = struct {...@@ -222,7 +222,7 @@ pub const ElfDynLib = struct {
222222
223 /// Trusts the file. Malicious file will be able to execute arbitrary code.223 /// Trusts the file. Malicious file will be able to execute arbitrary code.
224 pub fn open(path: []const u8) Error!ElfDynLib {224 pub fn open(path: []const u8) Error!ElfDynLib {
225 const io = Io.Threaded.global_single_threaded.ioBasic();225 const io = std.options.debug_io;
226226
227 const fd = try resolveFromName(io, path);227 const fd = try resolveFromName(io, path);
228 defer posix.close(fd);228 defer posix.close(fd);
lib/std/start.zig+10
...@@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {...@@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
669 std.os.argv = argv[0..argc];669 std.os.argv = argv[0..argc];
670 std.os.environ = envp;670 std.os.environ = envp;
671671
672 if (std.io_options.debug_threaded_io) |t| {
673 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
674 t.environ = .{ .block = envp };
675 }
676
672 std.debug.maybeEnableSegfaultHandler();677 std.debug.maybeEnableSegfaultHandler();
673678
674 return callMain();679 return callMain();
...@@ -691,6 +696,11 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal...@@ -691,6 +696,11 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
691696
692fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {697fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
693 std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];698 std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];
699
700 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
701 if (std.io_options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0];
702 }
703
694 return callMain();704 return callMain();
695}705}
696706
lib/std/std.zig+18-1
...@@ -108,8 +108,11 @@ pub const start = @import("start.zig");...@@ -108,8 +108,11 @@ pub const start = @import("start.zig");
108108
109const root = @import("root");109const root = @import("root");
110110
111/// Stdlib-wide options that can be overridden by the root file.111/// Compile-time known settings overridable by the root source file.
112pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{};112pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{};
113/// Minimal set of `options` moved here to avoid dependency loop compilation
114/// errors.
115pub const io_options: IoOptions = if (@hasDecl(root, "std_io_options")) root.std_io_options else .{};
113116
114pub const Options = struct {117pub const Options = struct {
115 enable_segfault_handler: bool = debug.default_enable_segfault_handler,118 enable_segfault_handler: bool = debug.default_enable_segfault_handler,
...@@ -174,8 +177,22 @@ pub const Options = struct {...@@ -174,8 +177,22 @@ pub const Options = struct {
174 /// stack traces will just print an error to the relevant `Io.Writer` and return.177 /// stack traces will just print an error to the relevant `Io.Writer` and return.
175 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,178 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,
176179
180 /// The `Io` instance that `std.debug` uses for `std.debug.print`,
181 /// capturing stack traces, loading debug info, finding the executable's
182 /// own path, and environment variables that affect terminal mode
183 /// detection. The default is to use statically initialized singleton that
184 /// is independent from the application's `Io` instance in order to make
185 /// debugging more straightforward. For example, while debugging an `Io`
186 /// implementation based on coroutines, one likely wants `std.debug.print`
187 /// to directly write to stderr without trying to interact with the code
188 /// being debugged.
189 debug_io: Io = io_options.debug_threaded_io.?.ioBasic(),
190};
191
192pub const IoOptions = struct {
177 /// Overrides `std.Io.File.Permissions`.193 /// Overrides `std.Io.File.Permissions`.
178 FilePermissions: ?type = null,194 FilePermissions: ?type = null,
195 debug_threaded_io: ?*Io.Threaded = Io.Threaded.global_single_threaded,
179};196};
180197
181// This forces the start.zig file to be imported, and the comptime logic inside that198// This forces the start.zig file to be imported, and the comptime logic inside that