fix(cli): Zig 0.15 core API changes

- ArrayList: .init(allocator) → .empty, add allocator param to append/deinit/toOwnedSlice
- Atomic: std.atomic.Atomic → std.atomic.Value, lowercase order names (.seq_cst)
- Process: execvp instead of execvpe, inline wait status macros for macOS
- Time: std.time.sleep → std.Thread.sleep
- Error handling: fix isProcessRunning error union comparison
This commit is contained in:
Jeremie Fraeys 2026-02-21 17:59:05 -05:00
parent 20fde4f79d
commit ccd1dd7a4d
No known key found for this signature in database
5 changed files with 55 additions and 36 deletions

View file

@ -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

View file

@ -12,7 +12,7 @@ const crypto = @import("../utils/crypto.zig");
/// Usage:
/// ml logs <run_id> # Fetch logs from local file or server
/// ml logs <run_id> --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);

View file

@ -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;

View file

@ -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});
}

View file

@ -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,