From dc58f8a719dab3a87332a46fed3bc51539dda0d4 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 11 Dec 2025 16:41:11 +1100 Subject: [PATCH 1/2] ensures all symbols from the static library are included --- src/cli/linker.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cli/linker.zig b/src/cli/linker.zig index c0cdbf51b3..babf5adb99 100644 --- a/src/cli/linker.zig +++ b/src/cli/linker.zig @@ -324,11 +324,23 @@ pub fn link(allocs: *Allocators, config: LinkConfig) LinkError!void { }, } + // For WASM targets, wrap platform files in --whole-archive to include all symbols + // This ensures host exports (init, handleEvent, update) aren't stripped even when + // not referenced by other code + const is_wasm = config.target_format == .wasm; + if (is_wasm and config.platform_files_pre.len > 0) { + try args.append("--whole-archive"); + } + // Add platform-provided files that come before object files for (config.platform_files_pre) |platform_file| { try args.append(platform_file); } + if (is_wasm and config.platform_files_pre.len > 0) { + try args.append("--no-whole-archive"); + } + // Add object files for (config.object_files) |obj_file| { try args.append(obj_file); From 24957cf7af28a8bac3730ff7bfd476201df83078 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 11 Dec 2025 16:52:12 +1100 Subject: [PATCH 2/2] make bundle more lenient, and improve errors --- src/cli/main.zig | 41 +++++++++++++++++---------------- src/cli/platform_validation.zig | 34 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/cli/main.zig b/src/cli/main.zig index e3cf176a24..ee0e3ea3b4 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -3271,11 +3271,23 @@ pub fn rocBundle(allocs: *Allocators, args: cli_args.BundleArgs) !void { } } - // Validate platform header if the first file looks like a platform - // This ensures bundles have proper targets sections - const main_file = file_paths.items[0]; - if (std.mem.endsWith(u8, main_file, ".roc")) { - if (platform_validation.validatePlatformHeader(allocs.arena, main_file)) |validation| { + // Find the platform file among the .roc files (if any) + // We need to check each file without side effects first, then validate the actual platform + var platform_file: ?[]const u8 = null; + for (file_paths.items) |path| { + if (std.mem.endsWith(u8, path, ".roc")) { + if (platform_validation.isPlatformFile(allocs.arena, path)) |is_platform| { + if (is_platform) { + platform_file = path; + break; + } + } + } + } + + // If we found a platform file, validate it has proper targets section + if (platform_file) |pf| { + if (platform_validation.validatePlatformHeader(allocs.arena, pf)) |validation| { // Platform validation succeeded - validate all target files exist if (platform_validation.validateAllTargetFilesExist( allocs.arena, @@ -3290,21 +3302,10 @@ pub fn rocBundle(allocs: *Allocators, args: cli_args.BundleArgs) !void { else => error.MissingTargetFile, }; } - std.log.debug("Platform validation passed for: {s}", .{main_file}); - } else |err| { - switch (err) { - error.MissingTargetsSection => { - // Only warn - file might be an app, not a platform - std.log.debug("File {s} has no targets section (may be an app)", .{main_file}); - }, - error.ParseError, error.FileReadError => { - // Parsing failed - could be invalid syntax or not a Roc file - std.log.debug("Could not parse {s} as platform: {}", .{ main_file, err }); - }, - else => { - std.log.warn("Platform validation warning: {}", .{err}); - }, - } + } else |_| { + // validatePlatformHeader already rendered the error message via the reporting system. + // We continue bundling for now (non-blocking warning), but the user has seen the error. + // This allows bundling apps or platforms that don't yet have targets sections. } } diff --git a/src/cli/platform_validation.zig b/src/cli/platform_validation.zig index f7d95884f9..ef01b4823e 100644 --- a/src/cli/platform_validation.zig +++ b/src/cli/platform_validation.zig @@ -64,6 +64,40 @@ pub const PlatformValidation = struct { platform_dir: []const u8, }; +/// Check if a file is a platform header (has `platform` at the start). +/// This is a quick check without side effects - useful for finding which file +/// in a set of .roc files is the actual platform file. +/// Returns true if the file is a platform, false if it's a module/app, or null on error. +pub fn isPlatformFile( + allocator: std.mem.Allocator, + source_path: []const u8, +) ?bool { + // Read source file + const source = std.fs.cwd().readFileAlloc(allocator, source_path, std.math.maxInt(usize)) catch { + return null; + }; + defer allocator.free(source); + + // Initialize parse environment + var env = base.CommonEnv.init(allocator, source) catch { + return null; + }; + + // Parse the file + const ast = parse.parse(&env, allocator) catch { + return null; + }; + + // Check the header type + const file = ast.store.getFile(); + const header = ast.store.getHeader(file.header); + + return switch (header) { + .platform => true, + else => false, + }; +} + /// Parse and validate a platform header. /// Returns the TargetsConfig if valid, or an error with details. pub fn validatePlatformHeader(