Commit 0bb93ca053
Changed files (4)
lib
compiler
std
Build
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);
+ },
+ };
+ }
+}
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);
-
- 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);
+ 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();
+
+ // Inherit dependencies on darwin frameworks.
+ if (!already_linked) {
+ for (mod.frameworks.keys(), mod.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"),
- },
+ // 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.* = &.{};
}
- 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,
+ 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"),
},
-
- 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;
}
+ prev_search_strategy = system_lib.search_strategy;
+ prev_preferred_link_mode = system_lib.preferred_link_mode;
+ }
- // 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);
+ 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,
+ },
- try zig_args.append(full_path_lib);
- total_linker_objects += 1;
+ 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 (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);
+ if (compile.isStaticLibrary() and other_is_static) {
+ // Avoid putting a static library inside a static library.
+ break :l;
}
- }
- },
- }
- },
- .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;
- },
+ // 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);
- .c_source_file => |c_source_file| l: {
- if (!my_responsibility) break :l;
+ 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 (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;
- },
+ try zig_args.append(asm_file.getPath2(mod.owner, step));
+ total_linker_objects += 1;
+ },
- .c_source_files => |c_source_files| l: {
- if (!my_responsibility) break :l;
+ .c_source_file => |c_source_file| l: {
+ if (!my_responsibility) break :l;
- if (c_source_files.flags.len == 0) {
- if (prev_has_cflags) {
+ 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 = false;
+ prev_has_cflags = true;
}
- } else {
- try zig_args.append("-cflags");
- for (c_source_files.flags) |flag| {
- try zig_args.append(flag);
+ 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;
}
- 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 }));
- }
+ 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;
- },
+ total_linker_objects += c_source_files.files.len;
+ },
- .win32_resource_file => |rc_source_file| l: {
- if (!my_responsibility) break :l;
+ .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) {
+ 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 = 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));
+ prev_has_rcflags = true;
}
- 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;
- },
+ 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(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 }));
+ // 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 (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}));
+ // 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();
+}
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)).?);
}
}
}
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;