From f08904c4ed46cb40c7c5ffdcc9ff96f6c1f23d32 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 6 Dec 2025 09:16:29 -0500 Subject: [PATCH 1/4] Fix Nix build on CI --- build.zig | 2 + src/cli/test/fx_platform_test.zig | 86 +++++++++++++++---------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/build.zig b/build.zig index 68da021feb..58a90759c7 100644 --- a/build.zig +++ b/build.zig @@ -1650,6 +1650,8 @@ pub fn build(b: *std.Build) void { } // Ensure host library is copied before running the test run_fx_platform_test.step.dependOn(©_test_fx_host.step); + // Ensure roc binary is built before running the test (tests invoke roc CLI) + run_fx_platform_test.step.dependOn(roc_step); tests_summary.addRun(&run_fx_platform_test.step); } diff --git a/src/cli/test/fx_platform_test.zig b/src/cli/test/fx_platform_test.zig index efda3ae395..b759b86e42 100644 --- a/src/cli/test/fx_platform_test.zig +++ b/src/cli/test/fx_platform_test.zig @@ -9,23 +9,23 @@ const testing = std.testing; const roc_binary_path = if (builtin.os.tag == .windows) ".\\zig-out\\bin\\roc.exe" else "./zig-out/bin/roc"; -/// Ensures the roc binary is up-to-date by always rebuilding it. -/// This is needed because these tests spawn the roc CLI as a child process, -/// and a stale binary will cause test failures even if the test code is correct. -fn ensureRocBinary(allocator: std.mem.Allocator) !void { - // Always rebuild to ensure the binary is up-to-date with the latest source changes. - // This prevents confusing test failures when the binary exists but is stale. - const build_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &[_][]const u8{ "zig", "build", "roc" }, - }); - defer allocator.free(build_result.stdout); - defer allocator.free(build_result.stderr); - - if (build_result.term != .Exited or build_result.term.Exited != 0) { - std.debug.print("Failed to build roc binary:\n{s}\n", .{build_result.stderr}); - return error.RocBuildFailed; - } +/// Asserts that the roc binary exists. +/// The build system (build.zig) is responsible for building the roc binary +/// before running these tests. This avoids rebuilding 50+ times (once per test) +/// which causes disk space issues on CI. +fn ensureRocBinary() !void { + std.fs.cwd().access(roc_binary_path, .{}) catch { + std.debug.print( + \\ + \\ERROR: roc binary not found at {s} + \\ + \\The fx_platform_test requires the roc binary to be pre-built. + \\Run `zig build test` which will build it automatically, or + \\run `zig build roc` manually before running individual tests. + \\ + , .{roc_binary_path}); + return error.RocBinaryNotFound; + }; } /// Options for running roc commands @@ -39,7 +39,7 @@ const RunOptions = struct { /// Runs a roc command and returns the result. /// Automatically adds --no-cache for non-test/non-check commands to ensure fresh builds. fn runRoc(allocator: std.mem.Allocator, roc_file: []const u8, options: RunOptions) !std.process.Child.RunResult { - try ensureRocBinary(allocator); + try ensureRocBinary(); var args = std.ArrayList([]const u8){}; defer args.deinit(allocator); @@ -113,7 +113,7 @@ fn checkFailure(result: std.process.Child.RunResult) !void { } fn runRocWithStdin(allocator: std.mem.Allocator, roc_file: []const u8, stdin_input: []const u8) !std.process.Child.RunResult { - try ensureRocBinary(allocator); + try ensureRocBinary(); var child = std.process.Child.init(&[_][]const u8{ "./zig-out/bin/roc", roc_file }, allocator); child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; @@ -152,7 +152,7 @@ fn runRocWithStdin(allocator: std.mem.Allocator, roc_file: []const u8, stdin_inp test "fx platform effectful functions" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); // Run the app directly with the roc CLI (not build, just run) const run_result = try std.process.Child.run(.{ @@ -204,7 +204,7 @@ test "fx platform effectful functions" { test "fx platform with dotdot starting path" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); // Run the app from a subdirectory that uses ../ at the START of its platform path // This tests that relative paths starting with .. are handled correctly @@ -401,7 +401,7 @@ test "fx platform dbg missing return value" { test "fx platform check unused state var reports correct errors" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); // Run `roc check` on an app with unused variables and type annotations // This test checks that the compiler reports the correct errors and doesn't @@ -541,7 +541,7 @@ test "fx platform opaque type with method" { test "fx platform string interpolation type mismatch" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); // Run an app that tries to interpolate a U8 (non-Str) type in a string. // This should fail with a type error because string interpolation only accepts Str. @@ -587,7 +587,7 @@ test "fx platform run from different cwd" { // running from a subdirectory correctly. const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); // Get absolute path to roc binary since we'll change cwd const roc_abs_path = try std.fs.cwd().realpathAlloc(allocator, roc_binary_path); @@ -828,7 +828,7 @@ test "fx platform str_interp_valid" { test "fx platform expect with toplevel numeric" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); // Run the app const run_result = try std.process.Child.run(.{ @@ -894,7 +894,7 @@ test "fx platform expect with toplevel numeric" { // test "fx platform test7" { // const allocator = testing.allocator; -// try ensureRocBinary(allocator); +// try ensureRocBinary(); // const run_result = try std.process.Child.run(.{ // .allocator = allocator, @@ -930,7 +930,7 @@ test "fx platform expect with toplevel numeric" { // test "fx platform test8" { // const allocator = testing.allocator; -// try ensureRocBinary(allocator); +// try ensureRocBinary(); // const run_result = try std.process.Child.run(.{ // .allocator = allocator, @@ -966,7 +966,7 @@ test "fx platform expect with toplevel numeric" { // test "fx platform test9" { // const allocator = testing.allocator; -// try ensureRocBinary(allocator); +// try ensureRocBinary(); // const run_result = try std.process.Child.run(.{ // .allocator = allocator, @@ -1001,7 +1001,7 @@ test "fx platform expect with toplevel numeric" { test "fx platform numeric_lookup_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1036,7 +1036,7 @@ test "fx platform numeric_lookup_test" { test "fx platform string_lookup_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1071,7 +1071,7 @@ test "fx platform string_lookup_test" { test "fx platform test_direct_string" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1106,7 +1106,7 @@ test "fx platform test_direct_string" { test "fx platform test_one_call" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1141,7 +1141,7 @@ test "fx platform test_one_call" { test "fx platform test_type_mismatch" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1177,7 +1177,7 @@ test "fx platform test_type_mismatch" { test "fx platform test_with_wrapper" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1212,7 +1212,7 @@ test "fx platform test_with_wrapper" { test "fx platform inspect_compare_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1249,7 +1249,7 @@ test "fx platform inspect_compare_test" { test "fx platform inspect_custom_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1285,7 +1285,7 @@ test "fx platform inspect_custom_test" { test "fx platform inspect_nested_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1321,7 +1321,7 @@ test "fx platform inspect_nested_test" { test "fx platform inspect_no_method_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1357,7 +1357,7 @@ test "fx platform inspect_no_method_test" { test "fx platform inspect_record_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1392,7 +1392,7 @@ test "fx platform inspect_record_test" { test "fx platform inspect_wrong_sig_test" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1427,7 +1427,7 @@ test "fx platform inspect_wrong_sig_test" { test "fx platform issue8433" { const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1463,7 +1463,7 @@ test "run aborts on errors by default" { // Tests that roc run aborts when there are type errors (without --allow-errors) const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, @@ -1486,7 +1486,7 @@ test "run with --allow-errors attempts execution despite errors" { // Tests that roc run --allow-errors attempts to execute even with type errors const allocator = testing.allocator; - try ensureRocBinary(allocator); + try ensureRocBinary(); const run_result = try std.process.Child.run(.{ .allocator = allocator, From c116e986ef8b9c645e2c822bd2097c80e68bd5fd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 7 Dec 2025 15:25:32 -0500 Subject: [PATCH 2/4] Fix valgrind error --- .github/workflows/ci_zig.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci_zig.yml b/.github/workflows/ci_zig.yml index 5e21453e43..577d94bc71 100644 --- a/.github/workflows/ci_zig.yml +++ b/.github/workflows/ci_zig.yml @@ -208,6 +208,7 @@ jobs: # We can re-evaluate as new version of zig/valgrind come out. if: ${{ matrix.os == 'ubuntu-22.04' }} run: | + sudo apt-get update && sudo apt-get install -y libc6-dbg sudo snap install valgrind --classic valgrind --version ./ci/custom_valgrind.sh ./zig-out/bin/snapshot --debug --verbose From 24dffc2c3fca3552427f70afa6eab15b5501aa4f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 7 Dec 2025 16:38:52 -0500 Subject: [PATCH 3/4] Fix static-ness of build --- build.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index 58a90759c7..7b66bdecef 100644 --- a/build.zig +++ b/build.zig @@ -2206,9 +2206,8 @@ fn addStaticLlvmOptionsToModule(mod: *std.Build.Module) !void { mod.linkSystemLibrary("z", link_static); if (mod.resolved_target.?.result.os.tag != .windows or mod.resolved_target.?.result.abi != .msvc) { - // TODO: Can this just be `mod.link_libcpp = true`? Does that make a difference? - // This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries. - mod.linkSystemLibrary("c++", .{}); + // Use Zig's bundled static libc++ to keep the binary statically linked + mod.link_libcpp = true; } if (mod.resolved_target.?.result.os.tag == .windows) { From ed95a91257354a7b0eb8b7d0cd90d3219805a700 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 7 Dec 2025 17:29:47 -0500 Subject: [PATCH 4/4] musl only --- .github/workflows/ci_zig.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_zig.yml b/.github/workflows/ci_zig.yml index 577d94bc71..37775075d3 100644 --- a/.github/workflows/ci_zig.yml +++ b/.github/workflows/ci_zig.yml @@ -85,18 +85,25 @@ jobs: include: - os: macos-15-intel cpu_flag: -Dcpu=x86_64_v3 + target_flag: '' - os: macos-15 cpu_flag: '' + target_flag: '' - os: ubuntu-22.04 cpu_flag: -Dcpu=x86_64_v3 + target_flag: -Dtarget=x86_64-linux-musl - os: ubuntu-24.04-arm cpu_flag: '' + target_flag: -Dtarget=aarch64-linux-musl - os: windows-2022 cpu_flag: -Dcpu=x86_64_v3 + target_flag: '' - os: windows-2025 cpu_flag: -Dcpu=x86_64_v3 + target_flag: '' - os: windows-11-arm cpu_flag: '' + target_flag: '' steps: - name: Checkout @@ -119,7 +126,7 @@ jobs: - name: build roc + repro executables uses: ./.github/actions/flaky-retry with: - command: "zig build -Dfuzz -Dsystem-afl=false -Doptimize=ReleaseFast ${{ matrix.cpu_flag }}" + command: "zig build -Dfuzz -Dsystem-afl=false -Doptimize=ReleaseFast ${{ matrix.cpu_flag }} ${{ matrix.target_flag }}" error_string_contains: "EndOfStream" retry_count: 3 @@ -136,7 +143,7 @@ jobs: - name: Run Test Platforms (Unix) if: runner.os != 'Windows' run: | - zig build test-cli + zig build test-cli ${{ matrix.target_flag }} - name: Setup MSVC (Windows) if: runner.os == 'Windows' @@ -165,13 +172,13 @@ jobs: zig-out\bin\roc.exe check ./src/PROFILING/bench_repeated_check.roc - name: zig snapshot tests - run: zig build snapshot -- --debug + run: zig build snapshot ${{ matrix.target_flag }} -- --debug # 1) in debug mode - name: build and execute tests, build repro executables uses: ./.github/actions/flaky-retry with: - command: "zig build test -Dfuzz -Dsystem-afl=false" + command: "zig build test -Dfuzz -Dsystem-afl=false ${{ matrix.target_flag }}" error_string_contains: "double roundtrip bundle" retry_count: 3 @@ -179,7 +186,7 @@ jobs: - name: Build and execute tests, build repro executables. All in release mode. uses: ./.github/actions/flaky-retry with: - command: "zig build test -Doptimize=ReleaseFast -Dfuzz -Dsystem-afl=false ${{ matrix.cpu_flag }}" + command: "zig build test -Doptimize=ReleaseFast -Dfuzz -Dsystem-afl=false ${{ matrix.cpu_flag }} ${{ matrix.target_flag }}" error_string_contains: "double roundtrip bundle" retry_count: 3 @@ -249,7 +256,7 @@ jobs: run: | git clean -fdx git reset --hard HEAD - nix develop ./src/ -c zig build && zig build snapshot && zig build test + nix develop ./src/ -c zig build ${{ matrix.target_flag }} && zig build snapshot ${{ matrix.target_flag }} && zig build test ${{ matrix.target_flag }} zig-cross-compile: needs: check-once