Skip to content

Commit e18b81d

Browse files
committed
feat: Zig 0.16.0 compatibility
Cross-compilation fixes (Zig 0.16.0 API changes): - Replace std.c.stat with Io.Dir.cwd().statFile - Rename Permissions.unixNew → Permissions.fromMode - Remove unused arena parameter from maybe_install_musl_runtime - Comptime-guard the musl runtime call site on macOS - Guard Permissions.toMode() on Windows (enum, no POSIX mode bits) Process spawning (cross-platform): - Replace std.process.replace with std.process.spawn + child.wait (std.process.replace does not work reliably on macOS arm64) - Unify Windows/Unix code paths via the same spawn+wait pattern CI: - Add Zig 0.16.0 to the cross-build matrix - Add macOS arm64 (macos-14) runner
1 parent de9c866 commit e18b81d

4 files changed

Lines changed: 77 additions & 60 deletions

File tree

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,47 @@
11
name: "Burrito Cross Build Build Tests"
2+
23
on:
34
pull_request:
45
push:
56
branches: [main]
7+
68
jobs:
79
build_examples:
8-
name: build_examples_${{ matrix.host }}
10+
name: build_examples_${{ matrix.host }}_${{ matrix.zig }}
911
runs-on: ${{ matrix.host }}
1012
strategy:
13+
fail-fast: false
1114
matrix:
1215
host: [ubuntu-latest, macos-latest]
16+
zig: ["0.15.2", "0.16.0"]
1317
steps:
1418
# Deps
1519
- uses: actions/checkout@v4
20+
1621
- uses: erlef/setup-beam@v1
1722
if: matrix.host == 'ubuntu-latest'
1823
with:
1924
otp-version: "27.3"
2025
elixir-version: "1.18.3"
26+
27+
- uses: erlef/setup-beam@v1
28+
if: matrix.host == 'macos-latest'
29+
with:
30+
otp-version: "27.3"
31+
elixir-version: "1.18.3"
32+
2133
- uses: goto-bus-stop/setup-zig@v2
2234
with:
23-
version: 0.15.2
35+
version: ${{ matrix.zig }}
36+
2437
- name: Set up Homebrew
2538
if: matrix.host == 'macos-latest'
2639
id: set-up-homebrew
2740
uses: Homebrew/actions/setup-homebrew@master
41+
2842
- run: sudo apt-get -y install xz-utils
2943
if: matrix.host == 'ubuntu-latest'
44+
3045
- run: brew install elixir xz
3146
if: matrix.host == 'macos-latest'
3247

@@ -36,13 +51,13 @@ jobs:
3651
id: cache-restore-linux
3752
with:
3853
path: /home/runner/.cache/burrito_file_cache/
39-
key: burrito-download-cache_${{ matrix.host }}
54+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
4055
- uses: actions/cache/restore@v3
4156
if: matrix.host == 'macos-latest'
4257
id: cache-restore-macos
4358
with:
4459
path: /Users/runner/Library/Caches/burrito_file_cache/
45-
key: burrito-download-cache_${{ matrix.host }}
60+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
4661

4762
# Build Example CLI App
4863
- name: cli_example
@@ -55,62 +70,69 @@ jobs:
5570
id: cache-save-linux
5671
with:
5772
path: /home/runner/.cache/burrito_file_cache/
58-
key: burrito-download-cache_${{ matrix.host }}
73+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
5974

6075
# Save Cache (macOS)
6176
- uses: actions/cache/save@v3
6277
if: matrix.host == 'macos-latest'
6378
id: cache-save-macos
6479
with:
6580
path: /Users/runner/Library/Caches/burrito_file_cache/
66-
key: burrito-download-cache_${{ matrix.host }}
81+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
6782

6883
# Upload Wrapped Binaries
6984
- name: Upload Binaries
7085
uses: actions/upload-artifact@v4
7186
with:
72-
name: burrito_${{ matrix.host }}_host_bins
87+
name: burrito_${{ matrix.host }}_${{ matrix.zig }}_host_bins
7388
path: ./examples/**/burrito_out/*
7489
retention-days: 1
7590

7691
#### Run example binaries ####
92+
# Windows binaries: built on Linux/macOS hosts, run on windows-latest
7793
run_examples_windows:
94+
needs: build_examples
7895
strategy:
96+
fail-fast: false
7997
matrix:
8098
host: [ubuntu-latest, macos-latest]
99+
zig: ["0.15.2", "0.16.0"]
81100
runs-on: windows-latest
82-
needs: build_examples
83101
steps:
84102
- name: Download a single artifact
85103
uses: actions/download-artifact@v4
86104
with:
87-
name: burrito_${{ matrix.host }}_host_bins
105+
name: burrito_${{ matrix.host }}_${{ matrix.zig }}_host_bins
88106
- run: cli_example/burrito_out/example_cli_app_windows.exe
89107

90108
run_examples_linux:
109+
needs: build_examples
91110
strategy:
111+
fail-fast: false
92112
matrix:
93113
host: [ubuntu-latest, macos-latest]
114+
zig: ["0.15.2", "0.16.0"]
94115
runs-on: ubuntu-latest
95-
needs: build_examples
96116
steps:
97117
- name: Download a single artifact
98118
uses: actions/download-artifact@v4
99119
with:
100-
name: burrito_${{ matrix.host }}_host_bins
120+
name: burrito_${{ matrix.host }}_${{ matrix.zig }}_host_bins
101121
- run: chmod +x cli_example/burrito_out/example_cli_app_linux
102122
- run: cli_example/burrito_out/example_cli_app_linux
103123

104124
run_examples_macos:
125+
needs: build_examples
105126
strategy:
127+
fail-fast: false
106128
matrix:
107129
host: [ubuntu-latest, macos-latest]
130+
zig: ["0.15.2", "0.16.0"]
108131
runs-on: macos-latest
109-
needs: build_examples
110132
steps:
111133
- name: Download a single artifact
112134
uses: actions/download-artifact@v4
113135
with:
114-
name: burrito_${{ matrix.host }}_host_bins
136+
name: burrito_${{ matrix.host }}_${{ matrix.zig }}_host_bins
115137
- run: chmod +x cli_example/burrito_out/example_cli_app_macos
116138
- run: cli_example/burrito_out/example_cli_app_macos

src/archiver.zig

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,16 @@ pub fn pack_directory(arena: Allocator, path: []const u8, archive_path: []const
9494
if (stat.size > 0) {
9595
assert(stat.size == try reader.streamRemaining(writer));
9696
}
97-
try writer.writeInt(usize, @intCast(stat.permissions.toMode()), .little);
97+
// On Windows, std.fs.File.Permissions is an enum (no
98+
// .toMode() method), and POSIX mode bits don't apply.
99+
// Write 0 — the archive is read on the same machine that
100+
// wrote it, so the receiver can derive permissions from
101+
// its own filesystem.
102+
const mode: usize = if (builtin.os.tag == .windows)
103+
0
104+
else
105+
@intCast(stat.permissions.toMode());
106+
try writer.writeInt(usize, mode, .little);
98107

99108
count += 1;
100109

src/erlang_launcher.zig

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ pub fn launch(io: Io, install_dir: []const u8, env_map: *std.process.Environ.Map
4646
release_cookie_content = cookie;
4747
}
4848

49-
// Set all the required release arguments
49+
// Set all the required release arguments. CLI args are passed
50+
// through native argv (after `-extra` below) and reach the BEAM
51+
// via :init.get_plain_arguments/0.
52+
5053
const erlang_cli = &[_][]const u8{
5154
erl_bin_path[0..],
5255
"-elixir ansi_enabled true",
@@ -67,54 +70,39 @@ pub fn launch(io: Io, install_dir: []const u8, env_map: *std.process.Environ.Map
6770
"-extra",
6871
};
6972

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 });
73+
// Cross-platform: build args once, set env, spawn child, wait for exit
74+
const final_args = try std.mem.concat(allocator, []const u8, &.{ erlang_cli, args_trimmed });
75+
76+
log.debug("CLI List: {any}", .{final_args});
9577

96-
log.debug("CLI List: {any}", .{final_args});
78+
try env_map.put("RELEASE_ROOT", install_dir);
79+
try env_map.put("RELEASE_SYS_CONFIG", config_sys_path_no_ext);
80+
try env_map.put("__BURRITO", "1");
81+
try env_map.put("__BURRITO_BIN_PATH", self_path);
9782

83+
// Unix: set ROOTDIR, BINDIR, LD_LIBRARY_PATH for NIF .so files
84+
if (builtin.os.tag != .windows) {
9885
try env_map.put("ROOTDIR", install_dir[0..]);
9986
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);
10487

10588
// 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
89+
// libraries (e.g. libgcc_s.so.1) when using a custom ERTS
10790
const system_lib_paths = "/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/lib:/usr/lib";
10891
if (env_map.get("LD_LIBRARY_PATH")) |existing| {
10992
const combined = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ existing, system_lib_paths });
11093
try env_map.put("LD_LIBRARY_PATH", combined);
11194
} else {
11295
try env_map.put("LD_LIBRARY_PATH", system_lib_paths);
11396
}
97+
}
11498

115-
return std.process.replace(io, .{
116-
.argv = final_args,
117-
.environ_map = env_map,
118-
});
99+
var child = try std.process.spawn(io, .{
100+
.argv = final_args,
101+
.environ_map = env_map,
102+
});
103+
const term = try child.wait(io);
104+
switch (term) {
105+
.exited => |code| std.process.exit(code),
106+
else => std.process.exit(1),
119107
}
120108
}

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)