mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Merge pull request #8600 from roc-lang/fix-roc-check-for-urls
Build platform and dependencies before app
This commit is contained in:
commit
4ee22547d3
2 changed files with 183 additions and 12 deletions
|
|
@ -376,6 +376,15 @@ pub const BuildEnv = struct {
|
|||
pkg_sink_ctxs: std.array_list.Managed(*PkgSinkCtx),
|
||||
// Owned schedule ctxs for pre-registration (one per package)
|
||||
schedule_ctxs: std.array_list.Managed(*ScheduleCtx),
|
||||
// Pending known module registrations (processed after schedulers are created)
|
||||
pending_known_modules: std.array_list.Managed(PendingKnownModule),
|
||||
|
||||
/// Info about a known module registration that needs to be applied after schedulers exist
|
||||
const PendingKnownModule = struct {
|
||||
target_package: []const u8, // Package to register with (e.g., "app")
|
||||
qualified_name: []const u8, // e.g., "pf.Stdout"
|
||||
import_name: []const u8, // e.g., "pf.Stdout"
|
||||
};
|
||||
|
||||
pub fn init(gpa: Allocator, mode: Mode, max_threads: usize) !BuildEnv {
|
||||
// Allocate builtin modules on heap to prevent moves that would invalidate internal pointers
|
||||
|
|
@ -396,6 +405,7 @@ pub const BuildEnv = struct {
|
|||
.resolver_ctxs = std.array_list.Managed(*ResolverCtx).init(gpa),
|
||||
.pkg_sink_ctxs = std.array_list.Managed(*PkgSinkCtx).init(gpa),
|
||||
.schedule_ctxs = std.array_list.Managed(*ScheduleCtx).init(gpa),
|
||||
.pending_known_modules = std.array_list.Managed(PendingKnownModule).init(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -425,6 +435,14 @@ pub const BuildEnv = struct {
|
|||
for (self.schedule_ctxs.items) |p| self.gpa.destroy(p);
|
||||
self.schedule_ctxs.deinit();
|
||||
|
||||
// Free pending known modules
|
||||
for (self.pending_known_modules.items) |pkm| {
|
||||
self.gpa.free(pkm.target_package);
|
||||
self.gpa.free(pkm.qualified_name);
|
||||
self.gpa.free(pkm.import_name);
|
||||
}
|
||||
self.pending_known_modules.deinit();
|
||||
|
||||
// Deinit schedulers
|
||||
var sit = self.schedulers.iterator();
|
||||
while (sit.next()) |e| {
|
||||
|
|
@ -525,6 +543,9 @@ pub const BuildEnv = struct {
|
|||
// Create per-package schedulers wired with a shared resolver and global queue hook
|
||||
try self.createSchedulers();
|
||||
|
||||
// Register pending known modules now that schedulers exist
|
||||
try self.processPendingKnownModules();
|
||||
|
||||
// Set back-pointer for dispatch
|
||||
self.global_queue.build_env = self;
|
||||
|
||||
|
|
@ -533,11 +554,8 @@ pub const BuildEnv = struct {
|
|||
try self.global_queue.start(self.gpa, self.max_threads, &self.sink);
|
||||
}
|
||||
|
||||
// Seed root module into global queue via schedule hook (ModuleBuild will call back)
|
||||
const root_sched = self.schedulers.getPtr(pkg_name).?;
|
||||
try root_sched.*.buildRoot(pkg_root_file);
|
||||
|
||||
// Kick remaining packages by seeding their root files too
|
||||
// Build platform and other dependency packages BEFORE the app
|
||||
// This ensures platform module envs are available when app is canonicalized
|
||||
var it = self.schedulers.iterator();
|
||||
while (it.next()) |e| {
|
||||
const name = e.key_ptr.*;
|
||||
|
|
@ -546,6 +564,10 @@ pub const BuildEnv = struct {
|
|||
try e.value_ptr.*.buildRoot(pkg.root_file);
|
||||
}
|
||||
|
||||
// Seed root module into global queue via schedule hook (ModuleBuild will call back)
|
||||
const root_sched = self.schedulers.getPtr(pkg_name).?;
|
||||
try root_sched.*.buildRoot(pkg_root_file);
|
||||
|
||||
// Wait for all work to complete
|
||||
if (builtin.target.cpu.arch != .wasm32 and self.mode == .multi_threaded) {
|
||||
// Multi-threaded mode: wait for global queue to drain
|
||||
|
|
@ -753,16 +775,22 @@ pub const BuildEnv = struct {
|
|||
const qual = parts.qual;
|
||||
const rest = parts.rest;
|
||||
|
||||
const ref = cur_pkg.shorthands.get(qual) orelse return;
|
||||
const ref = cur_pkg.shorthands.get(qual) orelse {
|
||||
return;
|
||||
};
|
||||
const target_pkg_name = ref.name;
|
||||
const target_pkg = self.ws.packages.get(target_pkg_name) orelse return;
|
||||
const target_pkg = self.ws.packages.get(target_pkg_name) orelse {
|
||||
return;
|
||||
};
|
||||
|
||||
const mod_path = self.ws.dottedToPath(target_pkg.root_dir, rest) catch {
|
||||
return;
|
||||
};
|
||||
defer self.ws.gpa.free(mod_path);
|
||||
|
||||
const sched = self.ws.schedulers.get(target_pkg_name) orelse return;
|
||||
const sched = self.ws.schedulers.get(target_pkg_name) orelse {
|
||||
return;
|
||||
};
|
||||
sched.*.scheduleModule(rest, mod_path, 1) catch {
|
||||
// Continue anyway - dependency resolution will handle missing modules
|
||||
};
|
||||
|
|
@ -790,10 +818,15 @@ pub const BuildEnv = struct {
|
|||
const qual = parts.qual;
|
||||
const rest = parts.rest;
|
||||
|
||||
const ref = cur_pkg.shorthands.get(qual) orelse return null;
|
||||
const sched = self.ws.schedulers.get(ref.name) orelse return null;
|
||||
const ref = cur_pkg.shorthands.get(qual) orelse {
|
||||
return null;
|
||||
};
|
||||
const sched = self.ws.schedulers.get(ref.name) orelse {
|
||||
return null;
|
||||
};
|
||||
|
||||
return sched.*.getEnvIfDone(rest);
|
||||
const result = sched.*.getEnvIfDone(rest);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn resolverResolveLocalPath(ctx: ?*anyopaque, _: []const u8, root_dir: []const u8, import_name: []const u8) []const u8 {
|
||||
|
|
@ -852,6 +885,8 @@ pub const BuildEnv = struct {
|
|||
platform_alias: ?[]u8 = null,
|
||||
platform_path: ?[]u8 = null,
|
||||
shorthands: std.StringHashMapUnmanaged([]const u8) = .{},
|
||||
/// Platform-exposed modules (e.g., Stdout, Stderr) that apps can import
|
||||
exposes: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
|
||||
fn deinit(self: *HeaderInfo, gpa: Allocator) void {
|
||||
if (self.platform_alias) |a| freeSlice(gpa, a);
|
||||
|
|
@ -862,6 +897,10 @@ pub const BuildEnv = struct {
|
|||
freeConstSlice(gpa, e.value_ptr.*);
|
||||
}
|
||||
self.shorthands.deinit(gpa);
|
||||
for (self.exposes.items) |e| {
|
||||
freeConstSlice(gpa, e);
|
||||
}
|
||||
self.exposes.deinit(gpa);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1125,6 +1164,22 @@ pub const BuildEnv = struct {
|
|||
}
|
||||
try info.shorthands.put(self.gpa, try self.gpa.dupe(u8, k), v);
|
||||
}
|
||||
|
||||
// Extract platform-exposed modules (e.g., Stdout, Stderr)
|
||||
// These are modules that apps can import from the platform
|
||||
const exposes_coll = ast.store.getCollection(p.exposes);
|
||||
const exposes_items = ast.store.exposedItemSlice(.{ .span = exposes_coll.span });
|
||||
for (exposes_items) |item_idx| {
|
||||
const item = ast.store.getExposedItem(item_idx);
|
||||
const token_idx = switch (item) {
|
||||
.upper_ident => |ui| ui.ident,
|
||||
.upper_ident_star => |uis| uis.ident,
|
||||
.lower_ident => |li| li.ident,
|
||||
.malformed => continue, // Skip malformed items
|
||||
};
|
||||
const item_name = ast.resolve(token_idx);
|
||||
try info.exposes.append(self.gpa, try self.gpa.dupe(u8, item_name));
|
||||
}
|
||||
},
|
||||
.module => {
|
||||
info.kind = .module;
|
||||
|
|
@ -1431,6 +1486,22 @@ pub const BuildEnv = struct {
|
|||
}
|
||||
}
|
||||
|
||||
/// Register pending known modules with their target schedulers.
|
||||
/// Also schedules the external modules so they'll be built before the app.
|
||||
/// Called after createSchedulers() to ensure all schedulers exist.
|
||||
fn processPendingKnownModules(self: *BuildEnv) !void {
|
||||
for (self.pending_known_modules.items) |pkm| {
|
||||
if (self.schedulers.get(pkm.target_package)) |sched| {
|
||||
try sched.addKnownModule(pkm.qualified_name, pkm.import_name);
|
||||
// Also schedule the external module so it gets built
|
||||
// This is needed so the module is ready when we populate module_envs_map
|
||||
if (sched.resolver) |res| {
|
||||
res.scheduleExternal(res.ctx, pkm.target_package, pkm.import_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn populatePackageShorthands(self: *BuildEnv, pkg_name: []const u8, info: *HeaderInfo) !void {
|
||||
var pack = self.packages.getPtr(pkg_name).?;
|
||||
|
||||
|
|
@ -1470,6 +1541,46 @@ pub const BuildEnv = struct {
|
|||
});
|
||||
|
||||
try self.populatePackageShorthands(dep_name, &child_info);
|
||||
|
||||
// Register platform-exposed modules as packages so apps can import them
|
||||
// This is necessary for URL platforms where the platform directory is in a cache
|
||||
const platform_dir = std.fs.path.dirname(abs) orelse ".";
|
||||
|
||||
for (child_info.exposes.items) |module_name| {
|
||||
// Create path to the module file (e.g., Stdout.roc)
|
||||
const module_filename = try std.fmt.allocPrint(self.gpa, "{s}.roc", .{module_name});
|
||||
defer self.gpa.free(module_filename);
|
||||
|
||||
const module_path = try std.fs.path.join(self.gpa, &.{ platform_dir, module_filename });
|
||||
defer self.gpa.free(module_path);
|
||||
|
||||
// Register this module as a package
|
||||
// Only allocate if package doesn't exist (ensurePackage makes its own copy)
|
||||
if (!self.packages.contains(module_name)) {
|
||||
try self.ensurePackage(module_name, .module, module_path);
|
||||
}
|
||||
|
||||
// Also add to app's shorthands so imports resolve correctly
|
||||
const mod_key = try self.gpa.dupe(u8, module_name);
|
||||
if (pack.shorthands.fetchRemove(mod_key)) |old_entry| {
|
||||
freeConstSlice(self.gpa, old_entry.key);
|
||||
freeConstSlice(self.gpa, old_entry.value.name);
|
||||
freeConstSlice(self.gpa, old_entry.value.root_file);
|
||||
}
|
||||
try pack.shorthands.put(self.gpa, mod_key, .{
|
||||
.name = try self.gpa.dupe(u8, module_name),
|
||||
.root_file = try self.gpa.dupe(u8, module_path),
|
||||
});
|
||||
|
||||
// Add to pending list - will be registered after schedulers are created
|
||||
// Use the QUALIFIED name (e.g., "pf.Stdout") because that's how imports are tracked
|
||||
const qualified_name = try std.fmt.allocPrint(self.gpa, "{s}.{s}", .{ alias, module_name });
|
||||
try self.pending_known_modules.append(.{
|
||||
.target_package = try self.gpa.dupe(u8, pkg_name),
|
||||
.qualified_name = qualified_name,
|
||||
.import_name = try self.gpa.dupe(u8, qualified_name),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Common package dependencies
|
||||
|
|
|
|||
|
|
@ -191,6 +191,18 @@ pub const PackageEnv = struct {
|
|||
total_type_checking_ns: u64 = 0,
|
||||
total_check_diagnostics_ns: u64 = 0,
|
||||
|
||||
// Additional known modules (e.g., from platform exposes) to include in module_envs_map
|
||||
// These are modules that exist in external directories (like URL platform cache)
|
||||
additional_known_modules: std.ArrayList(KnownModule),
|
||||
|
||||
/// Info about a known module from a platform or other package
|
||||
pub const KnownModule = struct {
|
||||
/// Qualified module name (e.g., "pf.Stdout")
|
||||
qualified_name: []const u8,
|
||||
/// Import name for resolver lookup (e.g., "pf.Stdout")
|
||||
import_name: []const u8,
|
||||
};
|
||||
|
||||
pub fn init(gpa: Allocator, package_name: []const u8, root_dir: []const u8, mode: Mode, max_threads: usize, sink: ReportSink, schedule_hook: ScheduleHook, compiler_version: []const u8, builtin_modules: *const BuiltinModules, file_provider: ?FileProvider) PackageEnv {
|
||||
return .{
|
||||
.gpa = gpa,
|
||||
|
|
@ -206,6 +218,7 @@ pub const PackageEnv = struct {
|
|||
.injector = std.ArrayList(Task).empty,
|
||||
.modules = std.ArrayList(ModuleState).empty,
|
||||
.discovered = std.ArrayList(ModuleId).empty,
|
||||
.additional_known_modules = std.ArrayList(KnownModule).empty,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -237,9 +250,24 @@ pub const PackageEnv = struct {
|
|||
.injector = std.ArrayList(Task).empty,
|
||||
.modules = std.ArrayList(ModuleState).empty,
|
||||
.discovered = std.ArrayList(ModuleId).empty,
|
||||
.additional_known_modules = std.ArrayList(KnownModule).empty,
|
||||
};
|
||||
}
|
||||
|
||||
/// Add a module that should be recognized during canonicalization.
|
||||
/// This is used for platform-exposed modules in URL platforms where the
|
||||
/// modules exist in a cache directory, not the app's directory.
|
||||
/// `qualified_name` is the full name like "pf.Stdout"
|
||||
/// `import_name` is the import path for resolver lookup (e.g., "pf.Stdout")
|
||||
pub fn addKnownModule(self: *PackageEnv, qualified_name: []const u8, import_name: []const u8) !void {
|
||||
const qualified_copy = try self.gpa.dupe(u8, qualified_name);
|
||||
const import_copy = try self.gpa.dupe(u8, import_name);
|
||||
try self.additional_known_modules.append(self.gpa, .{
|
||||
.qualified_name = qualified_copy,
|
||||
.import_name = import_copy,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *PackageEnv) void {
|
||||
// NOTE: builtin_modules is not owned by PackageEnv, so we don't deinit it here
|
||||
|
||||
|
|
@ -259,6 +287,13 @@ pub const PackageEnv = struct {
|
|||
self.injector.deinit(self.gpa);
|
||||
self.discovered.deinit(self.gpa);
|
||||
self.emitted.deinit(self.gpa);
|
||||
|
||||
// Free additional known module names
|
||||
for (self.additional_known_modules.items) |km| {
|
||||
self.gpa.free(km.qualified_name);
|
||||
self.gpa.free(km.import_name);
|
||||
}
|
||||
self.additional_known_modules.deinit(self.gpa);
|
||||
}
|
||||
|
||||
/// Get the root module's env (first module added)
|
||||
|
|
@ -630,6 +665,7 @@ pub const PackageEnv = struct {
|
|||
// Use shared canonicalization function to ensure consistency with snapshot tool
|
||||
// Pass sibling module names from the same directory so MODULE NOT FOUND isn't
|
||||
// reported prematurely for modules that exist but haven't been loaded yet.
|
||||
// Also include additional known modules from platform exposes (for URL platforms).
|
||||
try canonicalizeModuleWithSiblings(
|
||||
self.gpa,
|
||||
env,
|
||||
|
|
@ -637,6 +673,9 @@ pub const PackageEnv = struct {
|
|||
self.builtin_modules.builtin_module.env,
|
||||
self.builtin_modules.builtin_indices,
|
||||
self.root_dir,
|
||||
self.package_name,
|
||||
self.resolver,
|
||||
self.additional_known_modules.items,
|
||||
);
|
||||
|
||||
const canon_end = if (@import("builtin").target.cpu.arch != .wasm32) std.time.nanoTimestamp() else 0;
|
||||
|
|
@ -892,7 +931,8 @@ pub const PackageEnv = struct {
|
|||
czer.deinit();
|
||||
}
|
||||
|
||||
/// Canonicalization function that also discovers sibling .roc files in the same directory.
|
||||
/// Canonicalization function that also discovers sibling .roc files in the same directory
|
||||
/// and includes additional known modules (e.g., from platform exposes).
|
||||
/// This prevents premature MODULE NOT FOUND errors for modules that exist but haven't been loaded yet.
|
||||
fn canonicalizeModuleWithSiblings(
|
||||
gpa: Allocator,
|
||||
|
|
@ -901,6 +941,9 @@ pub const PackageEnv = struct {
|
|||
builtin_module_env: *const ModuleEnv,
|
||||
builtin_indices: can.CIR.BuiltinIndices,
|
||||
root_dir: []const u8,
|
||||
package_name: []const u8,
|
||||
resolver: ?ImportResolver,
|
||||
additional_known_modules: []const KnownModule,
|
||||
) !void {
|
||||
// Create module_envs map for auto-importing builtin types
|
||||
var module_envs_map = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(gpa);
|
||||
|
|
@ -948,6 +991,23 @@ pub const PackageEnv = struct {
|
|||
}
|
||||
}
|
||||
|
||||
// Add additional known modules (e.g., from platform exposes for URL platforms)
|
||||
// Use the resolver to get the ACTUAL module env if available
|
||||
for (additional_known_modules) |km| {
|
||||
const module_ident = try env.insertIdent(base.Ident.for_text(km.qualified_name));
|
||||
const qualified_ident = try env.insertIdent(base.Ident.for_text(km.qualified_name));
|
||||
if (!module_envs_map.contains(module_ident)) {
|
||||
// Try to get the actual module env using the resolver
|
||||
const actual_env: *const ModuleEnv = if (resolver) |res| blk: {
|
||||
if (res.getEnv(res.ctx, package_name, km.import_name)) |mod_env| {
|
||||
break :blk mod_env;
|
||||
}
|
||||
break :blk builtin_module_env;
|
||||
} else builtin_module_env;
|
||||
try module_envs_map.put(module_ident, .{ .env = actual_env, .qualified_type_ident = qualified_ident });
|
||||
}
|
||||
}
|
||||
|
||||
var czer = try Can.init(env, parse_ast, &module_envs_map);
|
||||
try czer.canonicalizeFile();
|
||||
czer.deinit();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue