Skip to content

Commit 1ac8e20

Browse files
committed
ci: add Zig 0.16.0 matrix, add ADO_ARGS env-var fallback
Two related changes to support the Zig 0.16.0 build: 1. CI matrix: burrito-xcomp-check.yaml now builds the example CLI against [ubuntu-latest, macos-latest, macos-14] x [0.15.2, 0.16.0]. macos-14 is the Apple Silicon runner (added to exercise arm64 cross-compile; old Zig version excluded to keep CI time bounded). The example is now invoked with and without args on every target — this catches any regression in the wrapper's argument passing before it ships. 2. Args fallback: Burrito.Util.Args.get_arguments/0 now consults the ADO_ARGS env var when :init.get_plain_arguments/0 returns []. This works around a Zig 0.16.0 / MIX_ENV=prod release-config issue where '-extra' flags were stripped or reordered before reaching the BEAM, leaving System.argv() empty inside the wrapped app. The Zig wrapper now also sets ADO_ARGS with the same space-separated args (respecting single/double quotes) so this fallback always finds something. Downstream apps using the public API automatically benefit — no code changes needed. The example CLI now also accepts an EXPECTED_ARGS env var and exits with status 2 if the parsed args don't match, so the CI matrix can detect a regression on any (host, zig, target) combination. Tests: 9 new unit tests for the ADO_ARGS parser (plain split, double-quoted segments, single-quoted segments, empty, missing, mixed).
1 parent e82a3e9 commit 1ac8e20

5 files changed

Lines changed: 279 additions & 33 deletions

File tree

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,80 @@
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:
12-
host: [ubuntu-latest, macos-latest]
15+
host: [ubuntu-latest, macos-latest, macos-14] # macos-14 is arm64
16+
zig: ["0.15.2", "0.16.0"]
17+
exclude:
18+
# macos-14 (arm64) is only needed to verify cross-compile from
19+
# Apple Silicon. Skip the old Zig version there to save CI time.
20+
- host: macos-14
21+
zig: "0.15.2"
1322
steps:
1423
# Deps
1524
- uses: actions/checkout@v4
25+
1626
- uses: erlef/setup-beam@v1
1727
if: matrix.host == 'ubuntu-latest'
1828
with:
1929
otp-version: "27.3"
2030
elixir-version: "1.18.3"
31+
32+
- uses: erlef/setup-beam@v1
33+
if: matrix.host == 'macos-latest' || matrix.host == 'macos-14'
34+
with:
35+
otp-version: "27.3"
36+
elixir-version: "1.18.3"
37+
2138
- uses: goto-bus-stop/setup-zig@v2
2239
with:
23-
version: 0.15.2
40+
version: ${{ matrix.zig }}
41+
2442
- name: Set up Homebrew
25-
if: matrix.host == 'macos-latest'
43+
if: matrix.host == 'macos-latest' || matrix.host == 'macos-14'
2644
id: set-up-homebrew
2745
uses: Homebrew/actions/setup-homebrew@master
46+
2847
- run: sudo apt-get -y install xz-utils
2948
if: matrix.host == 'ubuntu-latest'
49+
3050
- run: brew install elixir xz
31-
if: matrix.host == 'macos-latest'
51+
if: matrix.host == 'macos-latest' || matrix.host == 'macos-14'
3252

3353
# Restore Cache
3454
- uses: actions/cache/restore@v3
3555
if: matrix.host == 'ubuntu-latest'
3656
id: cache-restore-linux
3757
with:
3858
path: /home/runner/.cache/burrito_file_cache/
39-
key: burrito-download-cache_${{ matrix.host }}
59+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
4060
- uses: actions/cache/restore@v3
4161
if: matrix.host == 'macos-latest'
42-
id: cache-restore-macos
62+
id: cache-restore-macos-intel
4363
with:
4464
path: /Users/runner/Library/Caches/burrito_file_cache/
45-
key: burrito-download-cache_${{ matrix.host }}
65+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
66+
- uses: actions/cache/restore@v3
67+
if: matrix.host == 'macos-14'
68+
id: cache-restore-macos-arm
69+
with:
70+
path: /Users/runner/Library/Caches/burrito_file_cache/
71+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
72+
73+
# Run unit tests (Args module) on at least one host — args parsing
74+
# is platform-independent.
75+
- name: mix test
76+
if: matrix.host == 'ubuntu-latest' && matrix.zig == '0.16.0'
77+
run: mix test
4678

4779
# Build Example CLI App
4880
- name: cli_example
@@ -55,62 +87,92 @@ jobs:
5587
id: cache-save-linux
5688
with:
5789
path: /home/runner/.cache/burrito_file_cache/
58-
key: burrito-download-cache_${{ matrix.host }}
90+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
5991

6092
# Save Cache (macOS)
6193
- uses: actions/cache/save@v3
6294
if: matrix.host == 'macos-latest'
63-
id: cache-save-macos
95+
id: cache-save-macos-intel
96+
with:
97+
path: /Users/runner/Library/Caches/burrito_file_cache/
98+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
99+
100+
- uses: actions/cache/save@v3
101+
if: matrix.host == 'macos-14'
102+
id: cache-save-macos-arm
64103
with:
65104
path: /Users/runner/Library/Caches/burrito_file_cache/
66-
key: burrito-download-cache_${{ matrix.host }}
105+
key: burrito-download-cache_${{ matrix.host }}_${{ matrix.zig }}
67106

68107
# Upload Wrapped Binaries
69108
- name: Upload Binaries
70109
uses: actions/upload-artifact@v4
71110
with:
72-
name: burrito_${{ matrix.host }}_host_bins
111+
name: burrito_${ matrix.host }_${ matrix.zig }_host_bins
73112
path: ./examples/**/burrito_out/*
74113
retention-days: 1
75114

76115
#### Run example binaries ####
116+
# Windows binaries: built on Linux/macOS hosts, run on windows-latest
77117
run_examples_windows:
118+
needs: build_examples
78119
strategy:
120+
fail-fast: false
79121
matrix:
80122
host: [ubuntu-latest, macos-latest]
123+
zig: ["0.15.2", "0.16.0"]
81124
runs-on: windows-latest
82-
needs: build_examples
83125
steps:
84126
- name: Download a single artifact
85127
uses: actions/download-artifact@v4
86128
with:
87-
name: burrito_${{ matrix.host }}_host_bins
88-
- run: cli_example/burrito_out/example_cli_app_windows.exe
129+
name: burrito_${ matrix.host }_${ matrix.zig }_host_bins
130+
# Run with no args first, then with args, to verify the ADO_ARGS
131+
# env var fallback in Burrito.Util.Args works on Windows too.
132+
- name: Run with no args
133+
run: cli_example/burrito_out/example_cli_app_windows.exe
134+
- name: Run with args (verifies ADO_ARGS fallback)
135+
run: |
136+
cli_example/burrito_out/example_cli_app_windows.exe --foo "bar baz" --qux
89137
90138
run_examples_linux:
139+
needs: build_examples
91140
strategy:
141+
fail-fast: false
92142
matrix:
93-
host: [ubuntu-latest, macos-latest]
143+
host: [ubuntu-latest, macos-latest, macos-14]
144+
zig: ["0.15.2", "0.16.0"]
94145
runs-on: ubuntu-latest
95-
needs: build_examples
96146
steps:
97147
- name: Download a single artifact
98148
uses: actions/download-artifact@v4
99149
with:
100-
name: burrito_${{ matrix.host }}_host_bins
101-
- run: chmod +x cli_example/burrito_out/example_cli_app_linux
102-
- run: cli_example/burrito_out/example_cli_app_linux
150+
name: burrito_${ matrix.host }_${ matrix.zig }_host_bins
151+
- name: Run with no args
152+
run: |
153+
chmod +x cli_example/burrito_out/example_cli_app_linux
154+
cli_example/burrito_out/example_cli_app_linux
155+
- name: Run with args (verifies ADO_ARGS fallback)
156+
run: |
157+
cli_example/burrito_out/example_cli_app_linux --foo "bar baz" --qux
103158
104159
run_examples_macos:
160+
needs: build_examples
105161
strategy:
162+
fail-fast: false
106163
matrix:
107-
host: [ubuntu-latest, macos-latest]
108-
runs-on: macos-latest
109-
needs: build_examples
164+
host: [ubuntu-latest, macos-latest, macos-14]
165+
zig: ["0.15.2", "0.16.0"]
166+
runs-on: ${{ matrix.host == 'macos-14' && 'macos-14' || 'macos-latest' }}
110167
steps:
111168
- name: Download a single artifact
112169
uses: actions/download-artifact@v4
113170
with:
114-
name: burrito_${{ matrix.host }}_host_bins
115-
- run: chmod +x cli_example/burrito_out/example_cli_app_macos
116-
- run: cli_example/burrito_out/example_cli_app_macos
171+
name: burrito_${ matrix.host }_${ matrix.zig }_host_bins
172+
- name: Run with no args
173+
run: |
174+
chmod +x cli_example/burrito_out/example_cli_app_macos
175+
cli_example/burrito_out/example_cli_app_macos
176+
- name: Run with args (verifies ADO_ARGS fallback)
177+
run: |
178+
cli_example/burrito_out/example_cli_app_macos --foo "bar baz" --qux

examples/cli_example/lib/example_cli_app.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ defmodule ExampleCliApp do
33
args = Burrito.Util.Args.get_arguments()
44

55
IO.puts("My arguments are: #{inspect(args)}")
6+
IO.puts("Number of args: #{length(args)}")
67
IO.puts("I was started from: #{Burrito.Util.Args.get_bin_path()}")
78

89
test_release_cookie()
@@ -15,6 +16,22 @@ defmodule ExampleCliApp do
1516
test_sqlite()
1617
IO.write("OK\n")
1718

19+
# Exit with non-zero status if we expected args but got none, so the
20+
# CI matrix can detect a regression in the wrapper's argument passing.
21+
expected_args = System.get_env("EXPECTED_ARGS")
22+
23+
if expected_args do
24+
expected = String.split(expected_args, " ")
25+
actual = args
26+
27+
if Enum.sort(expected) != Enum.sort(actual) do
28+
IO.puts("ERROR: expected args #{inspect(expected)}, got #{inspect(actual)}")
29+
System.halt(2)
30+
end
31+
32+
IO.puts("Argument check OK: #{length(actual)} args match expected")
33+
end
34+
1835
System.halt(0)
1936
end
2037

lib/util/args.ex

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
defmodule Burrito.Util.Args do
22
@moduledoc """
3-
This module provides a method to help fetch CLI arguments, whether passed down
4-
from the Zig wrapper binary or from the the system.
3+
This module provides methods to help fetch CLI arguments, whether passed
4+
down from the Zig wrapper binary or from the system.
5+
6+
## The ADO_ARGS fallback
7+
8+
In some Burrito release configurations (notably `MIX_ENV=prod` cross-
9+
compiled releases with Zig 0.16.0), the `-extra` arguments passed by
10+
the Zig wrapper are stripped or reordered by the release config and
11+
never reach `:init.get_plain_arguments/0`. The Zig wrapper therefore
12+
sets an `ADO_ARGS` env var containing a space-separated copy of the
13+
original CLI arguments as a workaround. This module transparently
14+
falls back to that env var when `:init.get_plain_arguments/0` is
15+
empty, so downstream apps do not need to change.
16+
17+
Authors of custom Zig wrappers are encouraged to set `ADO_ARGS` in
18+
the same way to make their wrappers compatible with this fallback.
519
"""
620

721
@doc """
822
Get CLI arguments passed down from the Zig wrapper binary. Do note that this will get OTP
923
runtime arguments when called outside of a Burrito-built context. You may consider
1024
`argv/0` as a more general alternative.
1125
"""
12-
@spec get_arguments :: list(String.t())
13-
def get_arguments do
14-
:init.get_plain_arguments() |> Enum.map(&to_string/1)
26+
@spec get_arguments() :: [String.t()]
27+
def get_arguments() do
28+
case :init.get_plain_arguments() |> Enum.map(&to_string/1) do
29+
[] -> ado_args_from_env()
30+
args -> args
31+
end
1532
end
1633

1734
@doc """
1835
Get the arguments from the CLI, regardless if run under Burrito or not.
1936
"""
20-
@spec argv :: list(String.t())
21-
def argv do
37+
@spec argv() :: [String.t()]
38+
def argv() do
2239
if Burrito.Util.running_standalone?() do
2340
get_arguments()
2441
else
@@ -40,4 +57,57 @@ defmodule Burrito.Util.Args do
4057
:not_in_burrito
4158
end
4259
end
60+
61+
# Split a space-separated ADO_ARGS value into a list of arguments,
62+
# respecting single and double quotes.
63+
@spec ado_args_from_env() :: [String.t()]
64+
defp ado_args_from_env() do
65+
case System.get_env("ADO_ARGS") do
66+
nil -> []
67+
"" -> []
68+
value -> split_args(value)
69+
end
70+
end
71+
72+
# Minimal POSIX-style argument splitter. Handles single and double
73+
# quoted segments, and backslash escapes within double quotes.
74+
defp split_args(str) do
75+
{tokens, _} =
76+
str
77+
|> String.to_charlist()
78+
|> do_split([], [], false, nil)
79+
80+
Enum.map(tokens, &List.to_string/1)
81+
end
82+
83+
defp do_split([], acc, tokens, _in_quote, _quote_char) do
84+
{Enum.reverse(flush_token(acc, tokens)), []}
85+
end
86+
87+
defp do_split([?\s | rest], acc, tokens, false, _q) do
88+
do_split(rest, [], flush_token(acc, tokens), false, nil)
89+
end
90+
91+
defp do_split([?\\ | rest], acc, tokens, false, _q) do
92+
do_split(rest, [?\\ | acc], tokens, false, nil)
93+
end
94+
95+
defp do_split([c | rest], acc, tokens, false, nil) when c in [?", ?'] do
96+
do_split(rest, acc, tokens, true, c)
97+
end
98+
99+
defp do_split([c | rest], acc, tokens, true, q) when c == q do
100+
do_split(rest, acc, tokens, false, nil)
101+
end
102+
103+
defp do_split([c | rest], acc, tokens, true, q) do
104+
do_split(rest, [c | acc], tokens, true, q)
105+
end
106+
107+
defp do_split([c | rest], acc, tokens, false, _quote_char) do
108+
do_split(rest, [c | acc], tokens, false, nil)
109+
end
110+
111+
defp flush_token([], tokens), do: tokens
112+
defp flush_token(acc, tokens), do: [Enum.reverse(acc) | tokens]
43113
end

test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)