diff --git a/cli/src/commands/cancel.zig b/cli/src/commands/cancel.zig index 1217a89..e33ed58 100644 --- a/cli/src/commands/cancel.zig +++ b/cli/src/commands/cancel.zig @@ -11,8 +11,8 @@ const manifest_lib = @import("../manifest.zig"); pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var flags = core.flags.CommonFlags{}; var force = false; - var targets = std.ArrayList([]const u8).init(allocator); - defer targets.deinit(); + var targets: std.ArrayList([]const u8) = .empty; + defer targets.deinit(allocator); // Parse arguments var i: usize = 0; @@ -28,7 +28,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { core.output.errorMsg("cancel", "Unknown option"); return error.InvalidArgs; } else { - try targets.append(arg); + try targets.append(allocator, arg); } } @@ -129,7 +129,7 @@ fn cancelLocal(allocator: std.mem.Allocator, run_id: []const u8, force: bool, js if (!force) { // Wait 5 seconds for graceful termination - std.time.sleep(5 * std.time.ns_per_s); + std.Thread.sleep(5 * std.time.ns_per_s); } // Check if still running, send SIGKILL if needed @@ -170,7 +170,7 @@ fn cancelLocal(allocator: std.mem.Allocator, run_id: []const u8, force: bool, js /// Check if process is still running fn isProcessRunning(pid: i32) bool { const result = std.posix.kill(pid, 0); - return result == error.PermissionDenied or result == {}; + return if (result) |_| true else |err| err == error.PermissionDenied; } /// Cancel server job diff --git a/cli/src/commands/log.zig b/cli/src/commands/log.zig index 4b7a0c1..8eec012 100644 --- a/cli/src/commands/log.zig +++ b/cli/src/commands/log.zig @@ -12,7 +12,7 @@ const crypto = @import("../utils/crypto.zig"); /// Usage: /// ml logs # Fetch logs from local file or server /// ml logs --follow # Stream logs from server -pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { +pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var flags = core.flags.CommonFlags{}; var command_args = try core.flags.parseCommon(allocator, args, &flags); defer command_args.deinit(allocator); @@ -68,7 +68,7 @@ fn fetchLocalLogs(allocator: std.mem.Allocator, target: []const u8, cfg: *const defer allocator.free(manifest_path); // Read manifest to get artifact path - const manifest = try manifest_lib.readManifest(manifest_path, allocator); + var manifest = try manifest_lib.readManifest(manifest_path, allocator); defer manifest.deinit(allocator); // Build output.log path @@ -90,8 +90,8 @@ fn fetchLocalLogs(allocator: std.mem.Allocator, target: []const u8, cfg: *const if (json) { // Escape content for JSON - var escaped = std.ArrayList(u8).init(allocator); - defer escaped.deinit(); + var escaped: std.ArrayList(u8) = .empty; + defer escaped.deinit(allocator); const writer = escaped.writer(allocator); for (content) |c| { @@ -130,7 +130,7 @@ fn fetchServerLogs(allocator: std.mem.Allocator, target: []const u8, cfg: config var client = try ws.Client.connect(allocator, ws_url, cfg.api_key); defer client.close(); - try client.sendFetchLogs(target, api_key_hash); + try client.sendGetLogs(target, api_key_hash); const message = try client.receiveMessage(allocator); defer allocator.free(message); diff --git a/cli/src/commands/run.zig b/cli/src/commands/run.zig index 7d79a0f..7716ca3 100644 --- a/cli/src/commands/run.zig +++ b/cli/src/commands/run.zig @@ -1,9 +1,29 @@ const std = @import("std"); -const config = @import("../config.zig"); const db = @import("../db.zig"); -const core = @import("../core.zig"); -const colors = @import("../utils/colors.zig"); const manifest_lib = @import("../manifest.zig"); +const colors = @import("../utils/colors.zig"); +const core = @import("../core.zig"); +const config = @import("../config.zig"); + +extern fn execvp(path: [*:0]const u8, argv: [*]const ?[*:0]const u8) c_int; +extern fn waitpid(pid: c_int, status: *c_int, flags: c_int) c_int; + +// Get current environment from libc +extern var environ: [*]const ?[*:0]const u8; + +// Inline macros for wait status parsing (not available as extern on macOS) +fn WIFEXITED(status: c_int) c_int { + return if ((status & 0x7F) == 0) 1 else 0; +} +fn WEXITSTATUS(status: c_int) c_int { + return (status >> 8) & 0xFF; +} +fn WIFSIGNALED(status: c_int) c_int { + return if (((status & 0x7F) != 0x7F) and ((status & 0x7F) != 0)) 1 else 0; +} +fn WTERMSIG(status: c_int) c_int { + return status & 0x7F; +} const Manifest = manifest_lib.RunManifest; /// Run command - always executes locally @@ -97,7 +117,7 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { _ = try db.DB.step(stmt); // Write manifest - try manifest_lib.writeManifest(manifest, manifest_path); + try manifest_lib.writeManifest(manifest, manifest_path, allocator); // Fork and execute const output_log_path = try std.fs.path.join(allocator, &[_][]const u8{ @@ -176,22 +196,22 @@ fn resolveCommand(allocator: std.mem.Allocator, cfg: *const config.Config, args: if (cfg.experiment) |exp| { if (exp.entrypoint.len > 0) { // Split entrypoint on spaces - var argv = std.ArrayList([]const u8).init(allocator); + var argv: std.ArrayList([]const u8) = .empty; // Parse entrypoint (split on spaces) var iter = std.mem.splitScalar(u8, exp.entrypoint, ' '); while (iter.next()) |part| { if (part.len > 0) { - try argv.append(try allocator.dupe(u8, part)); + try argv.append(allocator, try allocator.dupe(u8, part)); } } // Append args for (args) |arg| { - try argv.append(try allocator.dupe(u8, arg)); + try argv.append(allocator, try allocator.dupe(u8, arg)); } - return argv.toOwnedSlice(); + return try argv.toOwnedSlice(allocator); } } @@ -246,7 +266,6 @@ fn executeAndCapture( // Create output file var output_file = try std.fs.cwd().createFile(output_path, .{}); defer output_file.close(); - const output_writer = output_file.writer(); // Create pipe for stdout const pipe = try std.posix.pipe(); @@ -263,13 +282,13 @@ fn executeAndCapture( std.posix.close(pipe[0]); // Close read end // Redirect stdout to pipe - _ = std.posix.dup2(pipe[1], std.posix.STDOUT_FILENO); - _ = std.posix.dup2(pipe[1], std.posix.STDERR_FILENO); + _ = std.posix.dup2(pipe[1], std.posix.STDOUT_FILENO) catch std.process.exit(1); + _ = std.posix.dup2(pipe[1], std.posix.STDERR_FILENO) catch std.process.exit(1); std.posix.close(pipe[1]); - // Execute command - const err = std.posix.execvpe(command[0], command, &[_:null]?[*:0]const u8{null}); - std.log.err("Failed to execute {s}: {}", .{ command[0], err }); + // Execute command using execvp (uses current environ) + const c_err = execvp(@ptrCast(command[0].ptr), @ptrCast(command.ptr)); + std.log.err("Failed to execute {s}: {}", .{ command[0], c_err }); std.process.exit(1); unreachable; } @@ -299,7 +318,7 @@ fn executeAndCapture( if (bytes_read == 0) break; // Write to output file - try output_writer.writeAll(buf[0..bytes_read]); + try output_file.writeAll(buf[0..bytes_read]); // Parse lines for (buf[0..bytes_read]) |byte| { @@ -318,14 +337,14 @@ fn executeAndCapture( } // Wait for child - var status: u32 = 0; - _ = std.posix.waitpid(pid, &status, 0); + var status: c_int = 0; + _ = waitpid(@intCast(pid), &status, 0); // Parse exit code - if (std.os.linux.W.IFEXITED(status)) { - return std.os.linux.W.EXITSTATUS(status); - } else if (std.os.linux.W.IFSIGNALED(status)) { - return 128 + std.os.linux.W.TERMSIG(status); + if (WIFEXITED(status) != 0) { + return WEXITSTATUS(status); + } else if (WIFSIGNALED(status) != 0) { + return 128 + WTERMSIG(status); } return -1; diff --git a/cli/src/native/hash.zig b/cli/src/native/hash.zig index 59835f5..c619dae 100644 --- a/cli/src/native/hash.zig +++ b/cli/src/native/hash.zig @@ -12,17 +12,17 @@ pub const HashError = error{ // Global context for reuse across multiple hash operations var global_ctx: ?*c.fh_context_t = null; -var ctx_initialized = std.atomic.Atomic(bool).init(false); +var ctx_initialized = std.atomic.Value(bool).init(false); var init_mutex = std.Thread.Mutex{}; /// Initialize global hash context once (thread-safe) pub fn init() !void { - if (ctx_initialized.load(.Acquire)) return; + if (ctx_initialized.load(.seq_cst)) return; init_mutex.lock(); defer init_mutex.unlock(); - if (ctx_initialized.load(.Relaxed)) return; // Double-check + if (ctx_initialized.load(.seq_cst)) return; // Double-check const start = std.time.milliTimestamp(); global_ctx = c.fh_init(0); // 0 = auto-detect threads @@ -32,7 +32,7 @@ pub fn init() !void { return HashError.ContextInitFailed; } - ctx_initialized.store(true, .Release); + ctx_initialized.store(true, .seq_cst); std.log.info("[native] hash context initialized: {}ms", .{elapsed}); } diff --git a/cli/src/utils/parallel_walk.zig b/cli/src/utils/parallel_walk.zig index 68cc964..917fd24 100644 --- a/cli/src/utils/parallel_walk.zig +++ b/cli/src/utils/parallel_walk.zig @@ -15,7 +15,7 @@ const WorkQueue = struct { fn init(allocator: std.mem.Allocator) WorkQueue { return .{ - .items = std.ArrayList(WorkItem).init(allocator), + .items = .empty, .mutex = .{}, .condition = .{}, .done = false,