ReviewOS

also looking at this

zig-utils/zig-test-framework

Add Windows support and CI coverage

#24
Merged glennmichael123 wants to merge codex/issue-6-windows-support into main
6 files +170 -266
.github/workflows/ci.ymlmodified+10-1
Changes to .github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
1515 runs-on: ${{ matrix.os }}
1616 strategy:
1717 matrix:
18 os: [ubuntu-latest, macos-latest]
18 os: [ubuntu-latest, macos-latest, windows-latest]
1919 fail-fast: false
2020
2121 steps:
@@ -24,13 +24,22 @@ jobs:
2424
2525 - name: Read supported Zig version
2626 id: zig-version
27 shell: bash
2728 run: echo "version=$(cat .zig-version)" >> "$GITHUB_OUTPUT"
2829
2930 - name: Setup Zig
31 if: runner.os != 'Windows'
3032 uses: pantry-pm/pantry/packages/action@main
3133 with:
3234 packages: zig@${{ steps.zig-version.outputs.version }}
3335
36 - name: Setup Zig on Windows
37 if: runner.os == 'Windows'
38 uses: mlugg/setup-zig@v2
39 with:
40 version: ${{ steps.zig-version.outputs.version }}
41 mirror: https://zig.mirror.mschae23.de/zig
42
3443 - name: Verify Zig installation
3544 run: zig version
3645
README.mdmodified+6-0
Changes to README.md
@@ -857,6 +857,12 @@ generated files, interactive output, or failure handling.
857857## Requirements
858858
859859- The exact Zig development build recorded in [`.zig-version`](.zig-version)
860- Linux, macOS, or Windows. All three platforms are required CI targets.
861
862On Windows, discovery accepts native `\\` paths as well as `/` paths and
863launches `zig test` through Zig's portable process API. Ctrl-C and console-close
864events use Windows' native termination behavior; POSIX hosts install cooperative
865SIGINT/SIGTERM handlers for long-running watch and UI modes.
860866
861867See [TOOLCHAIN.md](TOOLCHAIN.md) for the compatibility policy and upgrade
862868process.
TOOLCHAIN.mdmodified+4-1
Changes to TOOLCHAIN.md
@@ -2,7 +2,10 @@
22
33The Zig Test Framework supports the exact Zig development build recorded in
44`.zig-version`. Required CI jobs use that version so pull requests are tested
5against a reproducible toolchain.
5against a reproducible toolchain on `ubuntu-latest`, `macos-latest`, and
6`windows-latest`. Windows support follows the Windows version represented by
7GitHub's current `windows-latest` hosted runner; the exact Zig build remains the
8version pinned in `.zig-version`.
69
710Because the project follows Zig development builds, CI also runs a scheduled
811compatibility check against the latest `0.17.0-dev` build. That check is
src/compat.zigmodified+118-249
Changes to src/compat.zig
@@ -28,10 +28,9 @@ fn getRealtimeClock() struct { sec: i64, nsec: i64 } {
2828/// Replaces std.time.nanoTimestamp() which was removed in Zig 0.16.
2929pub fn nanoTimestamp() i128 {
3030 if (comptime builtin.os.tag == .windows) {
31 const ft = std.os.windows.GetSystemTimeAsFileTime();
32 const EPOCH_DIFF: i128 = 11644473600 * 1_000_000_000;
33 const intervals: i128 = @as(i128, @as(u64, ft.dwHighDateTime) << 32 | @as(u64, ft.dwLowDateTime));
34 return intervals * 100 - EPOCH_DIFF;
31 const intervals: i128 = @intCast(std.os.windows.ntdll.RtlGetSystemTimePrecise());
32 const epoch_ns: i128 = std.time.epoch.windows * std.time.ns_per_s;
33 return intervals * 100 + epoch_ns;
3534 } else {
3635 const clock = getRealtimeClock();
3736 return @as(i128, clock.sec) * 1_000_000_000 + @as(i128, clock.nsec);
@@ -41,15 +40,7 @@ pub fn nanoTimestamp() i128 {
4140/// Get current wall-clock time in milliseconds since Unix epoch.
4241/// Replaces std.time.milliTimestamp() which was removed in Zig 0.16.
4342pub fn milliTimestamp() i64 {
44 if (comptime builtin.os.tag == .windows) {
45 const ft = std.os.windows.GetSystemTimeAsFileTime();
46 const EPOCH_DIFF: i64 = 11644473600000;
47 const intervals: i64 = @bitCast(@as(u64, ft.dwHighDateTime) << 32 | @as(u64, ft.dwLowDateTime));
48 return @divFloor(intervals, 10000) - EPOCH_DIFF;
49 } else {
50 const clock = getRealtimeClock();
51 return @as(i64, clock.sec) * 1000 + @divFloor(@as(i64, clock.nsec), 1_000_000);
52 }
43 return @intCast(@divFloor(nanoTimestamp(), std.time.ns_per_ms));
5344}
5445
5546// ============================================================
@@ -59,6 +50,12 @@ pub fn milliTimestamp() i64 {
5950/// Sleep for the given number of nanoseconds.
6051/// Replaces std.Thread.sleep() which was removed in Zig 0.16.
6152pub fn sleep(ns: u64) void {
53 if (comptime builtin.os.tag == .windows) {
54 var interval: std.os.windows.LARGE_INTEGER = -@as(i64, @intCast(@max(ns / 100, 1)));
55 _ = std.os.windows.ntdll.NtDelayExecution(.FALSE, &interval);
56 return;
57 }
58
6259 const s: isize = @intCast(ns / std.time.ns_per_s);
6360 const remaining_ns: isize = @intCast(ns % std.time.ns_per_s);
6461 var ts: std.c.timespec = .{ .sec = s, .nsec = remaining_ns };
@@ -97,123 +94,58 @@ pub const Mutex = struct {
9794};
9895
9996// ============================================================
100// File descriptor close wrapper
97// File I/O helpers (std.fs.cwd() removed, needs std.Io now)
10198// ============================================================
10299
103/// Close a file descriptor using libc.
104/// Replaces std.posix.close() which was removed in Zig 0.16.
105fn closeFd(fd: std.posix.fd_t) void {
106 _ = std.c.close(fd);
100fn initIo(allocator: std.mem.Allocator) std.Io.Threaded {
101 return .init(allocator, .{ .environ = currentEnviron() });
107102}
108103
109// ============================================================
110// File I/O helpers (std.fs.cwd() removed, needs std.Io now)
111// ============================================================
104fn currentEnviron() std.process.Environ {
105 return if (comptime builtin.os.tag == .windows)
106 .{ .block = .global }
107 else
108 .{ .block = .{ .slice = std.mem.span(std.c.environ) } };
109}
112110
113/// Read entire file contents using POSIX APIs.
111/// Read entire file contents using portable Zig I/O.
114112/// Replaces std.fs.cwd().openFile() + file.readToEndAlloc().
115113pub fn readFileAlloc(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
116 const path_z = try allocator.dupeSentinel(u8, path, 0);
117 defer allocator.free(path_z);
118
119 const fd = std.posix.openatZ(std.posix.AT.FDCWD, path_z, .{}, 0) catch |err| {
120 if (err == error.FileNotFound) return error.FileNotFound;
121 return err;
122 };
123 defer closeFd(fd);
124
125 // Read file in chunks
126 var result = std.ArrayList(u8).empty;
127 errdefer result.deinit(allocator);
128
129 var buf: [4096]u8 = undefined;
130 while (true) {
131 const n = std.posix.read(fd, &buf) catch |err| return err;
132 if (n == 0) break;
133 try result.appendSlice(allocator, buf[0..n]);
134 }
135
136 return result.toOwnedSlice(allocator);
114 var threaded = initIo(allocator);
115 defer threaded.deinit();
116 return std.Io.Dir.cwd().readFileAlloc(threaded.io(), path, allocator, .unlimited);
137117}
138118
139/// Write content to a file using POSIX APIs.
119/// Write content to a file using portable Zig I/O.
140120/// Replaces std.fs.cwd().createFile() + file.writeAll().
141121pub fn writeFile(allocator: std.mem.Allocator, path: []const u8, content: []const u8) !void {
142 const path_z = try allocator.dupeSentinel(u8, path, 0);
143 defer allocator.free(path_z);
144
145 const fd = std.posix.openatZ(std.posix.AT.FDCWD, path_z, .{
146 .ACCMODE = .WRONLY,
147 .CREAT = true,
148 .TRUNC = true,
149 }, 0o644) catch |err| return err;
150 defer closeFd(fd);
151
152 var remaining = content;
153 while (remaining.len > 0) {
154 const rc = std.c.write(fd, remaining.ptr, remaining.len);
155 if (rc < 0) return error.Unexpected;
156 const written: usize = @intCast(rc);
157 remaining = remaining[written..];
158 }
122 var threaded = initIo(allocator);
123 defer threaded.deinit();
124 try std.Io.Dir.cwd().writeFile(threaded.io(), .{ .sub_path = path, .data = content });
159125}
160126
161/// Delete a file using POSIX APIs.
127/// Delete a file using portable Zig I/O.
162128/// Replaces std.fs.cwd().deleteFile().
163129pub fn deleteFile(allocator: std.mem.Allocator, path: []const u8) !void {
164 const path_z = try allocator.dupeSentinel(u8, path, 0);
165 defer allocator.free(path_z);
166 const rc = std.c.unlink(path_z);
167 if (rc != 0) {
168 switch (std.c.errno(rc)) {
169 .NOENT => return error.FileNotFound,
170 else => return error.Unexpected,
171 }
172 }
130 var threaded = initIo(allocator);
131 defer threaded.deinit();
132 try std.Io.Dir.cwd().deleteFile(threaded.io(), path);
173133}
174134
175/// Delete an empty directory using POSIX APIs.
135/// Delete an empty directory using portable Zig I/O.
176136/// Replaces std.fs.cwd().deleteDir().
177137pub fn deleteDir(allocator: std.mem.Allocator, path: []const u8) !void {
178 const path_z = try allocator.dupeSentinel(u8, path, 0);
179 defer allocator.free(path_z);
180 const rc = std.c.rmdir(path_z);
181 if (rc != 0) {
182 switch (std.c.errno(rc)) {
183 .NOENT => return error.FileNotFound,
184 else => return error.Unexpected,
185 }
186 }
138 var threaded = initIo(allocator);
139 defer threaded.deinit();
140 try std.Io.Dir.cwd().deleteDir(threaded.io(), path);
187141}
188142
189/// Create directories recursively using POSIX APIs.
143/// Create directories recursively using portable Zig I/O.
190144/// Replaces std.fs.cwd().makePath().
191145pub fn makePath(allocator: std.mem.Allocator, path: []const u8) !void {
192 const path_z = try allocator.dupeSentinel(u8, path, 0);
193 defer allocator.free(path_z);
194
195 const rc = std.c.mkdir(path_z, 0o755);
196 if (rc == 0) return;
197
198 switch (std.c.errno(rc)) {
199 .EXIST => return, // Already exists
200 .NOENT => {
201 // Parent doesn't exist, find and create it
202 if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| {
203 if (sep > 0) {
204 try makePath(allocator, path[0..sep]);
205 // Retry creating the directory
206 const retry_z = try allocator.dupeSentinel(u8, path, 0);
207 defer allocator.free(retry_z);
208 const rc2 = std.c.mkdir(retry_z, 0o755);
209 if (rc2 != 0 and std.c.errno(rc2) != .EXIST) {
210 return error.Unexpected;
211 }
212 }
213 }
214 },
215 else => return error.Unexpected,
216 }
146 var threaded = initIo(allocator);
147 defer threaded.deinit();
148 try std.Io.Dir.cwd().createDirPath(threaded.io(), path);
217149}
218150
219151// ============================================================
@@ -226,56 +158,44 @@ pub const DirEntry = struct {
226158 kind: enum { file, directory, sym_link, other },
227159};
228160
229/// A simple directory iterator using POSIX APIs.
161/// A simple directory iterator using portable Zig I/O.
230162/// Replaces std.fs.openDirAbsolute() + dir.iterate() which was removed in Zig 0.16.
231163pub const DirIterator = struct {
232 dir: *std.c.DIR,
233 path_buf: [1024]u8,
164 threaded: *std.Io.Threaded,
165 dir: std.Io.Dir,
166 iterator: std.Io.Dir.Iterator,
234167
235168 pub fn open(dir_path: []const u8) !DirIterator {
236 var path_buf: [1024:0]u8 = @splat(0);
237 if (dir_path.len >= path_buf.len) return error.NameTooLong;
238 @memcpy(path_buf[0..dir_path.len], dir_path);
169 const threaded = try std.heap.page_allocator.create(std.Io.Threaded);
170 errdefer std.heap.page_allocator.destroy(threaded);
171 threaded.* = initIo(std.heap.page_allocator);
172 errdefer threaded.deinit();
239173
240 const dir = std.c.opendir(&path_buf);
241 if (dir == null) return error.FileNotFound;
174 const dir = try std.Io.Dir.cwd().openDir(threaded.io(), dir_path, .{ .iterate = true });
242175 return .{
243 .dir = dir.?,
244 .path_buf = undefined,
176 .threaded = threaded,
177 .dir = dir,
178 .iterator = dir.iterateAssumeFirstIteration(),
245179 };
246180 }
247181
248182 pub fn next(self: *DirIterator) !?DirEntry {
249 while (true) {
250 const entry_opt = std.c.readdir(self.dir);
251 if (entry_opt == null) return null;
252 const entry = entry_opt.?;
253
254 // Get name as slice
255 const name_ptr: [*]const u8 = @ptrCast(&entry.name);
256 const name_len = if (@hasField(@TypeOf(entry.*), "namlen"))
257 entry.namlen
258 else
259 std.mem.indexOfScalar(u8, name_ptr[0..256], 0) orelse 256;
260 const name = name_ptr[0..name_len];
261
262 // Skip . and ..
263 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
264
265 return .{
266 .name = name,
267 .kind = switch (entry.type) {
268 std.c.DT.DIR => .directory,
269 std.c.DT.REG => .file,
270 std.c.DT.LNK => .sym_link,
271 else => .other,
272 },
273 };
274 }
183 const entry = try self.iterator.next(self.threaded.io()) orelse return null;
184 return .{
185 .name = entry.name,
186 .kind = switch (entry.kind) {
187 .directory => .directory,
188 .file => .file,
189 .sym_link => .sym_link,
190 else => .other,
191 },
192 };
275193 }
276194
277195 pub fn close(self: *DirIterator) void {
278 _ = std.c.closedir(self.dir);
196 self.dir.close(self.threaded.io());
197 self.threaded.deinit();
198 std.heap.page_allocator.destroy(self.threaded);
279199 }
280200};
281201
@@ -298,7 +218,8 @@ pub const SpawnResult = union(enum) {
298218};
299219
300220/// Spawn a child process and wait for it to complete.
301/// Replaces std.process.Child.init() + spawnAndWait() which was removed in Zig 0.16.
221/// Uses Zig's portable process API, including PATH lookup and Windows process
222/// creation, while preserving the compatibility result used by callers.
302223pub fn spawnAndWait(
303224 allocator: std.mem.Allocator,
304225 argv: []const []const u8,
@@ -307,105 +228,30 @@ pub fn spawnAndWait(
307228) !SpawnResult {
308229 if (argv.len == 0) return error.InvalidArgument;
309230
310 // Convert argv to null-terminated C strings
311 const c_argv = try allocator.alloc(?[*:0]const u8, argv.len + 1);
312 defer allocator.free(c_argv);
313
314 for (argv, 0..) |arg, i| {
315 const z = try allocator.dupeSentinel(u8, arg, 0);
316 c_argv[i] = z.ptr;
317 }
318 c_argv[argv.len] = null;
319
320 // Fork
321 const pid = std.c.fork();
322 if (pid < 0) return error.ForkFailed;
323
324 if (pid == 0) {
325 // Child process
326 // Handle stdout
327 switch (stdout_behavior) {
328 .Ignore => {
329 const dev_null: [*:0]const u8 = "/dev/null";
330 const null_fd = std.posix.openatZ(std.posix.AT.FDCWD, dev_null, .{ .ACCMODE = .WRONLY }, 0) catch std.process.exit(127);
331 if (std.c.dup2(null_fd, 1) < 0) std.process.exit(127);
332 closeFd(null_fd);
333 },
334 else => {},
335 }
336
337 // Handle stderr
338 switch (stderr_behavior) {
339 .Ignore => {
340 const dev_null: [*:0]const u8 = "/dev/null";
341 const null_fd = std.posix.openatZ(std.posix.AT.FDCWD, dev_null, .{ .ACCMODE = .WRONLY }, 0) catch std.process.exit(127);
342 if (std.c.dup2(null_fd, 2) < 0) std.process.exit(127);
343 closeFd(null_fd);
344 },
345 else => {},
346 }
347
348 // Exec - use execve with PATH lookup and current environment
349 const envp: [*:null]const ?[*:0]const u8 = @ptrCast(std.c.environ);
350
351 // Try direct execution first
352 _ = std.c.execve(c_argv[0].?, @ptrCast(c_argv.ptr), envp);
353
354 // If direct exec failed, try PATH lookup
355 const cmd = std.mem.sliceTo(c_argv[0].?, 0);
356 if (std.mem.indexOfScalar(u8, cmd, '/') == null) {
357 // Search PATH
358 var path_search: ?[*:0]const u8 = null;
359 var env_idx: usize = 0;
360 while (std.c.environ[env_idx]) |env_entry| : (env_idx += 1) {
361 const entry = std.mem.sliceTo(env_entry, 0);
362 if (std.mem.startsWith(u8, entry, "PATH=")) {
363 path_search = @ptrCast(env_entry + 5);
364 break;
365 }
366 }
367
368 if (path_search) |path_val| {
369 const path_str = std.mem.sliceTo(path_val, 0);
370 var path_iter = std.mem.splitScalar(u8, path_str, ':');
371 while (path_iter.next()) |dir| {
372 var full_path: [1024:0]u8 = @splat(0);
373 if (dir.len + 1 + cmd.len < full_path.len) {
374 @memcpy(full_path[0..dir.len], dir);
375 full_path[dir.len] = '/';
376 @memcpy(full_path[dir.len + 1 ..][0..cmd.len], cmd);
377 _ = std.c.execve(&full_path, @ptrCast(c_argv.ptr), envp);
378 }
379 }
380 }
381 }
382 std.process.exit(127);
383 }
384
385 // Parent process - free the duped strings
386 for (argv, 0..) |_, i| {
387 const z: [*:0]const u8 = c_argv[i].?;
388 const len = std.mem.len(z);
389 allocator.free(z[0 .. len + 1]);
390 }
391
392 // Wait for child
393 var status: c_int = 0;
394 const wait_result = std.c.waitpid(pid, &status, 0);
395 if (wait_result < 0) return error.WaitFailed;
396
397 const ustatus: u32 = @bitCast(status);
398
399 // Check if exited normally (WIFEXITED)
400 if (ustatus & 0x7f == 0) {
401 // WEXITSTATUS
402 const exit_code: u8 = @intCast((ustatus >> 8) & 0xff);
403 return .{ .Exited = exit_code };
404 }
231 var threaded = initIo(allocator);
232 defer threaded.deinit();
233 const io = threaded.io();
234
235 var child = try std.process.spawn(io, .{
236 .argv = argv,
237 .stdout = stdBehavior(stdout_behavior),
238 .stderr = stdBehavior(stderr_behavior),
239 .create_no_window = false,
240 });
241 const term = try child.wait(io);
242 return switch (term) {
243 .exited => |code| .{ .Exited = code },
244 .signal => |signal| .{ .Signal = @backingInt(signal) },
245 .stopped => |signal| .{ .Signal = @backingInt(signal) },
246 .unknown => |status| .{ .Unknown = status },
247 };
248}
405249
406 // Signal
407 const sig = ustatus & 0x7f;
408 return .{ .Signal = sig };
250fn stdBehavior(behavior: StdBehavior) std.process.SpawnOptions.StdIo {
251 return switch (behavior) {
252 .Inherit, .Pipe => .inherit,
253 .Ignore => .ignore,
254 };
409255}
410256
411257// ============================================================
@@ -452,10 +298,33 @@ pub const ArrayListWriter = struct {
452298 }
453299};
454300
455// ============================================================
456// Signal handler type
457// ============================================================
301test "portable file helpers preserve nested paths" {
302 const allocator = std.testing.allocator;
303 const nonce = nanoTimestamp();
304 const dir_path = try std.fmt.allocPrint(allocator, ".zig-cache/compat-{d}", .{nonce});
305 defer allocator.free(dir_path);
306 const file_path = try std.fs.path.join(allocator, &.{ dir_path, "roundtrip.txt" });
307 defer allocator.free(file_path);
308
309 try makePath(allocator, dir_path);
310 defer deleteDir(allocator, dir_path) catch {};
311 defer deleteFile(allocator, file_path) catch {};
312
313 try writeFile(allocator, file_path, "portable");
314 const contents = try readFileAlloc(allocator, file_path);
315 defer allocator.free(contents);
316 try std.testing.expectEqualStrings("portable", contents);
317}
458318
459/// The signal type used in signal handlers.
460/// In Zig 0.16, this changed from c_int to std.posix.SIG enum.
461pub const SignalType = std.posix.SIG;
319test "portable process helper preserves exit codes" {
320 const argv: []const []const u8 = if (comptime builtin.os.tag == .windows)
321 &.{ "cmd.exe", "/C", "exit", "7" }
322 else
323 &.{ "sh", "-c", "exit 7" };
324
325 const result = try spawnAndWait(std.testing.allocator, argv, .Ignore, .Ignore);
326 switch (result) {
327 .Exited => |code| try std.testing.expectEqual(@as(u8, 7), code),
328 else => return error.UnexpectedTermination,
329 }
330}
src/discovery.zigmodified+15-5
Changes to src/discovery.zig
@@ -119,11 +119,7 @@ fn scanDirectory(
119119 else
120120 full_path;
121121
122 // Skip leading slash in relative path
123 const clean_relative = if (relative_path.len > 0 and relative_path[0] == '/')
124 relative_path[1..]
125 else
126 relative_path;
122 const clean_relative = cleanRelativePath(relative_path);
127123
128124 try result.addFile(full_path, clean_relative, entry.name);
129125 }
@@ -135,6 +131,15 @@ fn scanDirectory(
135131 }
136132}
137133
134/// Removes separators introduced by slicing the root prefix. Accept both
135/// spellings so paths remain stable when a project moves between POSIX and
136/// Windows hosts.
137fn cleanRelativePath(path: []const u8) []const u8 {
138 var start: usize = 0;
139 while (start < path.len and (path[start] == '/' or path[start] == '\\')) : (start += 1) {}
140 return path[start..];
141}
142
138143/// Check if a filename matches the pattern
139144fn matchesPattern(filename: []const u8, pattern: []const u8) bool {
140145 // Simple pattern matching - check if filename ends with pattern
@@ -160,3 +165,8 @@ test "matchesPattern with exact match" {
160165 try std.testing.expect(matchesPattern("test.zig", "test.zig"));
161166 try std.testing.expect(!matchesPattern("other.zig", "test.zig"));
162167}
168
169test "relative paths discard POSIX and Windows separators" {
170 try std.testing.expectEqualStrings("suite/sample.test.zig", cleanRelativePath("/suite/sample.test.zig"));
171 try std.testing.expectEqualStrings("suite\\sample.test.zig", cleanRelativePath("\\suite\\sample.test.zig"));
172}
src/main.zigmodified+17-10
Changes to src/main.zig
@@ -1,24 +1,30 @@
11const std = ;
2const builtin = ;
23const lib = ;
34const compat = lib.compat;
45
56// Global signal handler state
67var shutdown_requested = std.atomic.Value(bool).init(false);
78
8/// Signal handler for SIGINT and SIGTERM
9fn handleSignal(sig: std.posix.SIG) callconv(.c) void {
10 _ = sig;
11 shutdown_requested.store(true, .monotonic);
12 std.debug.print("\n\nShutdown requested... cleaning up\n", .{});
13}
14
159/// Install signal handlers
1610fn installSignalHandlers() !void {
11 // Windows console control events retain their native default behavior:
12 // Ctrl-C and console close terminate the CLI. POSIX platforms install
13 // cooperative handlers so long-running watch/UI modes can clean up.
14 if (comptime builtin.os.tag == .windows) return;
15
16 const Handler = struct {
17 fn handle(sig: std.posix.SIG) callconv(.c) void {
18 _ = sig;
19 shutdown_requested.store(true, .monotonic);
20 std.debug.print("\n\nShutdown requested... cleaning up\n", .{});
21 }
22 };
1723 const posix = std.posix;
1824
1925 // Install SIGINT handler (Ctrl+C)
2026 const sigint_action = posix.Sigaction{
21 .handler = .{ .handler = handleSignal },
27 .handler = .{ .handler = Handler.handle },
2228 .mask = posix.sigemptyset(),
2329 .flags = 0,
2430 };
@@ -26,7 +32,7 @@ fn installSignalHandlers() !void {
2632
2733 // Install SIGTERM handler
2834 const sigterm_action = posix.Sigaction{
29 .handler = .{ .handler = handleSignal },
35 .handler = .{ .handler = Handler.handle },
3036 .mask = posix.sigemptyset(),
3137 .flags = 0,
3238 };
@@ -52,7 +58,8 @@ pub fn main(init: std.process.Init.Minimal) !void {
5258 for (args_list.items) |arg| allocator.free(arg);
5359 args_list.deinit(allocator);
5460 }
55 var args_iter = std.process.Args.Iterator.init(init.args);
61 var args_iter = try std.process.Args.Iterator.initAllocator(init.args, allocator);
62 defer args_iter.deinit();
5663 while (args_iter.next()) |arg| {
5764 try args_list.append(allocator, try allocator.dupe(u8, arg));
5865 }