diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 632982e812..91443a093a 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -337,6 +337,7 @@ pub fn main() !void { var prog_node = main_progress_node.start("Configure", 0); defer prog_node.end(); try builder.runBuild(root); + createModuleDependencies(builder) catch @panic("OOM"); } if (graph.needed_lazy_dependencies.entries.len != 0) { @@ -1440,3 +1441,80 @@ fn validateSystemLibraryOptions(b: *std.Build) void { process.exit(1); } } + +/// Starting from all top-level steps in `b`, traverses the entire step graph +/// and adds all step dependencies implied by module graphs. +fn createModuleDependencies(b: *std.Build) Allocator.Error!void { + const arena = b.graph.arena; + + var all_steps: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty; + var next_step_idx: usize = 0; + + try all_steps.ensureUnusedCapacity(arena, b.top_level_steps.count()); + for (b.top_level_steps.values()) |tls| { + all_steps.putAssumeCapacityNoClobber(&tls.step, {}); + } + + while (next_step_idx < all_steps.count()) { + const step = all_steps.keys()[next_step_idx]; + next_step_idx += 1; + + // Set up any implied dependencies for this step. It's important that we do this first, so + // that the loop below discovers steps implied by the module graph. + try createModuleDependenciesForStep(step); + + try all_steps.ensureUnusedCapacity(arena, step.dependencies.items.len); + for (step.dependencies.items) |other_step| { + all_steps.putAssumeCapacity(other_step, {}); + } + } +} + +/// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which +/// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`. +fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { + const root_module = if (step.cast(Step.Compile)) |cs| root: { + break :root cs.root_module; + } else return; // not a compile step so no module dependencies + + // Starting from `root_module`, discover all modules in this graph. + const modules = root_module.getGraph().modules; + + // For each of those modules, set up the implied step dependencies. + for (modules) |mod| { + if (mod.root_source_file) |lp| lp.addStepDependencies(step); + for (mod.include_dirs.items) |include_dir| switch (include_dir) { + .path, + .path_system, + .path_after, + .framework_path, + .framework_path_system, + => |lp| lp.addStepDependencies(step), + + .other_step => |other| { + other.getEmittedIncludeTree().addStepDependencies(step); + step.dependOn(&other.step); + }, + + .config_header_step => |other| step.dependOn(&other.step), + }; + for (mod.lib_paths.items) |lp| lp.addStepDependencies(step); + for (mod.rpaths.items) |rpath| switch (rpath) { + .lazy_path => |lp| lp.addStepDependencies(step), + .special => {}, + }; + for (mod.link_objects.items) |link_object| switch (link_object) { + .static_path, + .assembly_file, + => |lp| lp.addStepDependencies(step), + .other_step => |other| step.dependOn(&other.step), + .system_lib => {}, + .c_source_file => |source| source.file.addStepDependencies(step), + .c_source_files => |source_files| source_files.root.addStepDependencies(step), + .win32_resource_file => |rc_source| { + rc_source.file.addStepDependencies(step); + for (rc_source.include_paths) |lp| lp.addStepDependencies(step); + }, + }; + } +} diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 629d90dd0f..1f2b4f3fcb 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -1,9 +1,5 @@ /// The one responsible for creating this module. owner: *std.Build, -/// Tracks the set of steps that depend on this `Module`. This ensures that -/// when making this `Module` depend on other `Module` objects and `Step` -/// objects, respective `Step` dependencies can be added. -depending_steps: std.AutoArrayHashMapUnmanaged(*Step.Compile, void), root_source_file: ?LazyPath, /// The modules that are mapped into this module's import table. /// Use `addImport` rather than modifying this field directly in order to @@ -41,6 +37,10 @@ link_libcpp: ?bool, /// Symbols to be exported when compiling to WebAssembly. export_symbol_names: []const []const u8 = &.{}, +/// Caches the result of `getGraph` when called multiple times. +/// Use `getGraph` instead of accessing this field directly. +cached_graph: Graph = .{ .modules = &.{}, .names = &.{} }, + pub const RPath = union(enum) { lazy_path: LazyPath, special: []const u8, @@ -246,7 +246,6 @@ pub fn init( m: *Module, owner: *std.Build, value: union(enum) { options: CreateOptions, existing: *const Module }, - compile: ?*Step.Compile, ) void { const allocator = owner.allocator; @@ -254,7 +253,6 @@ pub fn init( .options => |options| { m.* = .{ .owner = owner, - .depending_steps = .{}, .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null, .import_table = .{}, .resolved_target = options.target, @@ -294,19 +292,11 @@ pub fn init( m.* = existing.*; }, } - - if (compile) |c| { - m.depending_steps.put(allocator, c, {}) catch @panic("OOM"); - } - - // This logic accesses `depending_steps` which was just modified above. - var it = m.iterateDependencies(null, false); - while (it.next()) |item| addShallowDependencies(m, item.module); } pub fn create(owner: *std.Build, options: CreateOptions) *Module { const m = owner.allocator.create(Module) catch @panic("OOM"); - m.init(owner, .{ .options = options }, null); + m.init(owner, .{ .options = options }); return m; } @@ -314,69 +304,6 @@ pub fn create(owner: *std.Build, options: CreateOptions) *Module { pub fn addImport(m: *Module, name: []const u8, module: *Module) void { const b = m.owner; m.import_table.put(b.allocator, b.dupe(name), module) catch @panic("OOM"); - - var it = module.iterateDependencies(null, false); - while (it.next()) |item| addShallowDependencies(m, item.module); -} - -/// Creates step dependencies and updates `depending_steps` of `dependee` so that -/// subsequent calls to `addImport` on `dependee` will additionally create step -/// dependencies on `m`'s `depending_steps`. -fn addShallowDependencies(m: *Module, dependee: *Module) void { - if (dependee.root_source_file) |lazy_path| addLazyPathDependencies(m, dependee, lazy_path); - for (dependee.lib_paths.items) |lib_path| addLazyPathDependencies(m, dependee, lib_path); - for (dependee.rpaths.items) |rpath| switch (rpath) { - .lazy_path => |lp| addLazyPathDependencies(m, dependee, lp), - .special => {}, - }; - - for (dependee.link_objects.items) |link_object| switch (link_object) { - .other_step => |compile| { - addStepDependencies(m, dependee, &compile.step); - addLazyPathDependenciesOnly(m, compile.getEmittedIncludeTree()); - }, - - .static_path, - .assembly_file, - => |lp| addLazyPathDependencies(m, dependee, lp), - - .c_source_file => |x| addLazyPathDependencies(m, dependee, x.file), - .win32_resource_file => |x| addLazyPathDependencies(m, dependee, x.file), - - .c_source_files, - .system_lib, - => {}, - }; -} - -fn addLazyPathDependencies(m: *Module, module: *Module, lazy_path: LazyPath) void { - addLazyPathDependenciesOnly(m, lazy_path); - if (m != module) { - for (m.depending_steps.keys()) |compile| { - module.depending_steps.put(m.owner.allocator, compile, {}) catch @panic("OOM"); - } - } -} - -fn addLazyPathDependenciesOnly(m: *Module, lazy_path: LazyPath) void { - for (m.depending_steps.keys()) |compile| { - lazy_path.addStepDependencies(&compile.step); - } -} - -fn addStepDependencies(m: *Module, module: *Module, dependee: *Step) void { - addStepDependenciesOnly(m, dependee); - if (m != module) { - for (m.depending_steps.keys()) |compile| { - module.depending_steps.put(m.owner.allocator, compile, {}) catch @panic("OOM"); - } - } -} - -fn addStepDependenciesOnly(m: *Module, dependee: *Step) void { - for (m.depending_steps.keys()) |compile| { - compile.step.dependOn(dependee); - } } /// Creates a new module and adds it to be used with `@import`. @@ -392,91 +319,6 @@ pub fn addOptions(m: *Module, module_name: []const u8, options: *Step.Options) v addImport(m, module_name, options.createModule()); } -pub const DependencyIterator = struct { - allocator: std.mem.Allocator, - index: usize, - set: std.AutoArrayHashMapUnmanaged(Key, []const u8), - chase_dyn_libs: bool, - - pub const Key = struct { - /// The compilation that contains the `Module`. Note that a `Module` might be - /// used by more than one compilation. - compile: ?*Step.Compile, - module: *Module, - }; - - pub const Item = struct { - /// The compilation that contains the `Module`. Note that a `Module` might be - /// used by more than one compilation. - compile: ?*Step.Compile, - module: *Module, - name: []const u8, - }; - - pub fn deinit(it: *DependencyIterator) void { - it.set.deinit(it.allocator); - it.* = undefined; - } - - pub fn next(it: *DependencyIterator) ?Item { - if (it.index >= it.set.count()) { - it.set.clearAndFree(it.allocator); - return null; - } - const key = it.set.keys()[it.index]; - const name = it.set.values()[it.index]; - it.index += 1; - const module = key.module; - it.set.ensureUnusedCapacity(it.allocator, module.import_table.count()) catch - @panic("OOM"); - for (module.import_table.keys(), module.import_table.values()) |dep_name, dep| { - it.set.putAssumeCapacity(.{ - .module = dep, - .compile = key.compile, - }, dep_name); - } - - if (key.compile != null) { - for (module.link_objects.items) |link_object| switch (link_object) { - .other_step => |compile| { - if (!it.chase_dyn_libs and compile.isDynamicLibrary()) continue; - - it.set.put(it.allocator, .{ - .module = compile.root_module, - .compile = compile, - }, "root") catch @panic("OOM"); - }, - else => {}, - }; - } - - return .{ - .compile = key.compile, - .module = key.module, - .name = name, - }; - } -}; - -pub fn iterateDependencies( - m: *Module, - chase_steps: ?*Step.Compile, - chase_dyn_libs: bool, -) DependencyIterator { - var it: DependencyIterator = .{ - .allocator = m.owner.allocator, - .index = 0, - .set = .{}, - .chase_dyn_libs = chase_dyn_libs, - }; - it.set.ensureUnusedCapacity(m.owner.allocator, m.import_table.count() + 1) catch @panic("OOM"); - it.set.putAssumeCapacity(.{ - .module = m, - .compile = chase_steps, - }, "root"); - return it; -} - pub const LinkSystemLibraryOptions = struct { /// Causes dynamic libraries to be linked regardless of whether they are /// actually depended on. When false, dynamic libraries with no referenced @@ -559,7 +401,6 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void { .flags = b.dupeStrings(options.flags), }; m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, c_source_files.root); } pub fn addCSourceFile(m: *Module, source: CSourceFile) void { @@ -568,7 +409,6 @@ pub fn addCSourceFile(m: *Module, source: CSourceFile) void { const c_source_file = allocator.create(CSourceFile) catch @panic("OOM"); c_source_file.* = source.dupe(b); m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, source.file); } /// Resource files must have the extension `.rc`. @@ -585,22 +425,16 @@ pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void { const rc_source_file = allocator.create(RcSourceFile) catch @panic("OOM"); rc_source_file.* = source.dupe(b); m.link_objects.append(allocator, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, source.file); - for (source.include_paths) |include_path| { - addLazyPathDependenciesOnly(m, include_path); - } } pub fn addAssemblyFile(m: *Module, source: LazyPath) void { const b = m.owner; m.link_objects.append(b.allocator, .{ .assembly_file = source.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, source); } pub fn addObjectFile(m: *Module, object: LazyPath) void { const b = m.owner; m.link_objects.append(b.allocator, .{ .static_path = object.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, object); } pub fn addObject(m: *Module, object: *Step.Compile) void { @@ -616,51 +450,43 @@ pub fn linkLibrary(m: *Module, library: *Step.Compile) void { pub fn addAfterIncludePath(m: *Module, lazy_path: LazyPath) void { const b = m.owner; m.include_dirs.append(b.allocator, .{ .path_after = lazy_path.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, lazy_path); } pub fn addSystemIncludePath(m: *Module, lazy_path: LazyPath) void { const b = m.owner; m.include_dirs.append(b.allocator, .{ .path_system = lazy_path.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, lazy_path); } pub fn addIncludePath(m: *Module, lazy_path: LazyPath) void { const b = m.owner; m.include_dirs.append(b.allocator, .{ .path = lazy_path.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, lazy_path); } pub fn addConfigHeader(m: *Module, config_header: *Step.ConfigHeader) void { const allocator = m.owner.allocator; m.include_dirs.append(allocator, .{ .config_header_step = config_header }) catch @panic("OOM"); - addStepDependenciesOnly(m, &config_header.step); } pub fn addSystemFrameworkPath(m: *Module, directory_path: LazyPath) void { const b = m.owner; m.include_dirs.append(b.allocator, .{ .framework_path_system = directory_path.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, directory_path); } pub fn addFrameworkPath(m: *Module, directory_path: LazyPath) void { const b = m.owner; m.include_dirs.append(b.allocator, .{ .framework_path = directory_path.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, directory_path); } pub fn addLibraryPath(m: *Module, directory_path: LazyPath) void { const b = m.owner; m.lib_paths.append(b.allocator, directory_path.dupe(b)) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, directory_path); } pub fn addRPath(m: *Module, directory_path: LazyPath) void { const b = m.owner; m.rpaths.append(b.allocator, .{ .lazy_path = directory_path.dupe(b) }) catch @panic("OOM"); - addLazyPathDependenciesOnly(m, directory_path); } pub fn addRPathSpecial(m: *Module, bytes: []const u8) void { @@ -784,7 +610,6 @@ fn addFlag( fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void { const allocator = m.owner.allocator; _ = other.getEmittedBin(); // Indicate there is a dependency on the outputted binary. - addStepDependenciesOnly(m, &other.step); if (other.rootModuleTarget().os.tag == .windows and other.isDynamicLibrary()) { _ = other.getEmittedImplib(); // Indicate dependency on the outputted implib. @@ -792,8 +617,6 @@ fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void { m.link_objects.append(allocator, .{ .other_step = other }) catch @panic("OOM"); m.include_dirs.append(allocator, .{ .other_step = other }) catch @panic("OOM"); - - addLazyPathDependenciesOnly(m, other.getEmittedIncludeTree()); } fn requireKnownTarget(m: *Module) std.Target { @@ -802,6 +625,46 @@ fn requireKnownTarget(m: *Module) std.Target { return resolved_target.result; } +/// Elements of `modules` and `names` are matched one-to-one. +pub const Graph = struct { + modules: []const *Module, + names: []const []const u8, +}; + +/// Intended to be used during the make phase only. +/// +/// Given that `root` is the root `Module` of a compilation, return all `Module`s +/// in the module graph, including `root` itself. `root` is guaranteed to be the +/// first module in the returned slice. +pub fn getGraph(root: *Module) Graph { + if (root.cached_graph.modules.len != 0) { + return root.cached_graph; + } + + const arena = root.owner.graph.arena; + + var modules: std.AutoArrayHashMapUnmanaged(*std.Build.Module, []const u8) = .empty; + var next_idx: usize = 0; + + modules.putNoClobber(arena, root, "root") catch @panic("OOM"); + + while (next_idx < modules.count()) { + const mod = modules.keys()[next_idx]; + next_idx += 1; + modules.ensureUnusedCapacity(arena, mod.import_table.count()) catch @panic("OOM"); + for (mod.import_table.keys(), mod.import_table.values()) |import_name, other_mod| { + modules.putAssumeCapacity(other_mod, import_name); + } + } + + const result: Graph = .{ + .modules = modules.keys(), + .names = modules.values(), + }; + root.cached_graph = result; + return result; +} + const Module = @This(); const std = @import("std"); const assert = std.debug.assert; diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index f34ce7785b..3c6e8231ca 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -389,7 +389,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile { const compile = owner.allocator.create(Compile) catch @panic("OOM"); compile.* = .{ - .root_module = undefined, + .root_module = options.root_module, .verbose_link = false, .verbose_cc = false, .linkage = options.linkage, @@ -432,8 +432,6 @@ pub fn create(owner: *std.Build, options: Options) *Compile { .zig_process = null, }; - options.root_module.init(owner, .{ .existing = options.root_module }, compile); - compile.root_module = options.root_module; if (options.zig_lib_dir) |lp| { compile.zig_lib_dir = lp.dupe(compile.step.owner); @@ -607,16 +605,17 @@ pub fn dependsOnSystemLibrary(compile: *const Compile, name: []const u8) bool { var is_linking_libc = false; var is_linking_libcpp = false; - var dep_it = compile.root_module.iterateDependencies(compile, true); - while (dep_it.next()) |module| { - for (module.link_objects.items) |link_object| { - switch (link_object) { - .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, - else => continue, + for (compile.getCompileDependencies(true)) |some_compile| { + for (some_compile.root_module.getGraph().modules) |mod| { + for (mod.link_objects.items) |lo| { + switch (lo) { + .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, + else => {}, + } } + if (mod.link_libc) is_linking_libc = true; + if (mod.link_libcpp) is_linking_libcpp = true; } - is_linking_libc = is_linking_libc or module.link_libc == true; - is_linking_libcpp = is_linking_libcpp or module.link_libcpp == true; } const target = compile.rootModuleTarget(); @@ -961,23 +960,22 @@ const CliNamedModules = struct { .modules = .{}, .names = .{}, }; - var dep_it = root_module.iterateDependencies(null, false); + const graph = root_module.getGraph(); { - const item = dep_it.next().?; - assert(root_module == item.module); + assert(graph.modules[0] == root_module); try compile.modules.put(arena, root_module, {}); try compile.names.put(arena, "root", {}); } - while (dep_it.next()) |item| { - var name = item.name; + for (graph.modules[1..], graph.names[1..]) |mod, orig_name| { + var name = orig_name; var n: usize = 0; while (true) { const gop = try compile.names.getOrPut(arena, name); if (!gop.found_existing) { - try compile.modules.putNoClobber(arena, item.module, {}); + try compile.modules.putNoClobber(arena, mod, {}); break; } - name = try std.fmt.allocPrint(arena, "{s}{d}", .{ item.name, n }); + name = try std.fmt.allocPrint(arena, "{s}{d}", .{ orig_name, n }); n += 1; } } @@ -1080,13 +1078,12 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { // emitted if there is nothing to link. var total_linker_objects: usize = @intFromBool(compile.root_module.root_source_file != null); - { - // Fully recursive iteration including dynamic libraries to detect - // libc and libc++ linkage. - var dep_it = compile.root_module.iterateDependencies(compile, true); - while (dep_it.next()) |key| { - if (key.module.link_libc == true) compile.is_linking_libc = true; - if (key.module.link_libcpp == true) compile.is_linking_libcpp = true; + // Fully recursive iteration including dynamic libraries to detect + // libc and libc++ linkage. + for (compile.getCompileDependencies(true)) |some_compile| { + for (some_compile.root_module.getGraph().modules) |mod| { + if (mod.link_libc == true) compile.is_linking_libc = true; + if (mod.link_libcpp == true) compile.is_linking_libcpp = true; } } @@ -1094,265 +1091,265 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { // For this loop, don't chase dynamic libraries because their link // objects are already linked. - var dep_it = compile.root_module.iterateDependencies(compile, false); + for (compile.getCompileDependencies(false)) |dep_compile| { + for (dep_compile.root_module.getGraph().modules) |mod| { + // While walking transitive dependencies, if a given link object is + // already included in a library, it should not redundantly be + // placed on the linker line of the dependee. + const my_responsibility = dep_compile == compile; + const already_linked = !my_responsibility and dep_compile.isDynamicLibrary(); - while (dep_it.next()) |dep| { - // While walking transitive dependencies, if a given link object is - // already included in a library, it should not redundantly be - // placed on the linker line of the dependee. - const my_responsibility = dep.compile.? == compile; - const already_linked = !my_responsibility and dep.compile.?.isDynamicLibrary(); - - // Inherit dependencies on darwin frameworks. - if (!already_linked) { - for (dep.module.frameworks.keys(), dep.module.frameworks.values()) |name, info| { - try frameworks.put(arena, name, info); - } - } - - // Inherit dependencies on system libraries and static libraries. - for (dep.module.link_objects.items) |link_object| { - switch (link_object) { - .static_path => |static_path| { - if (my_responsibility) { - try zig_args.append(static_path.getPath2(dep.module.owner, step)); - total_linker_objects += 1; - } - }, - .system_lib => |system_lib| { - const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name); - if (system_lib_gop.found_existing) { - try zig_args.appendSlice(system_lib_gop.value_ptr.*); - continue; - } else { - system_lib_gop.value_ptr.* = &.{}; - } - - if (already_linked) - continue; - - if ((system_lib.search_strategy != prev_search_strategy or - system_lib.preferred_link_mode != prev_preferred_link_mode) and - compile.linkage != .static) - { - switch (system_lib.search_strategy) { - .no_fallback => switch (system_lib.preferred_link_mode) { - .dynamic => try zig_args.append("-search_dylibs_only"), - .static => try zig_args.append("-search_static_only"), - }, - .paths_first => switch (system_lib.preferred_link_mode) { - .dynamic => try zig_args.append("-search_paths_first"), - .static => try zig_args.append("-search_paths_first_static"), - }, - .mode_first => switch (system_lib.preferred_link_mode) { - .dynamic => try zig_args.append("-search_dylibs_first"), - .static => try zig_args.append("-search_static_first"), - }, - } - prev_search_strategy = system_lib.search_strategy; - prev_preferred_link_mode = system_lib.preferred_link_mode; - } - - const prefix: []const u8 = prefix: { - if (system_lib.needed) break :prefix "-needed-l"; - if (system_lib.weak) break :prefix "-weak-l"; - break :prefix "-l"; - }; - switch (system_lib.use_pkg_config) { - .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), - .yes, .force => { - if (compile.runPkgConfig(system_lib.name)) |result| { - try zig_args.appendSlice(result.cflags); - try zig_args.appendSlice(result.libs); - try seen_system_libs.put(arena, system_lib.name, result.cflags); - } else |err| switch (err) { - error.PkgConfigInvalidOutput, - error.PkgConfigCrashed, - error.PkgConfigFailed, - error.PkgConfigNotInstalled, - error.PackageNotFound, - => switch (system_lib.use_pkg_config) { - .yes => { - // pkg-config failed, so fall back to linking the library - // by name directly. - try zig_args.append(b.fmt("{s}{s}", .{ - prefix, - system_lib.name, - })); - }, - .force => { - panic("pkg-config failed for library {s}", .{system_lib.name}); - }, - .no => unreachable, - }, - - else => |e| return e, - } - }, - } - }, - .other_step => |other| { - switch (other.kind) { - .exe => return step.fail("cannot link with an executable build artifact", .{}), - .@"test" => return step.fail("cannot link with a test", .{}), - .obj => { - const included_in_lib_or_obj = !my_responsibility and - (dep.compile.?.kind == .lib or dep.compile.?.kind == .obj); - if (!already_linked and !included_in_lib_or_obj) { - try zig_args.append(other.getEmittedBin().getPath2(b, step)); - total_linker_objects += 1; - } - }, - .lib => l: { - const other_produces_implib = other.producesImplib(); - const other_is_static = other_produces_implib or other.isStaticLibrary(); - - if (compile.isStaticLibrary() and other_is_static) { - // Avoid putting a static library inside a static library. - break :l; - } - - // For DLLs, we must link against the implib. - // For everything else, we directly link - // against the library file. - const full_path_lib = if (other_produces_implib) - other.getGeneratedFilePath("generated_implib", &compile.step) - else - other.getGeneratedFilePath("generated_bin", &compile.step); - - try zig_args.append(full_path_lib); - total_linker_objects += 1; - - if (other.linkage == .dynamic and - compile.rootModuleTarget().os.tag != .windows) - { - if (fs.path.dirname(full_path_lib)) |dirname| { - try zig_args.append("-rpath"); - try zig_args.append(dirname); - } - } - }, - } - }, - .assembly_file => |asm_file| l: { - if (!my_responsibility) break :l; - - if (prev_has_cflags) { - try zig_args.append("-cflags"); - try zig_args.append("--"); - prev_has_cflags = false; - } - try zig_args.append(asm_file.getPath2(dep.module.owner, step)); - total_linker_objects += 1; - }, - - .c_source_file => |c_source_file| l: { - if (!my_responsibility) break :l; - - if (c_source_file.flags.len == 0) { - if (prev_has_cflags) { - try zig_args.append("-cflags"); - try zig_args.append("--"); - prev_has_cflags = false; - } - } else { - try zig_args.append("-cflags"); - for (c_source_file.flags) |arg| { - try zig_args.append(arg); - } - try zig_args.append("--"); - prev_has_cflags = true; - } - try zig_args.append(c_source_file.file.getPath2(dep.module.owner, step)); - total_linker_objects += 1; - }, - - .c_source_files => |c_source_files| l: { - if (!my_responsibility) break :l; - - if (c_source_files.flags.len == 0) { - if (prev_has_cflags) { - try zig_args.append("-cflags"); - try zig_args.append("--"); - prev_has_cflags = false; - } - } else { - try zig_args.append("-cflags"); - for (c_source_files.flags) |flag| { - try zig_args.append(flag); - } - try zig_args.append("--"); - prev_has_cflags = true; - } - - const root_path = c_source_files.root.getPath2(dep.module.owner, step); - for (c_source_files.files) |file| { - try zig_args.append(b.pathJoin(&.{ root_path, file })); - } - - total_linker_objects += c_source_files.files.len; - }, - - .win32_resource_file => |rc_source_file| l: { - if (!my_responsibility) break :l; - - if (rc_source_file.flags.len == 0 and rc_source_file.include_paths.len == 0) { - if (prev_has_rcflags) { - try zig_args.append("-rcflags"); - try zig_args.append("--"); - prev_has_rcflags = false; - } - } else { - try zig_args.append("-rcflags"); - for (rc_source_file.flags) |arg| { - try zig_args.append(arg); - } - for (rc_source_file.include_paths) |include_path| { - try zig_args.append("/I"); - try zig_args.append(include_path.getPath2(dep.module.owner, step)); - } - try zig_args.append("--"); - prev_has_rcflags = true; - } - try zig_args.append(rc_source_file.file.getPath2(dep.module.owner, step)); - total_linker_objects += 1; - }, - } - } - - // We need to emit the --mod argument here so that the above link objects - // have the correct parent module, but only if the module is part of - // this compilation. - if (!my_responsibility) continue; - if (cli_named_modules.modules.getIndex(dep.module)) |module_cli_index| { - const module_cli_name = cli_named_modules.names.keys()[module_cli_index]; - try dep.module.appendZigProcessFlags(&zig_args, step); - - // --dep arguments - try zig_args.ensureUnusedCapacity(dep.module.import_table.count() * 2); - for (dep.module.import_table.keys(), dep.module.import_table.values()) |name, import| { - const import_index = cli_named_modules.modules.getIndex(import).?; - const import_cli_name = cli_named_modules.names.keys()[import_index]; - zig_args.appendAssumeCapacity("--dep"); - if (std.mem.eql(u8, import_cli_name, name)) { - zig_args.appendAssumeCapacity(import_cli_name); - } else { - zig_args.appendAssumeCapacity(b.fmt("{s}={s}", .{ name, import_cli_name })); + // Inherit dependencies on darwin frameworks. + if (!already_linked) { + for (mod.frameworks.keys(), mod.frameworks.values()) |name, info| { + try frameworks.put(arena, name, info); } } - // When the CLI sees a -M argument, it determines whether it - // implies the existence of a Zig compilation unit based on - // whether there is a root source file. If there is no root - // source file, then this is not a zig compilation unit - it is - // perhaps a set of linker objects, or C source files instead. - // Linker objects are added to the CLI globally, while C source - // files must have a module parent. - if (dep.module.root_source_file) |lp| { - const src = lp.getPath2(dep.module.owner, step); - try zig_args.append(b.fmt("-M{s}={s}", .{ module_cli_name, src })); - } else if (moduleNeedsCliArg(dep.module)) { - try zig_args.append(b.fmt("-M{s}", .{module_cli_name})); + // Inherit dependencies on system libraries and static libraries. + for (mod.link_objects.items) |link_object| { + switch (link_object) { + .static_path => |static_path| { + if (my_responsibility) { + try zig_args.append(static_path.getPath2(mod.owner, step)); + total_linker_objects += 1; + } + }, + .system_lib => |system_lib| { + const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name); + if (system_lib_gop.found_existing) { + try zig_args.appendSlice(system_lib_gop.value_ptr.*); + continue; + } else { + system_lib_gop.value_ptr.* = &.{}; + } + + if (already_linked) + continue; + + if ((system_lib.search_strategy != prev_search_strategy or + system_lib.preferred_link_mode != prev_preferred_link_mode) and + compile.linkage != .static) + { + switch (system_lib.search_strategy) { + .no_fallback => switch (system_lib.preferred_link_mode) { + .dynamic => try zig_args.append("-search_dylibs_only"), + .static => try zig_args.append("-search_static_only"), + }, + .paths_first => switch (system_lib.preferred_link_mode) { + .dynamic => try zig_args.append("-search_paths_first"), + .static => try zig_args.append("-search_paths_first_static"), + }, + .mode_first => switch (system_lib.preferred_link_mode) { + .dynamic => try zig_args.append("-search_dylibs_first"), + .static => try zig_args.append("-search_static_first"), + }, + } + prev_search_strategy = system_lib.search_strategy; + prev_preferred_link_mode = system_lib.preferred_link_mode; + } + + const prefix: []const u8 = prefix: { + if (system_lib.needed) break :prefix "-needed-l"; + if (system_lib.weak) break :prefix "-weak-l"; + break :prefix "-l"; + }; + switch (system_lib.use_pkg_config) { + .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), + .yes, .force => { + if (compile.runPkgConfig(system_lib.name)) |result| { + try zig_args.appendSlice(result.cflags); + try zig_args.appendSlice(result.libs); + try seen_system_libs.put(arena, system_lib.name, result.cflags); + } else |err| switch (err) { + error.PkgConfigInvalidOutput, + error.PkgConfigCrashed, + error.PkgConfigFailed, + error.PkgConfigNotInstalled, + error.PackageNotFound, + => switch (system_lib.use_pkg_config) { + .yes => { + // pkg-config failed, so fall back to linking the library + // by name directly. + try zig_args.append(b.fmt("{s}{s}", .{ + prefix, + system_lib.name, + })); + }, + .force => { + panic("pkg-config failed for library {s}", .{system_lib.name}); + }, + .no => unreachable, + }, + + else => |e| return e, + } + }, + } + }, + .other_step => |other| { + switch (other.kind) { + .exe => return step.fail("cannot link with an executable build artifact", .{}), + .@"test" => return step.fail("cannot link with a test", .{}), + .obj => { + const included_in_lib_or_obj = !my_responsibility and + (dep_compile.kind == .lib or dep_compile.kind == .obj); + if (!already_linked and !included_in_lib_or_obj) { + try zig_args.append(other.getEmittedBin().getPath2(b, step)); + total_linker_objects += 1; + } + }, + .lib => l: { + const other_produces_implib = other.producesImplib(); + const other_is_static = other_produces_implib or other.isStaticLibrary(); + + if (compile.isStaticLibrary() and other_is_static) { + // Avoid putting a static library inside a static library. + break :l; + } + + // For DLLs, we must link against the implib. + // For everything else, we directly link + // against the library file. + const full_path_lib = if (other_produces_implib) + other.getGeneratedFilePath("generated_implib", &compile.step) + else + other.getGeneratedFilePath("generated_bin", &compile.step); + + try zig_args.append(full_path_lib); + total_linker_objects += 1; + + if (other.linkage == .dynamic and + compile.rootModuleTarget().os.tag != .windows) + { + if (fs.path.dirname(full_path_lib)) |dirname| { + try zig_args.append("-rpath"); + try zig_args.append(dirname); + } + } + }, + } + }, + .assembly_file => |asm_file| l: { + if (!my_responsibility) break :l; + + if (prev_has_cflags) { + try zig_args.append("-cflags"); + try zig_args.append("--"); + prev_has_cflags = false; + } + try zig_args.append(asm_file.getPath2(mod.owner, step)); + total_linker_objects += 1; + }, + + .c_source_file => |c_source_file| l: { + if (!my_responsibility) break :l; + + if (c_source_file.flags.len == 0) { + if (prev_has_cflags) { + try zig_args.append("-cflags"); + try zig_args.append("--"); + prev_has_cflags = false; + } + } else { + try zig_args.append("-cflags"); + for (c_source_file.flags) |arg| { + try zig_args.append(arg); + } + try zig_args.append("--"); + prev_has_cflags = true; + } + try zig_args.append(c_source_file.file.getPath2(mod.owner, step)); + total_linker_objects += 1; + }, + + .c_source_files => |c_source_files| l: { + if (!my_responsibility) break :l; + + if (c_source_files.flags.len == 0) { + if (prev_has_cflags) { + try zig_args.append("-cflags"); + try zig_args.append("--"); + prev_has_cflags = false; + } + } else { + try zig_args.append("-cflags"); + for (c_source_files.flags) |flag| { + try zig_args.append(flag); + } + try zig_args.append("--"); + prev_has_cflags = true; + } + + const root_path = c_source_files.root.getPath2(mod.owner, step); + for (c_source_files.files) |file| { + try zig_args.append(b.pathJoin(&.{ root_path, file })); + } + + total_linker_objects += c_source_files.files.len; + }, + + .win32_resource_file => |rc_source_file| l: { + if (!my_responsibility) break :l; + + if (rc_source_file.flags.len == 0 and rc_source_file.include_paths.len == 0) { + if (prev_has_rcflags) { + try zig_args.append("-rcflags"); + try zig_args.append("--"); + prev_has_rcflags = false; + } + } else { + try zig_args.append("-rcflags"); + for (rc_source_file.flags) |arg| { + try zig_args.append(arg); + } + for (rc_source_file.include_paths) |include_path| { + try zig_args.append("/I"); + try zig_args.append(include_path.getPath2(mod.owner, step)); + } + try zig_args.append("--"); + prev_has_rcflags = true; + } + try zig_args.append(rc_source_file.file.getPath2(mod.owner, step)); + total_linker_objects += 1; + }, + } + } + + // We need to emit the --mod argument here so that the above link objects + // have the correct parent module, but only if the module is part of + // this compilation. + if (!my_responsibility) continue; + if (cli_named_modules.modules.getIndex(mod)) |module_cli_index| { + const module_cli_name = cli_named_modules.names.keys()[module_cli_index]; + try mod.appendZigProcessFlags(&zig_args, step); + + // --dep arguments + try zig_args.ensureUnusedCapacity(mod.import_table.count() * 2); + for (mod.import_table.keys(), mod.import_table.values()) |name, import| { + const import_index = cli_named_modules.modules.getIndex(import).?; + const import_cli_name = cli_named_modules.names.keys()[import_index]; + zig_args.appendAssumeCapacity("--dep"); + if (std.mem.eql(u8, import_cli_name, name)) { + zig_args.appendAssumeCapacity(import_cli_name); + } else { + zig_args.appendAssumeCapacity(b.fmt("{s}={s}", .{ name, import_cli_name })); + } + } + + // When the CLI sees a -M argument, it determines whether it + // implies the existence of a Zig compilation unit based on + // whether there is a root source file. If there is no root + // source file, then this is not a zig compilation unit - it is + // perhaps a set of linker objects, or C source files instead. + // Linker objects are added to the CLI globally, while C source + // files must have a module parent. + if (mod.root_source_file) |lp| { + const src = lp.getPath2(mod.owner, step); + try zig_args.append(b.fmt("-M{s}={s}", .{ module_cli_name, src })); + } else if (moduleNeedsCliArg(mod)) { + try zig_args.append(b.fmt("-M{s}", .{module_cli_name})); + } } } } @@ -2064,3 +2061,35 @@ fn moduleNeedsCliArg(mod: *const Module) bool { else => continue, } else false; } + +/// Return the full set of `Step.Compile` which `start` depends on, recursively. `start` itself is +/// always returned as the first element. If `chase_dynamic` is `false`, then dynamic libraries are +/// not included, and their dependencies are not considered; if `chase_dynamic` is `true`, dynamic +/// libraries are treated the same as other linked `Compile`s. +pub fn getCompileDependencies(start: *Compile, chase_dynamic: bool) []const *Compile { + const arena = start.step.owner.graph.arena; + + var compiles: std.AutoArrayHashMapUnmanaged(*Compile, void) = .empty; + var next_idx: usize = 0; + + compiles.putNoClobber(arena, start, {}) catch @panic("OOM"); + + while (next_idx < compiles.count()) { + const compile = compiles.keys()[next_idx]; + next_idx += 1; + + for (compile.root_module.getGraph().modules) |mod| { + for (mod.link_objects.items) |lo| { + switch (lo) { + .other_step => |other_compile| { + if (!chase_dynamic and other_compile.isDynamicLibrary()) continue; + compiles.put(arena, other_compile, {}) catch @panic("OOM"); + }, + else => {}, + } + } + } + } + + return compiles.keys(); +} diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 5408bb287b..37db10916f 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1719,15 +1719,12 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void { const b = run.step.owner; - var it = artifact.root_module.iterateDependencies(artifact, true); - while (it.next()) |item| { - const other = item.compile.?; - if (item.module == other.root_module) { - if (item.module.resolved_target.?.result.os.tag == .windows and - other.isDynamicLibrary()) - { - addPathDir(run, fs.path.dirname(other.getEmittedBin().getPath2(b, &run.step)).?); - } + const compiles = artifact.getCompileDependencies(true); + for (compiles) |compile| { + if (compile.root_module.resolved_target.?.result.os.tag == .windows and + compile.isDynamicLibrary()) + { + addPathDir(run, fs.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?); } } }