Skip to content

Commit e82a3e9

Browse files
committed
feat: Zig 0.16.0 compatibility — cross-compile, CLI args, process spawn
## Cross-compilation fixes (Zig 0.16.0 API changes) - Replace std.c.stat with Io.Dir.cwd().statFile — std.c removed from cross-compilation C headers in Zig 0.16.0 - Rename Permissions.unixNew → Permissions.fromMode — API renamed - Remove unused arena parameter from maybe_install_musl_runtime (arena.dupeZ replaced by statFile which takes a string directly) - Comptime-guard the musl runtime call site to avoid unused-parameter warnings on macOS (the function only runs on Linux) ## Process spawning (cross-platform) - Replace std.process.replace with std.process.spawn + child.wait std.process.replace does not work reliably on macOS arm64 in Zig 0.16.0 - Unify Windows/Unix code paths — both use the same spawn+wait pattern (removes the broken Windows-only std.process.Child.spawnAndWait) - Use .exited (lowercase) for Child.Term enum, var for mutable Child ## CLI argument passing - Pass CLI args via ADO_ARGS environment variable in the Zig wrapper (the -extra BEAM flag does not reliably reach init:get_plain_arguments() in certain release configurations) - Join args_trimmed into a space-separated string and set as ADO_ARGS All targets compile: macOS (arm64/x86_64), Linux (x86_64/arm64), Windows (x86_64)
1 parent de9c866 commit e82a3e9

2 files changed

Lines changed: 44 additions & 46 deletions

File tree

src/erlang_launcher.zig

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ pub fn launch(io: Io, install_dir: []const u8, env_map: *std.process.Environ.Map
4747
}
4848

4949
// Set all the required release arguments
50+
51+
// Pass CLI args via ADO_ARGS env var (reliable, works around -extra quirks)
52+
var args_buf: [4096]u8 = undefined;
53+
var args_len: usize = 0;
54+
for (args_trimmed, 0..) |arg, i| {
55+
if (i > 0) {
56+
args_buf[args_len] = ' ';
57+
args_len += 1;
58+
}
59+
const end = args_len + arg.len;
60+
if (end < args_buf.len) {
61+
@memcpy(args_buf[args_len..end], arg);
62+
args_len = end;
63+
}
64+
}
65+
try env_map.put("ADO_ARGS", args_buf[0..args_len]);
66+
5067
const erlang_cli = &[_][]const u8{
5168
erl_bin_path[0..],
5269
"-elixir ansi_enabled true",
@@ -67,54 +84,37 @@ pub fn launch(io: Io, install_dir: []const u8, env_map: *std.process.Environ.Map
6784
"-extra",
6885
};
6986

70-
if (builtin.os.tag == .windows) {
71-
// Fix up Windows 10+ consoles having ANSI escape support, but only if we set some flags
72-
const final_args = try std.mem.concat(allocator, []const u8, &.{ erlang_cli, args_trimmed });
73-
74-
try env_map.put("RELEASE_ROOT", install_dir);
75-
try env_map.put("RELEASE_SYS_CONFIG", config_sys_path_no_ext);
76-
try env_map.put("__BURRITO", "1");
77-
try env_map.put("__BURRITO_BIN_PATH", self_path);
78-
79-
var win_child_proc = std.process.Child.init(final_args, allocator);
80-
win_child_proc.env_map = env_map;
81-
win_child_proc.stdout_behavior = .Inherit;
82-
win_child_proc.stdin_behavior = .Inherit;
83-
84-
log.debug("CLI List: {any}", .{final_args});
85-
86-
const win_term = try win_child_proc.spawnAndWait();
87-
switch (win_term) {
88-
.Exited => |code| {
89-
std.process.exit(code);
90-
},
91-
else => std.process.exit(1),
92-
}
93-
} else {
94-
const final_args = try std.mem.concat(allocator, []const u8, &.{ erlang_cli, args_trimmed });
87+
// Cross-platform: build args once, set env, spawn child, wait for exit
88+
const final_args = try std.mem.concat(allocator, []const u8, &.{ erlang_cli, args_trimmed });
9589

96-
log.debug("CLI List: {any}", .{final_args});
90+
log.debug("CLI List: {any}", .{final_args});
9791

92+
try env_map.put("RELEASE_ROOT", install_dir);
93+
try env_map.put("RELEASE_SYS_CONFIG", config_sys_path_no_ext);
94+
try env_map.put("__BURRITO", "1");
95+
try env_map.put("__BURRITO_BIN_PATH", self_path);
96+
97+
// Unix: set ROOTDIR, BINDIR, LD_LIBRARY_PATH for NIF .so files
98+
if (builtin.os.tag != .windows) {
9899
try env_map.put("ROOTDIR", install_dir[0..]);
99100
try env_map.put("BINDIR", erts_bin_path[0..]);
100-
try env_map.put("RELEASE_ROOT", install_dir);
101-
try env_map.put("RELEASE_SYS_CONFIG", config_sys_path_no_ext);
102-
try env_map.put("__BURRITO", "1");
103-
try env_map.put("__BURRITO_BIN_PATH", self_path);
104101

105-
// Extend LD_LIBRARY_PATH so NIF .so files can find system shared
106-
// libraries (e.g. libgcc_s.so.1) when using a custom glibc ERTS
107102
const system_lib_paths = "/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/lib:/usr/lib";
108103
if (env_map.get("LD_LIBRARY_PATH")) |existing| {
109104
const combined = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ existing, system_lib_paths });
110105
try env_map.put("LD_LIBRARY_PATH", combined);
111106
} else {
112107
try env_map.put("LD_LIBRARY_PATH", system_lib_paths);
113108
}
109+
}
114110

115-
return std.process.replace(io, .{
116-
.argv = final_args,
117-
.environ_map = env_map,
118-
});
111+
var child = try std.process.spawn(io, .{
112+
.argv = final_args,
113+
.environ_map = env_map,
114+
});
115+
const term = try child.wait(io);
116+
switch (term) {
117+
.exited => |code| std.process.exit(code),
118+
else => std.process.exit(1),
119119
}
120120
}

src/wrapper.zig

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub fn main(init: std.process.Init) !void {
4646
const environ = init.minimal.environ;
4747

4848
// If on linux, maybe install the musl libc runtime file for our pre-compiled Erlang
49-
try maybe_install_musl_runtime(io, arena);
49+
if (comptime IS_LINUX) try maybe_install_musl_runtime(io);
5050

5151
const self_path = try std.process.executablePathAlloc(io, arena);
5252

@@ -218,16 +218,14 @@ fn install_dir_error(arena: std.mem.Allocator) void {
218218
std.process.exit(1);
219219
}
220220

221-
fn maybe_install_musl_runtime(io: Io, arena: std.mem.Allocator) !void {
222-
if (comptime IS_LINUX and !std.mem.eql(u8, build_options.MUSL_RUNTIME_PATH, "")) {
223-
// Check if the file was already extracted
224-
const cStr = try arena.dupeZ(u8, build_options.MUSL_RUNTIME_PATH);
225-
var statBuffer: std.c.Stat = undefined;
226-
const statResult = std.c.stat(cStr, &statBuffer);
221+
fn maybe_install_musl_runtime(io: Io) !void {
222+
if (!std.mem.eql(u8, build_options.MUSL_RUNTIME_PATH, "")) {
223+
// Check if the file was already extracted using std.fs API (cross-platform)
224+
const file_exists = Io.Dir.cwd().statFile(io, build_options.MUSL_RUNTIME_PATH, .{}) catch null;
227225

228-
if (statResult == 0) {
226+
if (file_exists != null) {
229227
// File exists
230-
log.debug("The musl runtime file is already preset. Continuing.", .{});
228+
log.debug("The musl runtime file is already present. Continuing.", .{});
231229
return;
232230
}
233231

@@ -237,7 +235,7 @@ fn maybe_install_musl_runtime(io: Io, arena: std.mem.Allocator) !void {
237235
};
238236
defer file.close(io);
239237

240-
const exec_permissions = Io.File.Permissions.unixNew(0o754);
238+
const exec_permissions = Io.File.Permissions.fromMode(@intCast(0o754));
241239
try file.setPermissions(io, exec_permissions);
242240

243241
const MUSL_RUNTIME_BYTES = @embedFile("musl-runtime.so");

0 commit comments

Comments
 (0)