@@ -1824,13 +1824,59 @@ const FixArchivePaddingStep = struct {
18241824 defer file .close ();
18251825
18261826 const stat = try file .stat ();
1827- const file_size = stat .size ;
1827+ var file_size = stat .size ;
18281828
18291829 // AR format requires archives to end on an even byte boundary.
18301830 // If file size is odd, append a newline padding byte.
1831+ // This fixes Zig bug https://codeberg.org/ziglang/zig/issues/30572
1832+ // where Zig's archiver doesn't add required padding after odd-sized members.
18311833 if (file_size % 2 == 1 ) {
18321834 try file .seekTo (file_size );
18331835 try file .writeAll ("\n " );
1836+ file_size += 1 ;
1837+ }
1838+
1839+ // Parse the archive to verify member offsets are valid.
1840+ // This catches cases where lld would fail with "truncated or malformed archive".
1841+ try file .seekTo (0 );
1842+ var header_buf : [8 ]u8 = undefined ;
1843+ _ = try file .read (& header_buf );
1844+ if (! std .mem .eql (u8 , & header_buf , "!<arch>\n " )) {
1845+ std .debug .print ("Warning: Invalid archive magic in {s}\n " , .{self .archive_path });
1846+ return ;
1847+ }
1848+
1849+ var offset : u64 = 8 ; // After magic
1850+ while (offset + 60 <= file_size ) {
1851+ try file .seekTo (offset + 48 ); // Seek to size field (offset 48 within 60-byte header)
1852+ var size_buf : [10 ]u8 = undefined ;
1853+ _ = try file .read (& size_buf );
1854+
1855+ // Parse size (ASCII decimal, space-padded)
1856+ var size : u64 = 0 ;
1857+ for (size_buf ) | c | {
1858+ if (c >= '0' and c <= '9' ) {
1859+ size = size * 10 + (c - '0' );
1860+ } else break ;
1861+ }
1862+
1863+ // Move to next member (header + content + padding if odd)
1864+ offset += 60 + size ;
1865+ if (size % 2 == 1 ) {
1866+ offset += 1 ; // Padding byte expected
1867+ }
1868+
1869+ // If we're exactly at EOF, archive is valid
1870+ if (offset == file_size ) break ;
1871+
1872+ // If next offset would be past EOF, we have a problem - add missing padding
1873+ if (offset > file_size ) {
1874+ const missing = offset - file_size ;
1875+ try file .seekTo (file_size );
1876+ const padding = "\n\n " ; // At most 1 byte needed, but be safe
1877+ try file .writeAll (padding [0.. @min (missing , 2 )]);
1878+ break ;
1879+ }
18341880 }
18351881 }
18361882};
@@ -2859,17 +2905,39 @@ pub fn build(b: *std.Build) void {
28592905 // Copy the fx test platform host library to the source directory
28602906 const copy_test_fx_host = b .addUpdateSourceFiles ();
28612907 const test_fx_host_filename = if (target .result .os .tag == .windows ) "host.lib" else "libhost.a" ;
2862- copy_test_fx_host . addCopyFileToSource ( test_platform_fx_host_lib . getEmittedBin (), b .pathJoin (&.{ "test/fx/platform" , test_fx_host_filename }) );
2863- b . getInstallStep (). dependOn ( & copy_test_fx_host . step );
2908+ const fx_host_main_path = b .pathJoin (&.{ "test/fx/platform" , test_fx_host_filename });
2909+ copy_test_fx_host . addCopyFileToSource ( test_platform_fx_host_lib . getEmittedBin (), fx_host_main_path );
28642910
28652911 // Also copy to the target-specific directory so findHostLibrary finds it
2866- if (fx_host_target_dir ) | target_dir | {
2912+ const fx_host_target_path = if (fx_host_target_dir ) | target_dir |
2913+ b .pathJoin (&.{ "test/fx/platform/targets" , target_dir , test_fx_host_filename })
2914+ else
2915+ null ;
2916+ if (fx_host_target_path ) | target_path | {
28672917 copy_test_fx_host .addCopyFileToSource (
28682918 test_platform_fx_host_lib .getEmittedBin (),
2869- b . pathJoin (&.{ "test/fx/platform/targets" , target_dir , test_fx_host_filename }) ,
2919+ target_path ,
28702920 );
28712921 }
28722922
2923+ // Apply archive padding fix for non-Windows targets (Zig bug workaround)
2924+ // The final_fx_host_step is what tests should depend on to ensure the archive is ready
2925+ const final_fx_host_step : * Step = if (target .result .os .tag != .windows ) blk : {
2926+ const fix_main = FixArchivePaddingStep .create (b , fx_host_main_path );
2927+ fix_main .step .dependOn (& copy_test_fx_host .step );
2928+
2929+ if (fx_host_target_path ) | target_path | {
2930+ const fix_target = FixArchivePaddingStep .create (b , target_path );
2931+ fix_target .step .dependOn (& copy_test_fx_host .step );
2932+ // Make fix_target depend on fix_main so both complete
2933+ fix_target .step .dependOn (& fix_main .step );
2934+ break :blk & fix_target .step ;
2935+ }
2936+ break :blk & fix_main .step ;
2937+ } else & copy_test_fx_host .step ;
2938+
2939+ b .getInstallStep ().dependOn (final_fx_host_step );
2940+
28732941 const fx_platform_test = b .addTest (.{
28742942 .name = "fx_platform_test" ,
28752943 .root_module = b .createModule (.{
@@ -2884,8 +2952,8 @@ pub fn build(b: *std.Build) void {
28842952 if (run_args .len != 0 ) {
28852953 run_fx_platform_test .addArgs (run_args );
28862954 }
2887- // Ensure host library is copied before running the test
2888- run_fx_platform_test .step .dependOn (& copy_test_fx_host . step );
2955+ // Ensure host library is copied AND fixed before running the test
2956+ run_fx_platform_test .step .dependOn (final_fx_host_step );
28892957 // Ensure roc binary is built before running the test (tests invoke roc CLI)
28902958 run_fx_platform_test .step .dependOn (roc_step );
28912959 tests_summary .addRun (& run_fx_platform_test .step );
0 commit comments