Merge pull request #8628 from roc-lang/improve-bundle

Add `--whole-archive` for WASM and improve `roc bundle`
This commit is contained in:
Luke Boswell 2025-12-11 20:14:58 +11:00 committed by GitHub
commit 85b6a0fb9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 20 deletions

View file

@ -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);

View file

@ -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.
}
}

View file

@ -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(