@@ -2009,13 +2009,59 @@ const FixArchivePaddingStep = struct {
20092009 defer file .close ();
20102010
20112011 const stat = try file .stat ();
2012- const file_size = stat .size ;
2012+ var file_size = stat .size ;
20132013
20142014 // AR format requires archives to end on an even byte boundary.
20152015 // If file size is odd, append a newline padding byte.
2016+ // This fixes Zig bug https://codeberg.org/ziglang/zig/issues/30572
2017+ // where Zig's archiver doesn't add required padding after odd-sized members.
20162018 if (file_size % 2 == 1 ) {
20172019 try file .seekTo (file_size );
20182020 try file .writeAll ("\n " );
2021+ file_size += 1 ;
2022+ }
2023+
2024+ // Parse the archive to verify member offsets are valid.
2025+ // This catches cases where lld would fail with "truncated or malformed archive".
2026+ try file .seekTo (0 );
2027+ var header_buf : [8 ]u8 = undefined ;
2028+ _ = try file .read (& header_buf );
2029+ if (! std .mem .eql (u8 , & header_buf , "!<arch>\n " )) {
2030+ std .debug .print ("Warning: Invalid archive magic in {s}\n " , .{self .archive_path });
2031+ return ;
2032+ }
2033+
2034+ var offset : u64 = 8 ; // After magic
2035+ while (offset + 60 <= file_size ) {
2036+ try file .seekTo (offset + 48 ); // Seek to size field (offset 48 within 60-byte header)
2037+ var size_buf : [10 ]u8 = undefined ;
2038+ _ = try file .read (& size_buf );
2039+
2040+ // Parse size (ASCII decimal, space-padded)
2041+ var size : u64 = 0 ;
2042+ for (size_buf ) | c | {
2043+ if (c >= '0' and c <= '9' ) {
2044+ size = size * 10 + (c - '0' );
2045+ } else break ;
2046+ }
2047+
2048+ // Move to next member (header + content + padding if odd)
2049+ offset += 60 + size ;
2050+ if (size % 2 == 1 ) {
2051+ offset += 1 ; // Padding byte expected
2052+ }
2053+
2054+ // If we're exactly at EOF, archive is valid
2055+ if (offset == file_size ) break ;
2056+
2057+ // If next offset would be past EOF, we have a problem - add missing padding
2058+ if (offset > file_size ) {
2059+ const missing = offset - file_size ;
2060+ try file .seekTo (file_size );
2061+ const padding = "\n\n " ; // At most 1 byte needed, but be safe
2062+ try file .writeAll (padding [0.. @min (missing , 2 )]);
2063+ break ;
2064+ }
20192065 }
20202066 }
20212067};
@@ -2415,6 +2461,7 @@ pub fn build(b: *std.Build) void {
24152461 roc_modules .repl .addImport ("compiled_builtins" , compiled_builtins_module );
24162462 roc_modules .compile .addImport ("compiled_builtins" , compiled_builtins_module );
24172463 roc_modules .eval .addImport ("compiled_builtins" , compiled_builtins_module );
2464+ roc_modules .lsp .addImport ("compiled_builtins" , compiled_builtins_module );
24182465
24192466 // Setup test platform host libraries
24202467 setupTestPlatforms (b , target , optimize , roc_modules , test_platforms_step , strip , omit_frame_pointer );
@@ -2727,8 +2774,8 @@ pub fn build(b: *std.Build) void {
27272774 const module_tests_result = roc_modules .createModuleTests (b , target , optimize , zstd , test_filters );
27282775 const tests_summary = TestsSummaryStep .create (b , test_filters , module_tests_result .forced_passes );
27292776 for (module_tests_result .tests ) | module_test | {
2730- // Add compiled builtins to check, repl, eval, and compile module tests
2731- if (std .mem .eql (u8 , module_test .test_step .name , "check" ) or std .mem .eql (u8 , module_test .test_step .name , "repl" ) or std .mem .eql (u8 , module_test .test_step .name , "eval" ) or std .mem .eql (u8 , module_test .test_step .name , "compile" )) {
2777+ // Add compiled builtins to check, repl, eval, compile, and lsp module tests
2778+ if (std .mem .eql (u8 , module_test .test_step .name , "check" ) or std .mem .eql (u8 , module_test .test_step .name , "repl" ) or std .mem .eql (u8 , module_test .test_step .name , "eval" ) or std .mem .eql (u8 , module_test .test_step .name , "compile" ) or std . mem . eql ( u8 , module_test . test_step . name , "lsp" ) ) {
27322779 module_test .test_step .root_module .addImport ("compiled_builtins" , compiled_builtins_module );
27332780 module_test .test_step .step .dependOn (& write_compiled_builtins .step );
27342781 }
@@ -3089,17 +3136,39 @@ pub fn build(b: *std.Build) void {
30893136 // Copy the fx test platform host library to the source directory
30903137 const copy_test_fx_host = b .addUpdateSourceFiles ();
30913138 const test_fx_host_filename = if (target .result .os .tag == .windows ) "host.lib" else "libhost.a" ;
3092- copy_test_fx_host . addCopyFileToSource ( test_platform_fx_host_lib . getEmittedBin (), b .pathJoin (&.{ "test/fx/platform" , test_fx_host_filename }) );
3093- b . getInstallStep (). dependOn ( & copy_test_fx_host . step );
3139+ const fx_host_main_path = b .pathJoin (&.{ "test/fx/platform" , test_fx_host_filename });
3140+ copy_test_fx_host . addCopyFileToSource ( test_platform_fx_host_lib . getEmittedBin (), fx_host_main_path );
30943141
30953142 // Also copy to the target-specific directory so findHostLibrary finds it
3096- if (fx_host_target_dir ) | target_dir | {
3143+ const fx_host_target_path = if (fx_host_target_dir ) | target_dir |
3144+ b .pathJoin (&.{ "test/fx/platform/targets" , target_dir , test_fx_host_filename })
3145+ else
3146+ null ;
3147+ if (fx_host_target_path ) | target_path | {
30973148 copy_test_fx_host .addCopyFileToSource (
30983149 test_platform_fx_host_lib .getEmittedBin (),
3099- b . pathJoin (&.{ "test/fx/platform/targets" , target_dir , test_fx_host_filename }) ,
3150+ target_path ,
31003151 );
31013152 }
31023153
3154+ // Apply archive padding fix for non-Windows targets (Zig bug workaround)
3155+ // The final_fx_host_step is what tests should depend on to ensure the archive is ready
3156+ const final_fx_host_step : * Step = if (target .result .os .tag != .windows ) blk : {
3157+ const fix_main = FixArchivePaddingStep .create (b , fx_host_main_path );
3158+ fix_main .step .dependOn (& copy_test_fx_host .step );
3159+
3160+ if (fx_host_target_path ) | target_path | {
3161+ const fix_target = FixArchivePaddingStep .create (b , target_path );
3162+ fix_target .step .dependOn (& copy_test_fx_host .step );
3163+ // Make fix_target depend on fix_main so both complete
3164+ fix_target .step .dependOn (& fix_main .step );
3165+ break :blk & fix_target .step ;
3166+ }
3167+ break :blk & fix_main .step ;
3168+ } else & copy_test_fx_host .step ;
3169+
3170+ b .getInstallStep ().dependOn (final_fx_host_step );
3171+
31033172 const fx_platform_test = b .addTest (.{
31043173 .name = "fx_platform_test" ,
31053174 .root_module = b .createModule (.{
@@ -3114,8 +3183,8 @@ pub fn build(b: *std.Build) void {
31143183 if (run_args .len != 0 ) {
31153184 run_fx_platform_test .addArgs (run_args );
31163185 }
3117- // Ensure host library is copied before running the test
3118- run_fx_platform_test .step .dependOn (& copy_test_fx_host . step );
3186+ // Ensure host library is copied AND fixed before running the test
3187+ run_fx_platform_test .step .dependOn (final_fx_host_step );
31193188 // Ensure roc binary is built before running the test (tests invoke roc CLI)
31203189 run_fx_platform_test .step .dependOn (roc_step );
31213190 tests_summary .addRun (& run_fx_platform_test .step );
0 commit comments