From 8f5b259228ccba5bb37f502da20ed25de60a62da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Nov 2023 22:47:34 -0700 Subject: [PATCH] introduce std.Build.Module and extract some logic into it This moves many settings from `std.Build.Step.Compile` and into `std.Build.Module`, and then makes them transitive. In other words, it adds support for exposing Zig modules in packages, which are configured in various ways, such as depending on other link objects, include paths, or even a different optimization mode. Now, transitive dependencies will be included in the compilation, so you can, for example, make a Zig module depend on some C source code, and expose that Zig module in a package. Currently, the compiler frontend autogenerates only one `@import("builtin")` module for the entire compilation, however, a future enhancement will be to make it honor the differences in modules, so that modules can be compiled with different optimization modes, code model, valgrind integration, or even target CPU feature set. closes #14719 --- lib/std/Build.zig | 164 ++--- lib/std/Build/Module.zig | 616 ++++++++++++++++++ lib/std/Build/Step/Compile.zig | 1123 +++++++++----------------------- lib/std/Build/Step/Run.zig | 36 +- 4 files changed, 991 insertions(+), 948 deletions(-) create mode 100644 lib/std/Build/Module.zig diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 7ace95947368..590ac5aadf6a 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -29,36 +29,7 @@ pub const Builder = Build; pub const InstallDirectoryOptions = Step.InstallDir.Options; pub const Step = @import("Build/Step.zig"); -/// deprecated: use `Step.CheckFile`. -pub const CheckFileStep = @import("Build/Step/CheckFile.zig"); -/// deprecated: use `Step.CheckObject`. -pub const CheckObjectStep = @import("Build/Step/CheckObject.zig"); -/// deprecated: use `Step.ConfigHeader`. -pub const ConfigHeaderStep = @import("Build/Step/ConfigHeader.zig"); -/// deprecated: use `Step.Fmt`. -pub const FmtStep = @import("Build/Step/Fmt.zig"); -/// deprecated: use `Step.InstallArtifact`. -pub const InstallArtifactStep = @import("Build/Step/InstallArtifact.zig"); -/// deprecated: use `Step.InstallDir`. -pub const InstallDirStep = @import("Build/Step/InstallDir.zig"); -/// deprecated: use `Step.InstallFile`. -pub const InstallFileStep = @import("Build/Step/InstallFile.zig"); -/// deprecated: use `Step.ObjCopy`. -pub const ObjCopyStep = @import("Build/Step/ObjCopy.zig"); -/// deprecated: use `Step.Compile`. -pub const CompileStep = @import("Build/Step/Compile.zig"); -/// deprecated: use `Step.Options`. -pub const OptionsStep = @import("Build/Step/Options.zig"); -/// deprecated: use `Step.RemoveDir`. -pub const RemoveDirStep = @import("Build/Step/RemoveDir.zig"); -/// deprecated: use `Step.Run`. -pub const RunStep = @import("Build/Step/Run.zig"); -/// deprecated: use `Step.TranslateC`. -pub const TranslateCStep = @import("Build/Step/TranslateC.zig"); -/// deprecated: use `Step.WriteFile`. -pub const WriteFileStep = @import("Build/Step/WriteFile.zig"); -/// deprecated: use `LazyPath`. -pub const FileSource = LazyPath; +pub const Module = @import("Build/Module.zig"); install_tls: TopLevelStep, uninstall_tls: TopLevelStep, @@ -634,34 +605,31 @@ pub const ExecutableOptions = struct { use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, - main_mod_path: ?LazyPath = null, /// Embed a `.manifest` file in the compilation if the object format supports it. /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference /// Manifest files must have the extension `.manifest`. /// Can be set regardless of target. The `.manifest` file will be ignored /// if the target object format does not support embedded manifests. win32_manifest: ?LazyPath = null, - - /// Deprecated; use `main_mod_path`. - main_pkg_path: ?LazyPath = null, }; pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile { return Step.Compile.create(b, .{ .name = options.name, - .root_source_file = options.root_source_file, + .root_module = .{ + .root_source_file = options.root_source_file, + .target = options.target, + .optimize = options.optimize, + .link_libc = options.link_libc, + .single_threaded = options.single_threaded, + }, .version = options.version, - .target = options.target, - .optimize = options.optimize, .kind = .exe, .linkage = options.linkage, .max_rss = options.max_rss, - .link_libc = options.link_libc, - .single_threaded = options.single_threaded, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, - .main_mod_path = options.main_mod_path orelse options.main_pkg_path, .win32_manifest = options.win32_manifest, }); } @@ -677,26 +645,23 @@ pub const ObjectOptions = struct { use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, - main_mod_path: ?LazyPath = null, - - /// Deprecated; use `main_mod_path`. - main_pkg_path: ?LazyPath = null, }; pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile { return Step.Compile.create(b, .{ .name = options.name, - .root_source_file = options.root_source_file, - .target = options.target, - .optimize = options.optimize, + .root_module = .{ + .root_source_file = options.root_source_file, + .target = options.target, + .optimize = options.optimize, + .link_libc = options.link_libc, + .single_threaded = options.single_threaded, + }, .kind = .obj, .max_rss = options.max_rss, - .link_libc = options.link_libc, - .single_threaded = options.single_threaded, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, - .main_mod_path = options.main_mod_path orelse options.main_pkg_path, }); } @@ -712,34 +677,31 @@ pub const SharedLibraryOptions = struct { use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, - main_mod_path: ?LazyPath = null, /// Embed a `.manifest` file in the compilation if the object format supports it. /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference /// Manifest files must have the extension `.manifest`. /// Can be set regardless of target. The `.manifest` file will be ignored /// if the target object format does not support embedded manifests. win32_manifest: ?LazyPath = null, - - /// Deprecated; use `main_mod_path`. - main_pkg_path: ?LazyPath = null, }; pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile { return Step.Compile.create(b, .{ .name = options.name, - .root_source_file = options.root_source_file, + .root_module = .{ + .target = options.target, + .optimize = options.optimize, + .root_source_file = options.root_source_file, + .link_libc = options.link_libc, + .single_threaded = options.single_threaded, + }, .kind = .lib, .linkage = .dynamic, .version = options.version, - .target = options.target, - .optimize = options.optimize, .max_rss = options.max_rss, - .link_libc = options.link_libc, - .single_threaded = options.single_threaded, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, - .main_mod_path = options.main_mod_path orelse options.main_pkg_path, .win32_manifest = options.win32_manifest, }); } @@ -756,28 +718,25 @@ pub const StaticLibraryOptions = struct { use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, - main_mod_path: ?LazyPath = null, - - /// Deprecated; use `main_mod_path`. - main_pkg_path: ?LazyPath = null, }; pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile { return Step.Compile.create(b, .{ .name = options.name, - .root_source_file = options.root_source_file, + .root_module = .{ + .target = options.target, + .optimize = options.optimize, + .root_source_file = options.root_source_file, + .link_libc = options.link_libc, + .single_threaded = options.single_threaded, + }, .kind = .lib, .linkage = .static, .version = options.version, - .target = options.target, - .optimize = options.optimize, .max_rss = options.max_rss, - .link_libc = options.link_libc, - .single_threaded = options.single_threaded, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, - .main_mod_path = options.main_mod_path orelse options.main_pkg_path, }); } @@ -795,28 +754,25 @@ pub const TestOptions = struct { use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, - main_mod_path: ?LazyPath = null, - - /// Deprecated; use `main_mod_path`. - main_pkg_path: ?LazyPath = null, }; pub fn addTest(b: *Build, options: TestOptions) *Step.Compile { return Step.Compile.create(b, .{ .name = options.name, .kind = .@"test", - .root_source_file = options.root_source_file, - .target = options.target, - .optimize = options.optimize, + .root_module = .{ + .root_source_file = options.root_source_file, + .target = options.target, + .optimize = options.optimize, + .link_libc = options.link_libc, + .single_threaded = options.single_threaded, + }, .max_rss = options.max_rss, .filter = options.filter, .test_runner = options.test_runner, - .link_libc = options.link_libc, - .single_threaded = options.single_threaded, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, - .main_mod_path = options.main_mod_path orelse options.main_pkg_path, }); } @@ -833,9 +789,10 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile { const obj_step = Step.Compile.create(b, .{ .name = options.name, .kind = .obj, - .root_source_file = null, - .target = options.target, - .optimize = options.optimize, + .root_module = .{ + .target = options.target, + .optimize = options.optimize, + }, .max_rss = options.max_rss, .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, }); @@ -846,41 +803,17 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile { /// This function creates a module and adds it to the package's module set, making /// it available to other packages which depend on this one. /// `createModule` can be used instead to create a private module. -pub fn addModule(b: *Build, name: []const u8, options: CreateModuleOptions) *Module { - const module = b.createModule(options); +pub fn addModule(b: *Build, name: []const u8, options: Module.CreateOptions) *Module { + const module = Module.create(b, options); b.modules.put(b.dupe(name), module) catch @panic("OOM"); return module; } -pub const ModuleDependency = struct { - name: []const u8, - module: *Module, -}; - -pub const CreateModuleOptions = struct { - source_file: LazyPath, - dependencies: []const ModuleDependency = &.{}, -}; - /// This function creates a private module, to be used by the current package, /// but not exposed to other packages depending on this one. /// `addModule` can be used instead to create a public module. -pub fn createModule(b: *Build, options: CreateModuleOptions) *Module { - const module = b.allocator.create(Module) catch @panic("OOM"); - module.* = .{ - .builder = b, - .source_file = options.source_file.dupe(b), - .dependencies = moduleDependenciesToArrayHashMap(b.allocator, options.dependencies), - }; - return module; -} - -fn moduleDependenciesToArrayHashMap(arena: Allocator, deps: []const ModuleDependency) std.StringArrayHashMap(*Module) { - var result = std.StringArrayHashMap(*Module).init(arena); - for (deps) |dep| { - result.put(dep.name, dep.module) catch @panic("OOM"); - } - return result; +pub fn createModule(b: *Build, options: Module.CreateOptions) *Module { + return Module.create(b, options); } /// Initializes a `Step.Run` with argv, which must at least have the path to the @@ -1885,15 +1818,6 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void { } } -pub const Module = struct { - builder: *Build, - /// This could either be a generated file, in which case the module - /// contains exactly one file, or it could be a path to the root source - /// file of directory of files which constitute the module. - source_file: LazyPath, - dependencies: std.StringArrayHashMap(*Module), -}; - /// A file that is generated by a build step. /// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic. pub const GeneratedFile = struct { diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig new file mode 100644 index 000000000000..31db0ad6894b --- /dev/null +++ b/lib/std/Build/Module.zig @@ -0,0 +1,616 @@ +/// 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(*std.Build.Step.Compile, void), +/// This could either be a generated file, in which case the module +/// contains exactly one file, or it could be a path to the root source +/// file of directory of files which constitute the module. +/// If `null`, it means this module is made up of only `link_objects`. +root_source_file: ?LazyPath, +/// The modules that are mapped into this module's import table. +import_table: std.StringArrayHashMap(*Module), + +target: std.zig.CrossTarget, +target_info: NativeTargetInfo, +optimize: std.builtin.OptimizeMode, +dwarf_format: ?std.dwarf.Format, + +c_macros: std.ArrayList([]const u8), +include_dirs: std.ArrayList(IncludeDir), +lib_paths: std.ArrayList(LazyPath), +rpaths: std.ArrayList(LazyPath), +frameworks: std.StringArrayHashMapUnmanaged(FrameworkLinkInfo), +c_std: std.Build.CStd, +link_objects: std.ArrayList(LinkObject), + +strip: ?bool, +unwind_tables: ?bool, +single_threaded: ?bool, +stack_protector: ?bool, +stack_check: ?bool, +sanitize_c: ?bool, +sanitize_thread: ?bool, +code_model: std.builtin.CodeModel, +/// Whether to emit machine code that integrates with Valgrind. +valgrind: ?bool, +/// Position Independent Code +pic: ?bool, +red_zone: ?bool, +/// Whether to omit the stack frame pointer. Frees up a register and makes it +/// more more difficiult to obtain stack traces. Has target-dependent effects. +omit_frame_pointer: ?bool, +/// `true` requires a compilation that includes this Module to link libc. +/// `false` causes a build failure if a compilation that includes this Module would link libc. +/// `null` neither requires nor prevents libc from being linked. +link_libc: ?bool, +/// `true` requires a compilation that includes this Module to link libc++. +/// `false` causes a build failure if a compilation that includes this Module would link libc++. +/// `null` neither requires nor prevents libc++ from being linked. +link_libcpp: ?bool, + +/// Symbols to be exported when compiling to WebAssembly. +export_symbol_names: []const []const u8 = &.{}, + +pub const LinkObject = union(enum) { + static_path: LazyPath, + other_step: *std.Build.Step.Compile, + system_lib: SystemLib, + assembly_file: LazyPath, + c_source_file: *CSourceFile, + c_source_files: *CSourceFiles, + win32_resource_file: *RcSourceFile, +}; + +pub const SystemLib = struct { + name: []const u8, + needed: bool, + weak: bool, + use_pkg_config: UsePkgConfig, + preferred_link_mode: std.builtin.LinkMode, + search_strategy: SystemLib.SearchStrategy, + + pub const UsePkgConfig = enum { + /// Don't use pkg-config, just pass -lfoo where foo is name. + no, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, fall back to passing -lfoo where foo is name. + yes, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, error out. + force, + }; + + pub const SearchStrategy = enum { paths_first, mode_first, no_fallback }; +}; + +pub const CSourceFiles = struct { + dependency: ?*std.Build.Dependency, + /// If `dependency` is not null relative to it, + /// else relative to the build root. + files: []const []const u8, + flags: []const []const u8, +}; + +pub const CSourceFile = struct { + file: LazyPath, + flags: []const []const u8, + + pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { + return .{ + .file = self.file.dupe(b), + .flags = b.dupeStrings(self.flags), + }; + } +}; + +pub const RcSourceFile = struct { + file: LazyPath, + /// Any option that rc.exe accepts will work here, with the exception of: + /// - `/fo`: The output filename is set by the build system + /// - `/p`: Only running the preprocessor is not supported in this context + /// - `/:no-preprocess` (non-standard option): Not supported in this context + /// - Any MUI-related option + /// https://learn.microsoft.com/en-us/windows/win32/menurc/using-rc-the-rc-command-line- + /// + /// Implicitly defined options: + /// /x (ignore the INCLUDE environment variable) + /// /D_DEBUG or /DNDEBUG depending on the optimization mode + flags: []const []const u8 = &.{}, + + pub fn dupe(self: RcSourceFile, b: *std.Build) RcSourceFile { + return .{ + .file = self.file.dupe(b), + .flags = b.dupeStrings(self.flags), + }; + } +}; + +pub const IncludeDir = union(enum) { + path: LazyPath, + path_system: LazyPath, + path_after: LazyPath, + framework_path: LazyPath, + framework_path_system: LazyPath, + other_step: *std.Build.Step.Compile, + config_header_step: *std.Build.Step.ConfigHeader, +}; + +pub const FrameworkLinkInfo = struct { + needed: bool = false, + weak: bool = false, +}; + +pub const CreateOptions = struct { + target: std.zig.CrossTarget, + target_info: ?NativeTargetInfo = null, + optimize: std.builtin.OptimizeMode, + root_source_file: ?LazyPath = null, + import_table: []const Import = &.{}, + link_libc: ?bool = null, + link_libcpp: ?bool = null, + single_threaded: ?bool = null, + strip: ?bool = null, + unwind_tables: ?bool = null, + dwarf_format: ?std.dwarf.Format = null, + c_std: std.Build.CStd = .C99, + code_model: std.builtin.CodeModel = .default, + stack_protector: ?bool = null, + stack_check: ?bool = null, + sanitize_c: ?bool = null, + sanitize_thread: ?bool = null, + valgrind: ?bool = null, + pic: ?bool = null, + red_zone: ?bool = null, + /// Whether to omit the stack frame pointer. Frees up a register and makes it + /// more more difficiult to obtain stack traces. Has target-dependent effects. + omit_frame_pointer: ?bool = null, +}; + +pub const Import = struct { + name: []const u8, + module: *Module, +}; + +pub fn init(owner: *std.Build, options: CreateOptions, compile: ?*std.Build.Step.Compile) Module { + var m: Module = .{ + .owner = owner, + .depending_steps = .{}, + .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null, + .import_table = std.StringArrayHashMap(*Module).init(owner.allocator), + .target = options.target, + .target_info = options.target_info orelse + NativeTargetInfo.detect(options.target) catch @panic("unhandled error"), + .optimize = options.optimize, + .link_libc = options.link_libc, + .link_libcpp = options.link_libcpp, + .dwarf_format = options.dwarf_format, + .c_macros = std.ArrayList([]const u8).init(owner.allocator), + .include_dirs = std.ArrayList(IncludeDir).init(owner.allocator), + .lib_paths = std.ArrayList(LazyPath).init(owner.allocator), + .rpaths = std.ArrayList(LazyPath).init(owner.allocator), + .frameworks = .{}, + .c_std = options.c_std, + .link_objects = std.ArrayList(LinkObject).init(owner.allocator), + .strip = options.strip, + .unwind_tables = options.unwind_tables, + .single_threaded = options.single_threaded, + .stack_protector = options.stack_protector, + .stack_check = options.stack_check, + .sanitize_c = options.sanitize_c, + .sanitize_thread = options.sanitize_thread, + .code_model = options.code_model, + .valgrind = options.valgrind, + .pic = options.pic, + .red_zone = options.red_zone, + .omit_frame_pointer = options.omit_frame_pointer, + .export_symbol_names = &.{}, + }; + + if (compile) |c| { + m.depending_steps.put(owner.allocator, c, {}) catch @panic("OOM"); + } + + m.import_table.ensureUnusedCapacity(options.import_table.len) catch @panic("OOM"); + for (options.import_table) |dep| { + m.import_table.putAssumeCapacity(dep.name, dep.module); + } + + var it = m.iterateDependencies(null); + while (it.next()) |item| addShallowDependencies(&m, item.module); + + return m; +} + +pub fn create(owner: *std.Build, options: CreateOptions) *Module { + const m = owner.allocator.create(Module) catch @panic("OOM"); + m.* = init(owner, options, null); + return m; +} + +/// Adds an existing module to be used with `@import`. +pub fn addImport(m: *Module, name: []const u8, module: *Module) void { + const b = m.owner; + m.import_table.put(b.dupe(name), module) catch @panic("OOM"); + + var it = module.iterateDependencies(null); + 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| addLazyPathDependencies(m, dependee, rpath); + + for (dependee.link_objects.items) |link_object| switch (link_object) { + .other_step => |compile| addStepDependencies(m, dependee, &compile.step), + + .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: *std.Build.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: *std.Build.Step) void { + for (m.depending_steps.keys()) |compile| { + compile.step.dependOn(dependee); + } +} + +/// Creates a new module and adds it to be used with `@import`. +pub fn addAnonymousImport(m: *Module, name: []const u8, options: std.Build.CreateModuleOptions) void { + const b = m.step.owner; + const module = b.createModule(options); + return addImport(m, name, module); +} + +pub fn addOptions(m: *Module, module_name: []const u8, options: *std.Build.Step.Options) void { + addImport(m, module_name, options.createModule()); +} + +pub const DependencyIterator = struct { + allocator: std.mem.Allocator, + index: usize, + set: std.AutoArrayHashMapUnmanaged(Key, []const u8), + + pub const Key = struct { + /// The compilation that contains the `Module`. Note that a `Module` might be + /// used by more than one compilation. + compile: ?*std.Build.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: ?*std.Build.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| { + 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: ?*std.Build.Step.Compile, +) DependencyIterator { + var it: DependencyIterator = .{ + .allocator = m.owner.allocator, + .index = 0, + .set = .{}, + }; + 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 { + needed: bool = false, + weak: bool = false, + use_pkg_config: SystemLib.UsePkgConfig = .yes, + preferred_link_mode: std.builtin.LinkMode = .Dynamic, + search_strategy: SystemLib.SearchStrategy = .paths_first, +}; + +pub fn linkSystemLibrary( + m: *Module, + name: []const u8, + options: LinkSystemLibraryOptions, +) void { + const b = m.owner; + if (m.target_info.target.is_libc_lib_name(name)) { + m.link_libc = true; + return; + } + if (m.target_info.target.is_libcpp_lib_name(name)) { + m.link_libcpp = true; + return; + } + + m.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(name), + .needed = options.needed, + .weak = options.weak, + .use_pkg_config = options.use_pkg_config, + .preferred_link_mode = options.preferred_link_mode, + .search_strategy = options.search_strategy, + }, + }) catch @panic("OOM"); +} + +pub const AddCSourceFilesOptions = struct { + /// When provided, `files` are relative to `dependency` rather than the + /// package that owns the `Compile` step. + dependency: ?*std.Build.Dependency = null, + files: []const []const u8, + flags: []const []const u8 = &.{}, +}; + +/// Handy when you have many C/C++ source files and want them all to have the same flags. +pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void { + const c_source_files = m.owner.allocator.create(CSourceFiles) catch @panic("OOM"); + c_source_files.* = .{ + .dependency = options.dependency, + .files = m.owner.dupeStrings(options.files), + .flags = m.owner.dupeStrings(options.flags), + }; + m.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM"); +} + +pub fn addCSourceFile(m: *Module, source: CSourceFile) void { + const c_source_file = m.owner.allocator.create(CSourceFile) catch @panic("OOM"); + c_source_file.* = source.dupe(m.owner); + m.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); + addLazyPathDependenciesOnly(m, source.file); +} + +/// Resource files must have the extension `.rc`. +/// Can be called regardless of target. The .rc file will be ignored +/// if the target object format does not support embedded resources. +pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void { + // Only the PE/COFF format has a Resource Table, so for any other target + // the resource file is ignored. + if (m.target_info.target.ofmt != .coff) return; + + const rc_source_file = m.owner.allocator.create(RcSourceFile) catch @panic("OOM"); + rc_source_file.* = source.dupe(m.owner); + m.link_objects.append(.{ .win32_resource_file = rc_source_file }) catch @panic("OOM"); + addLazyPathDependenciesOnly(m, source.file); +} + +pub fn addAssemblyFile(m: *Module, source: LazyPath) void { + m.link_objects.append(.{ .assembly_file = source.dupe(m.owner) }) catch @panic("OOM"); + addLazyPathDependenciesOnly(m, source); +} + +pub fn addObjectFile(m: *Module, source: LazyPath) void { + m.link_objects.append(.{ .static_path = source.dupe(m.owner) }) catch @panic("OOM"); + addLazyPathDependencies(m, source); +} + +pub fn appendZigProcessFlags( + m: *Module, + zig_args: *std.ArrayList([]const u8), + asking_step: ?*std.Build.Step, +) !void { + const b = m.owner; + + try addFlag(zig_args, m.strip, "-fstrip", "-fno-strip"); + try addFlag(zig_args, m.unwind_tables, "-funwind-tables", "-fno-unwind-tables"); + try addFlag(zig_args, m.single_threaded, "-fsingle-threaded", "-fno-single-threaded"); + try addFlag(zig_args, m.stack_check, "-fstack-check", "-fno-stack-check"); + try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector"); + try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer"); + try addFlag(zig_args, m.sanitize_c, "-fsanitize-c", "-fno-sanitize-c"); + try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread"); + try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind"); + try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); + try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); + + if (m.dwarf_format) |dwarf_format| { + try zig_args.append(switch (dwarf_format) { + .@"32" => "-gdwarf32", + .@"64" => "-gdwarf64", + }); + } + + try zig_args.ensureUnusedCapacity(1); + switch (m.optimize) { + .Debug => {}, // Skip since it's the default. + .ReleaseSmall => zig_args.appendAssumeCapacity("-OReleaseSmall"), + .ReleaseFast => zig_args.appendAssumeCapacity("-OReleaseFast"), + .ReleaseSafe => zig_args.appendAssumeCapacity("-OReleaseSafe"), + } + + if (m.code_model != .default) { + try zig_args.append("-mcmodel"); + try zig_args.append(@tagName(m.code_model)); + } + + if (!m.target.isNative()) { + try zig_args.appendSlice(&.{ + "-target", try m.target.zigTriple(b.allocator), + "-mcpu", try std.Build.serializeCpu(b.allocator, m.target.getCpu()), + }); + + if (m.target.dynamic_linker.get()) |dynamic_linker| { + try zig_args.append("--dynamic-linker"); + try zig_args.append(dynamic_linker); + } + } + + for (m.export_symbol_names) |symbol_name| { + try zig_args.append(b.fmt("--export={s}", .{symbol_name})); + } + + for (m.include_dirs.items) |include_dir| { + switch (include_dir) { + .path => |include_path| { + try zig_args.append("-I"); + try zig_args.append(include_path.getPath(b)); + }, + .path_system => |include_path| { + try zig_args.append("-isystem"); + try zig_args.append(include_path.getPath(b)); + }, + .path_after => |include_path| { + try zig_args.append("-idirafter"); + try zig_args.append(include_path.getPath(b)); + }, + .framework_path => |include_path| { + try zig_args.append("-F"); + try zig_args.append(include_path.getPath2(b, asking_step)); + }, + .framework_path_system => |include_path| { + try zig_args.append("-iframework"); + try zig_args.append(include_path.getPath2(b, asking_step)); + }, + .other_step => |other| { + if (other.generated_h) |header| { + try zig_args.append("-isystem"); + try zig_args.append(std.fs.path.dirname(header.path.?).?); + } + if (other.installed_headers.items.len > 0) { + try zig_args.append("-I"); + try zig_args.append(b.pathJoin(&.{ + other.step.owner.install_prefix, "include", + })); + } + }, + .config_header_step => |config_header| { + const full_file_path = config_header.output_file.path.?; + const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len]; + try zig_args.appendSlice(&.{ "-I", header_dir_path }); + }, + } + } + + for (m.c_macros.items) |c_macro| { + try zig_args.append("-D"); + try zig_args.append(c_macro); + } + + try zig_args.ensureUnusedCapacity(2 * m.lib_paths.items.len); + for (m.lib_paths.items) |lib_path| { + zig_args.appendAssumeCapacity("-L"); + zig_args.appendAssumeCapacity(lib_path.getPath2(b, asking_step)); + } + + try zig_args.ensureUnusedCapacity(2 * m.rpaths.items.len); + for (m.rpaths.items) |rpath| { + zig_args.appendAssumeCapacity("-rpath"); + + if (m.target_info.target.isDarwin()) switch (rpath) { + .path, .cwd_relative => |path| { + // On Darwin, we should not try to expand special runtime paths such as + // * @executable_path + // * @loader_path + if (std.mem.startsWith(u8, path, "@executable_path") or + std.mem.startsWith(u8, path, "@loader_path")) + { + zig_args.appendAssumeCapacity(path); + continue; + } + }, + .generated, .dependency => {}, + }; + + zig_args.appendAssumeCapacity(rpath.getPath2(b, asking_step)); + } +} + +fn addFlag( + args: *std.ArrayList([]const u8), + opt: ?bool, + then_name: []const u8, + else_name: []const u8, +) !void { + const cond = opt orelse return; + return args.append(if (cond) then_name else else_name); +} + +const Module = @This(); +const std = @import("std"); +const assert = std.debug.assert; +const LazyPath = std.Build.LazyPath; +const NativeTargetInfo = std.zig.system.NativeTargetInfo; diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index d06a44c4da47..31d07a43596b 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -24,35 +24,24 @@ const Compile = @This(); pub const base_id: Step.Id = .compile; step: Step, +root_module: Module, + name: []const u8, -target: CrossTarget, -target_info: NativeTargetInfo, -optimize: std.builtin.OptimizeMode, linker_script: ?LazyPath = null, version_script: ?[]const u8 = null, out_filename: []const u8, +out_lib_filename: []const u8, linkage: ?Linkage = null, version: ?std.SemanticVersion, kind: Kind, major_only_filename: ?[]const u8, name_only_filename: ?[]const u8, -strip: ?bool, -unwind_tables: ?bool, // keep in sync with src/link.zig:CompressDebugSections compress_debug_sections: enum { none, zlib, zstd } = .none, -lib_paths: ArrayList(LazyPath), -rpaths: ArrayList(LazyPath), -frameworks: StringHashMap(FrameworkLinkInfo), verbose_link: bool, verbose_cc: bool, bundle_compiler_rt: ?bool = null, -single_threaded: ?bool, -stack_protector: ?bool = null, -disable_stack_probing: bool, -disable_sanitize_c: bool, -sanitize_thread: bool, rdynamic: bool, -dwarf_format: ?std.dwarf.Format = null, import_memory: bool = false, export_memory: bool = false, /// For WebAssembly targets, this will allow for undefined symbols to @@ -64,31 +53,16 @@ initial_memory: ?u64 = null, max_memory: ?u64 = null, shared_memory: bool = false, global_base: ?u64 = null, -c_std: std.Build.CStd, /// Set via options; intended to be read-only after that. zig_lib_dir: ?LazyPath, -/// Set via options; intended to be read-only after that. -main_mod_path: ?LazyPath, exec_cmd_args: ?[]const ?[]const u8, filter: ?[]const u8, test_evented_io: bool = false, test_runner: ?[]const u8, test_server_mode: bool, -code_model: std.builtin.CodeModel = .default, wasi_exec_model: ?std.builtin.WasiExecModel = null, -/// Symbols to be exported when compiling to wasm -export_symbol_names: []const []const u8 = &.{}, - -root_src: ?LazyPath, -out_lib_filename: []const u8, -modules: std.StringArrayHashMap(*Module), -link_objects: ArrayList(LinkObject), -include_dirs: ArrayList(IncludeDir), -c_macros: ArrayList([]const u8), installed_headers: ArrayList(*Step), -is_linking_libc: bool, -is_linking_libcpp: bool, vcpkg_bin_path: ?[]const u8 = null, // keep in sync with src/Compilation.zig:RcIncludes @@ -110,7 +84,6 @@ image_base: ?u64 = null, libc_file: ?LazyPath = null, -valgrind_support: ?bool = null, each_lib_rpath: ?bool = null, /// On ELF targets, this will emit a link section called ".note.gnu.build-id" /// which can be used to coordinate a stripped binary with its debug symbols. @@ -176,15 +149,9 @@ headerpad_max_install_names: bool = false, /// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. dead_strip_dylibs: bool = false, -/// Position Independent Code -force_pic: ?bool = null, - /// Position Independent Executable pie: ?bool = null, -red_zone: ?bool = null, - -omit_frame_pointer: ?bool = null, dll_export_fns: ?bool = null, subsystem: ?std.Target.SubSystem = null, @@ -225,90 +192,16 @@ generated_h: ?*GeneratedFile, /// Defaults to `std.math.maxInt(u16)` error_limit: ?u32 = null, +/// Computed during make(). +is_linking_libc: bool = false, +/// Computed during make(). +is_linking_libcpp: bool = false, + pub const ExpectedCompileErrors = union(enum) { contains: []const u8, exact: []const []const u8, }; -pub const CSourceFiles = struct { - dependency: ?*std.Build.Dependency, - /// If `dependency` is not null relative to it, - /// else relative to the build root. - files: []const []const u8, - flags: []const []const u8, -}; - -pub const CSourceFile = struct { - file: LazyPath, - flags: []const []const u8, - - pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { - return .{ - .file = self.file.dupe(b), - .flags = b.dupeStrings(self.flags), - }; - } -}; - -pub const RcSourceFile = struct { - file: LazyPath, - /// Any option that rc.exe accepts will work here, with the exception of: - /// - `/fo`: The output filename is set by the build system - /// - `/p`: Only running the preprocessor is not supported in this context - /// - `/:no-preprocess` (non-standard option): Not supported in this context - /// - Any MUI-related option - /// https://learn.microsoft.com/en-us/windows/win32/menurc/using-rc-the-rc-command-line- - /// - /// Implicitly defined options: - /// /x (ignore the INCLUDE environment variable) - /// /D_DEBUG or /DNDEBUG depending on the optimization mode - flags: []const []const u8 = &.{}, - - pub fn dupe(self: RcSourceFile, b: *std.Build) RcSourceFile { - return .{ - .file = self.file.dupe(b), - .flags = b.dupeStrings(self.flags), - }; - } -}; - -pub const LinkObject = union(enum) { - static_path: LazyPath, - other_step: *Compile, - system_lib: SystemLib, - assembly_file: LazyPath, - c_source_file: *CSourceFile, - c_source_files: *CSourceFiles, - win32_resource_file: *RcSourceFile, -}; - -pub const SystemLib = struct { - name: []const u8, - needed: bool, - weak: bool, - use_pkg_config: UsePkgConfig, - preferred_link_mode: std.builtin.LinkMode, - search_strategy: SystemLib.SearchStrategy, - - pub const UsePkgConfig = enum { - /// Don't use pkg-config, just pass -lfoo where foo is name. - no, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, fall back to passing -lfoo where foo is name. - yes, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, error out. - force, - }; - - pub const SearchStrategy = enum { paths_first, mode_first, no_fallback }; -}; - -const FrameworkLinkInfo = struct { - needed: bool = false, - weak: bool = false, -}; - const Entry = union(enum) { /// Let the compiler decide whether to make an entry point and what to name /// it. @@ -321,42 +214,24 @@ const Entry = union(enum) { symbol_name: []const u8, }; -pub const IncludeDir = union(enum) { - path: LazyPath, - path_system: LazyPath, - path_after: LazyPath, - framework_path: LazyPath, - framework_path_system: LazyPath, - other_step: *Compile, - config_header_step: *Step.ConfigHeader, -}; - pub const Options = struct { name: []const u8, - root_source_file: ?LazyPath = null, - target: CrossTarget, - optimize: std.builtin.OptimizeMode, + root_module: Module.CreateOptions, kind: Kind, linkage: ?Linkage = null, version: ?std.SemanticVersion = null, max_rss: usize = 0, filter: ?[]const u8 = null, test_runner: ?[]const u8 = null, - link_libc: ?bool = null, - single_threaded: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, - main_mod_path: ?LazyPath = null, /// Embed a `.manifest` file in the compilation if the object format supports it. /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference /// Manifest files must have the extension `.manifest`. /// Can be set regardless of target. The `.manifest` file will be ignored /// if the target object format does not support embedded manifests. win32_manifest: ?LazyPath = null, - - /// deprecated; use `main_mod_path`. - main_pkg_path: ?LazyPath = null, }; pub const BuildId = union(enum) { @@ -446,7 +321,6 @@ pub const Linkage = enum { dynamic, static }; pub fn create(owner: *std.Build, options: Options) *Compile { const name = owner.dupe(options.name); - const root_src: ?LazyPath = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null; if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); } @@ -465,11 +339,12 @@ pub fn create(owner: *std.Build, options: Options) *Compile { .@"test" => "zig test", }, name_adjusted, - @tagName(options.optimize), - options.target.zigTriple(owner.allocator) catch @panic("OOM"), + @tagName(options.root_module.optimize), + options.root_module.target.zigTriple(owner.allocator) catch @panic("OOM"), }); - const target_info = NativeTargetInfo.detect(options.target) catch @panic("unhandled error"); + const target_info = NativeTargetInfo.detect(options.root_module.target) catch + @panic("unhandled error"); const out_filename = std.zig.binNameAlloc(owner.allocator, .{ .root_name = name, @@ -488,17 +363,12 @@ pub fn create(owner: *std.Build, options: Options) *Compile { const self = owner.allocator.create(Compile) catch @panic("OOM"); self.* = .{ - .strip = null, - .unwind_tables = null, + .root_module = Module.init(owner, options.root_module, self), .verbose_link = false, .verbose_cc = false, - .optimize = options.optimize, - .target = options.target, .linkage = options.linkage, .kind = options.kind, - .root_src = root_src, .name = name, - .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator), .step = Step.init(.{ .id = base_id, .name = step_name, @@ -511,23 +381,12 @@ pub fn create(owner: *std.Build, options: Options) *Compile { .out_lib_filename = undefined, .major_only_filename = null, .name_only_filename = null, - .modules = std.StringArrayHashMap(*Module).init(owner.allocator), - .include_dirs = ArrayList(IncludeDir).init(owner.allocator), - .link_objects = ArrayList(LinkObject).init(owner.allocator), - .c_macros = ArrayList([]const u8).init(owner.allocator), - .lib_paths = ArrayList(LazyPath).init(owner.allocator), - .rpaths = ArrayList(LazyPath).init(owner.allocator), .installed_headers = ArrayList(*Step).init(owner.allocator), - .c_std = std.Build.CStd.C99, .zig_lib_dir = null, - .main_mod_path = null, .exec_cmd_args = null, .filter = options.filter, .test_runner = options.test_runner, .test_server_mode = options.test_runner == null, - .disable_stack_probing = false, - .disable_sanitize_c = false, - .sanitize_thread = false, .rdynamic = false, .installed_path = null, .force_undefined_symbols = StringHashMap(void).init(owner.allocator), @@ -542,11 +401,6 @@ pub fn create(owner: *std.Build, options: Options) *Compile { .generated_llvm_ir = null, .generated_h = null, - .target_info = target_info, - - .is_linking_libc = options.link_libc orelse false, - .is_linking_libcpp = false, - .single_threaded = options.single_threaded, .use_llvm = options.use_llvm, .use_lld = options.use_lld, }; @@ -556,14 +410,9 @@ pub fn create(owner: *std.Build, options: Options) *Compile { lp.addStepDependencies(&self.step); } - if (options.main_mod_path orelse options.main_pkg_path) |lp| { - self.main_mod_path = lp.dupe(self.step.owner); - lp.addStepDependencies(&self.step); - } - // Only the PE/COFF format has a Resource Table which is where the manifest // gets embedded, so for any other target the manifest file is just ignored. - if (self.target.getObjectFormat() == .coff) { + if (target_info.target.ofmt == .coff) { if (options.win32_manifest) |lp| { self.win32_manifest = lp.dupe(self.step.owner); lp.addStepDependencies(&self.step); @@ -599,8 +448,6 @@ pub fn create(owner: *std.Build, options: Options) *Compile { } } - if (root_src) |rs| rs.addStepDependencies(&self.step); - return self; } @@ -737,19 +584,30 @@ pub fn linkFrameworkWeak(self: *Compile, framework_name: []const u8) void { } /// Returns whether the library, executable, or object depends on a particular system library. -pub fn dependsOnSystemLibrary(self: Compile, name: []const u8) bool { - if (isLibCLibrary(name)) { - return self.is_linking_libc; +pub fn dependsOnSystemLibrary(self: *const Compile, name: []const u8) bool { + var is_linking_libc = false; + var is_linking_libcpp = false; + + var it = self.root_module.iterateDependencies(self); + while (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, + } + } + is_linking_libc = is_linking_libc or module.link_libcpp == true; + is_linking_libcpp = is_linking_libcpp or module.link_libcpp == true; } - if (isLibCppLibrary(name)) { - return self.is_linking_libcpp; + + if (self.root_module.target_info.target.is_libc_lib_name(name)) { + return is_linking_libc; } - for (self.link_objects.items) |link_object| { - switch (link_object) { - .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, - else => continue, - } + + if (self.root_module.target_info.target.is_libcpp_lib_name(name)) { + return is_linking_libcpp; } + return false; } @@ -758,11 +616,11 @@ pub fn linkLibrary(self: *Compile, lib: *Compile) void { self.linkLibraryOrObject(lib); } -pub fn isDynamicLibrary(self: *Compile) bool { +pub fn isDynamicLibrary(self: *const Compile) bool { return self.kind == .lib and self.linkage == Linkage.dynamic; } -pub fn isStaticLibrary(self: *Compile) bool { +pub fn isStaticLibrary(self: *const Compile) bool { return self.kind == .lib and self.linkage != Linkage.dynamic; } @@ -776,15 +634,15 @@ pub fn producesPdbFile(self: *Compile) bool { } pub fn producesImplib(self: *Compile) bool { - return self.isDynamicLibrary() and self.target.isWindows(); + return self.isDynamicLibrary() and self.root_module.target_info.target.os.tag == .windows; } pub fn linkLibC(self: *Compile) void { - self.is_linking_libc = true; + self.root_module.link_libc = true; } pub fn linkLibCpp(self: *Compile) void { - self.is_linking_libcpp = true; + self.root_module.link_libcpp = true; } /// If the value is omitted, it is set to 1. @@ -801,31 +659,6 @@ pub fn defineCMacroRaw(self: *Compile, name_and_value: []const u8) void { self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); } -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryName(self: *Compile, name: []const u8) void { - return linkSystemLibrary2(self, name, .{ .use_pkg_config = .no }); -} - -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryNeededName(self: *Compile, name: []const u8) void { - return linkSystemLibrary2(self, name, .{ .needed = true, .use_pkg_config = .no }); -} - -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryWeakName(self: *Compile, name: []const u8) void { - return linkSystemLibrary2(self, name, .{ .weak = true, .use_pkg_config = .no }); -} - -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryPkgConfigOnly(self: *Compile, lib_name: []const u8) void { - return linkSystemLibrary2(self, lib_name, .{ .use_pkg_config = .force }); -} - -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryNeededPkgConfigOnly(self: *Compile, lib_name: []const u8) void { - return linkSystemLibrary2(self, lib_name, .{ .needed = true, .use_pkg_config = .force }); -} - /// Run pkg-config for the given library name and parse the output, returning the arguments /// that should be passed to zig to link the given library. fn runPkgConfig(self: *Compile, lib_name: []const u8) ![]const []const u8 { @@ -923,98 +756,31 @@ fn runPkgConfig(self: *Compile, lib_name: []const u8) ![]const []const u8 { } pub fn linkSystemLibrary(self: *Compile, name: []const u8) void { - self.linkSystemLibrary2(name, .{}); -} - -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryNeeded(self: *Compile, name: []const u8) void { - return linkSystemLibrary2(self, name, .{ .needed = true }); + return self.root_module.linkSystemLibrary(name, .{}); } -/// deprecated: use linkSystemLibrary2 -pub fn linkSystemLibraryWeak(self: *Compile, name: []const u8) void { - return linkSystemLibrary2(self, name, .{ .weak = true }); -} - -pub const LinkSystemLibraryOptions = struct { - needed: bool = false, - weak: bool = false, - use_pkg_config: SystemLib.UsePkgConfig = .yes, - preferred_link_mode: std.builtin.LinkMode = .Dynamic, - search_strategy: SystemLib.SearchStrategy = .paths_first, -}; - pub fn linkSystemLibrary2( self: *Compile, name: []const u8, - options: LinkSystemLibraryOptions, + options: Module.LinkSystemLibraryOptions, ) void { - const b = self.step.owner; - if (isLibCLibrary(name)) { - self.linkLibC(); - return; - } - if (isLibCppLibrary(name)) { - self.linkLibCpp(); - return; - } - - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = options.needed, - .weak = options.weak, - .use_pkg_config = options.use_pkg_config, - .preferred_link_mode = options.preferred_link_mode, - .search_strategy = options.search_strategy, - }, - }) catch @panic("OOM"); + return self.root_module.linkSystemLibrary(name, options); } -pub const AddCSourceFilesOptions = struct { - /// When provided, `files` are relative to `dependency` rather than the package that owns the `Compile` step. - dependency: ?*std.Build.Dependency = null, - files: []const []const u8, - flags: []const []const u8 = &.{}, -}; - /// Handy when you have many C/C++ source files and want them all to have the same flags. -pub fn addCSourceFiles(self: *Compile, options: AddCSourceFilesOptions) void { - const b = self.step.owner; - const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM"); - - const files_copy = b.dupeStrings(options.files); - const flags_copy = b.dupeStrings(options.flags); - - c_source_files.* = .{ - .dependency = options.dependency, - .files = files_copy, - .flags = flags_copy, - }; - self.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM"); +pub fn addCSourceFiles(self: *Compile, options: Module.AddCSourceFilesOptions) void { + self.root_module.addCSourceFiles(options); } -pub fn addCSourceFile(self: *Compile, source: CSourceFile) void { - const b = self.step.owner; - const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM"); - c_source_file.* = source.dupe(b); - self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); - source.file.addStepDependencies(&self.step); +pub fn addCSourceFile(self: *Compile, source: Module.CSourceFile) void { + self.root_module.addCSourceFile(source); } /// Resource files must have the extension `.rc`. /// Can be called regardless of target. The .rc file will be ignored /// if the target object format does not support embedded resources. -pub fn addWin32ResourceFile(self: *Compile, source: RcSourceFile) void { - // Only the PE/COFF format has a Resource Table, so for any other target - // the resource file is just ignored. - if (self.target.getObjectFormat() != .coff) return; - - const b = self.step.owner; - const rc_source_file = b.allocator.create(RcSourceFile) catch @panic("OOM"); - rc_source_file.* = source.dupe(b); - self.link_objects.append(.{ .win32_resource_file = rc_source_file }) catch @panic("OOM"); - source.file.addStepDependencies(&self.step); +pub fn addWin32ResourceFile(self: *Compile, source: Module.RcSourceFile) void { + self.root_module.addWin32ResourceFile(source); } pub fn setVerboseLink(self: *Compile, value: bool) void { @@ -1111,16 +877,11 @@ pub fn getEmittedLlvmBc(self: *Compile) LazyPath { } pub fn addAssemblyFile(self: *Compile, source: LazyPath) void { - const b = self.step.owner; - const source_duped = source.dupe(b); - self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM"); - source_duped.addStepDependencies(&self.step); + self.root_module.addAssemblyFile(source); } pub fn addObjectFile(self: *Compile, source: LazyPath) void { - const b = self.step.owner; - self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM"); - source.addStepDependencies(&self.step); + self.root_module.addObjectFile(source); } pub fn addObject(self: *Compile, obj: *Compile) void { @@ -1130,19 +891,19 @@ pub fn addObject(self: *Compile, obj: *Compile) void { pub fn addAfterIncludePath(self: *Compile, path: LazyPath) void { const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .path_after = path.dupe(b) }) catch @panic("OOM"); + self.include_dirs.append(.{ .path_after = path.dupe(b) }) catch @panic("OOM"); path.addStepDependencies(&self.step); } pub fn addSystemIncludePath(self: *Compile, path: LazyPath) void { const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .path_system = path.dupe(b) }) catch @panic("OOM"); + self.include_dirs.append(.{ .path_system = path.dupe(b) }) catch @panic("OOM"); path.addStepDependencies(&self.step); } pub fn addIncludePath(self: *Compile, path: LazyPath) void { const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .path = path.dupe(b) }) catch @panic("OOM"); + self.include_dirs.append(.{ .path = path.dupe(b) }) catch @panic("OOM"); path.addStepDependencies(&self.step); } @@ -1165,48 +926,16 @@ pub fn addRPath(self: *Compile, directory_source: LazyPath) void { pub fn addSystemFrameworkPath(self: *Compile, directory_source: LazyPath) void { const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .framework_path_system = directory_source.dupe(b) }) catch @panic("OOM"); + self.include_dirs.append(.{ .framework_path_system = directory_source.dupe(b) }) catch @panic("OOM"); directory_source.addStepDependencies(&self.step); } pub fn addFrameworkPath(self: *Compile, directory_source: LazyPath) void { const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .framework_path = directory_source.dupe(b) }) catch @panic("OOM"); + self.include_dirs.append(.{ .framework_path = directory_source.dupe(b) }) catch @panic("OOM"); directory_source.addStepDependencies(&self.step); } -/// Adds a module to be used with `@import` and exposing it in the current -/// package's module table using `name`. -pub fn addModule(cs: *Compile, name: []const u8, module: *Module) void { - const b = cs.step.owner; - cs.modules.put(b.dupe(name), module) catch @panic("OOM"); - - var done = std.AutoHashMap(*Module, void).init(b.allocator); - defer done.deinit(); - cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM"); -} - -/// Adds a module to be used with `@import` without exposing it in the current -/// package's module table. -pub fn addAnonymousModule(cs: *Compile, name: []const u8, options: std.Build.CreateModuleOptions) void { - const b = cs.step.owner; - const module = b.createModule(options); - return addModule(cs, name, module); -} - -pub fn addOptions(cs: *Compile, module_name: []const u8, options: *Step.Options) void { - addModule(cs, module_name, options.createModule()); -} - -fn addRecursiveBuildDeps(cs: *Compile, module: *Module, done: *std.AutoHashMap(*Module, void)) !void { - if (done.contains(module)) return; - try done.put(module, {}); - module.source_file.addStepDependencies(&cs.step); - for (module.dependencies.values()) |dep| { - try cs.addRecursiveBuildDeps(dep, done); - } -} - /// If Vcpkg was found on the system, it will be added to include and lib /// paths for the specified target. pub fn addVcpkgPaths(self: *Compile, linkage: Compile.Linkage) !void { @@ -1235,7 +964,7 @@ pub fn addVcpkgPaths(self: *Compile, linkage: Compile.Linkage) !void { const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" }); errdefer allocator.free(include_path); - try self.include_dirs.append(IncludeDir{ .path = .{ .path = include_path } }); + try self.include_dirs.append(.{ .path = .{ .path = include_path } }); const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); try self.lib_paths.append(.{ .path = lib_path }); @@ -1269,87 +998,53 @@ fn linkLibraryOrObject(self: *Compile, other: *Compile) void { } } -fn appendModuleArgs( - cs: *Compile, - zig_args: *ArrayList([]const u8), -) error{OutOfMemory}!void { +fn appendModuleArgs(cs: *Compile, zig_args: *ArrayList([]const u8)) !void { const b = cs.step.owner; - // First, traverse the whole dependency graph and give every module a unique name, ideally one - // named after what it's called somewhere in the graph. It will help here to have both a mapping - // from module to name and a set of all the currently-used names. - var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator); + // First, traverse the whole dependency graph and give every module a + // unique name, ideally one named after what it's called somewhere in the + // graph. It will help here to have both a mapping from module to name and + // a set of all the currently-used names. + var mod_names: std.AutoArrayHashMapUnmanaged(*Module, []const u8) = .{}; var names = std.StringHashMap(void).init(b.allocator); - var to_name = std.ArrayList(struct { - name: []const u8, - mod: *Module, - }).init(b.allocator); { - var it = cs.modules.iterator(); - while (it.next()) |kv| { + var it = cs.root_module.iterateDependencies(null); + _ = it.next(); // Skip over the root module. + while (it.next()) |item| { // While we're traversing the root dependencies, let's make sure that no module names // have colons in them, since the CLI forbids it. We handle this for transitive // dependencies further down. - if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { - @panic("Module names cannot contain colons"); + if (std.mem.indexOfScalar(u8, item.name, ':') != null) { + return cs.step.fail("module '{s}' contains a colon", .{item.name}); } - try to_name.append(.{ - .name = kv.key_ptr.*, - .mod = kv.value_ptr.*, - }); - } - } - - while (to_name.popOrNull()) |dep| { - if (mod_names.contains(dep.mod)) continue; - // We'll use this buffer to store the name we decide on - var buf = try b.allocator.alloc(u8, dep.name.len + 32); - // First, try just the exposed dependency name - @memcpy(buf[0..dep.name.len], dep.name); - var name = buf[0..dep.name.len]; - var n: usize = 0; - while (names.contains(name)) { - // If that failed, append an incrementing number to the end - name = std.fmt.bufPrint(buf, "{s}{}", .{ dep.name, n }) catch unreachable; - n += 1; - } - - try mod_names.put(dep.mod, name); - try names.put(name, {}); - - var it = dep.mod.dependencies.iterator(); - while (it.next()) |kv| { - // Same colon-in-name check as above, but for transitive dependencies. - if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { - @panic("Module names cannot contain colons"); + var name = item.name; + var n: usize = 0; + while (names.contains(name)) { + name = b.fmt("{s}{d}", .{ item.name, n }); + n += 1; } - try to_name.append(.{ - .name = kv.key_ptr.*, - .mod = kv.value_ptr.*, - }); + + try mod_names.put(b.allocator, item.module, name); + try names.put(name, {}); } } - // Since the module names given to the CLI are based off of the exposed names, we already know - // that none of the CLI names have colons in them, so there's no need to check that explicitly. + // Since the module names given to the CLI are based off of the exposed + // names, we already know that none of the CLI names have colons in them, + // so there's no need to check that explicitly. // Every module in the graph is now named; output their definitions - { - var it = mod_names.iterator(); - while (it.next()) |kv| { - const mod = kv.key_ptr.*; - const name = kv.value_ptr.*; - - const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies); - const src = mod.source_file.getPath(mod.builder); - try zig_args.append("--mod"); - try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); - } + for (mod_names.keys(), mod_names.values()) |mod, name| { + const root_src = mod.root_source_file orelse continue; + const deps_str = try constructDepString(b.allocator, mod_names, mod.import_table); + const src = root_src.getPath2(mod.owner, &cs.step); + try zig_args.append("--mod"); + try zig_args.append(b.fmt("{s}:{s}:{s}", .{ name, deps_str, src })); } // Lastly, output the root dependencies - const deps_str = try constructDepString(b.allocator, mod_names, cs.modules); + const deps_str = try constructDepString(b.allocator, mod_names, cs.root_module.import_table); if (deps_str.len > 0) { try zig_args.append("--deps"); try zig_args.append(deps_str); @@ -1358,7 +1053,7 @@ fn appendModuleArgs( fn constructDepString( allocator: std.mem.Allocator, - mod_names: std.AutoHashMap(*Module, []const u8), + mod_names: std.AutoArrayHashMapUnmanaged(*Module, []const u8), deps: std.StringArrayHashMap(*Module), ) ![]const u8 { var deps_str = std.ArrayList(u8).init(allocator); @@ -1407,10 +1102,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const b = step.owner; const self = @fieldParentPtr(Compile, "step", step); - if (self.root_src == null and self.link_objects.items.len == 0) { - return step.fail("the linker needs one or more objects to link", .{}); - } - var zig_args = ArrayList([]const u8).init(b.allocator); defer zig_args.deinit(); @@ -1431,7 +1122,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try addFlag(&zig_args, "llvm", self.use_llvm); try addFlag(&zig_args, "lld", self.use_lld); - if (self.target.ofmt) |ofmt| { + if (self.root_module.target.ofmt) |ofmt| { try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); } @@ -1457,204 +1148,248 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); } - if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b)); + { + var seen_system_libs: std.StringHashMapUnmanaged(void) = .{}; + var frameworks: std.StringArrayHashMapUnmanaged(Module.FrameworkLinkInfo) = .{}; + + var prev_has_cflags = false; + var prev_has_rcflags = false; + var prev_search_strategy: Module.SystemLib.SearchStrategy = .paths_first; + var prev_preferred_link_mode: std.builtin.LinkMode = .Dynamic; + // Track the number of positional arguments so that a nice error can be + // emitted if there is nothing to link. + var total_linker_objects: usize = 0; + + if (self.root_module.root_source_file) |lp| { + try zig_args.append(lp.getPath(b)); + total_linker_objects += 1; + } - // We will add link objects from transitive dependencies, but we want to keep - // all link objects in the same order provided. - // This array is used to keep self.link_objects immutable. - var transitive_deps: TransitiveDeps = .{ - .link_objects = ArrayList(LinkObject).init(b.allocator), - .seen_system_libs = StringHashMap(void).init(b.allocator), - .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator), - .is_linking_libcpp = self.is_linking_libcpp, - .is_linking_libc = self.is_linking_libc, - .frameworks = &self.frameworks, - }; + try self.root_module.appendZigProcessFlags(&zig_args, step); - try transitive_deps.seen_steps.put(&self.step, {}); - try transitive_deps.add(self.link_objects.items); - - var prev_has_cflags = false; - var prev_has_rcflags = false; - var prev_search_strategy: SystemLib.SearchStrategy = .paths_first; - var prev_preferred_link_mode: std.builtin.LinkMode = .Dynamic; - - for (transitive_deps.link_objects.items) |link_object| { - switch (link_object) { - .static_path => |static_path| try zig_args.append(static_path.getPath(b)), - - .other_step => |other| switch (other.kind) { - .exe => @panic("Cannot link with an executable build artifact"), - .@"test" => @panic("Cannot link with a test"), - .obj => { - try zig_args.append(other.getEmittedBin().getPath(b)); - }, - .lib => l: { - if (self.isStaticLibrary() and other.isStaticLibrary()) { - // Avoid putting a static library inside a static library. - break :l; - } + var it = self.root_module.iterateDependencies(self); + while (it.next()) |key| { + const module = key.module; + const compile = key.compile.?; + const dyn = compile.isDynamicLibrary(); - // For DLLs, we gotta link against the implib. For - // everything else, we directly link against the library file. - const full_path_lib = if (other.producesImplib()) - other.getGeneratedFilePath("generated_implib", &self.step) - else - other.getGeneratedFilePath("generated_bin", &self.step); - try zig_args.append(full_path_lib); - - if (other.linkage == Linkage.dynamic and !self.target.isWindows()) { - if (fs.path.dirname(full_path_lib)) |dirname| { - try zig_args.append("-rpath"); - try zig_args.append(dirname); - } - } - }, - }, + // Inherit dependency on libc and libc++. + if (module.link_libc == true) self.is_linking_libc = true; + if (module.link_libcpp == true) self.is_linking_libcpp = true; - .system_lib => |system_lib| { - if ((system_lib.search_strategy != prev_search_strategy or - system_lib.preferred_link_mode != prev_preferred_link_mode) and - self.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; + // Inherit dependencies on darwin frameworks. + if (!dyn) { + for (module.frameworks.keys(), module.frameworks.values()) |name, info| { + try frameworks.put(b.allocator, name, info); } + } - 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 (self.runPkgConfig(system_lib.name)) |args| { - try zig_args.appendSlice(args); - } 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, - })); + // Inherit dependencies on system libraries and static libraries. + total_linker_objects += module.link_objects.items.len; + for (module.link_objects.items) |link_object| { + switch (link_object) { + .static_path => |static_path| try zig_args.append(static_path.getPath(b)), + .system_lib => |system_lib| { + if ((try seen_system_libs.fetchPut(b.allocator, system_lib.name, {})) != null) + continue; + + if (dyn) + continue; + + if ((system_lib.search_strategy != prev_search_strategy or + system_lib.preferred_link_mode != prev_preferred_link_mode) and + self.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"), }, - .force => { - panic("pkg-config failed for library {s}", .{system_lib.name}); + .mode_first => switch (system_lib.preferred_link_mode) { + .Dynamic => try zig_args.append("-search_dylibs_first"), + .Static => try zig_args.append("-search_static_first"), }, - .no => unreachable, + } + 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 (self.runPkgConfig(system_lib.name)) |args| { + try zig_args.appendSlice(args); + } 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| { + const included_in_lib = (compile.kind == .lib and other.kind == .obj); + if (dyn or included_in_lib) + continue; + + switch (other.kind) { + .exe => return step.fail("cannot link with an executable build artifact", .{}), + .@"test" => return step.fail("cannot link with a test", .{}), + .obj => { + try zig_args.append(other.getEmittedBin().getPath(b)); + }, + .lib => l: { + if (self.isStaticLibrary() and other.isStaticLibrary()) { + // Avoid putting a static library inside a static library. + break :l; + } + + // For DLLs, we gotta link against the implib. For + // everything else, we directly link against the library file. + const full_path_lib = if (other.producesImplib()) + other.getGeneratedFilePath("generated_implib", &self.step) + else + other.getGeneratedFilePath("generated_bin", &self.step); + try zig_args.append(full_path_lib); + + if (other.linkage == Linkage.dynamic and + self.root_module.target_info.target.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| { + if (prev_has_cflags) { + try zig_args.append("-cflags"); + try zig_args.append("--"); + prev_has_cflags = false; + } + try zig_args.append(asm_file.getPath(b)); + }, - else => |e| return e, + .c_source_file => |c_source_file| { + 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.getPath(b)); }, - } - }, - .assembly_file => |asm_file| { - if (prev_has_cflags) { - try zig_args.append("-cflags"); - try zig_args.append("--"); - prev_has_cflags = false; - } - try zig_args.append(asm_file.getPath(b)); - }, + .c_source_files => |c_source_files| { + 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; + } + if (c_source_files.dependency) |dep| { + for (c_source_files.files) |file| { + try zig_args.append(dep.builder.pathFromRoot(file)); + } + } else { + for (c_source_files.files) |file| { + try zig_args.append(b.pathFromRoot(file)); + } + } + }, - .c_source_file => |c_source_file| { - 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; + .win32_resource_file => |rc_source_file| { + if (rc_source_file.flags.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); + } + try zig_args.append("--"); + prev_has_rcflags = true; + } + try zig_args.append(rc_source_file.file.getPath(b)); + }, } - try zig_args.append(c_source_file.file.getPath(b)); - }, + } + } - .c_source_files => |c_source_files| { - 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; - } - if (c_source_files.dependency) |dep| { - for (c_source_files.files) |file| { - try zig_args.append(dep.builder.pathFromRoot(file)); - } - } else { - for (c_source_files.files) |file| { - try zig_args.append(b.pathFromRoot(file)); - } - } - }, + if (total_linker_objects == 0) { + return step.fail("the linker needs one or more objects to link", .{}); + } - .win32_resource_file => |rc_source_file| { - if (rc_source_file.flags.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); - } - try zig_args.append("--"); - prev_has_rcflags = true; - } - try zig_args.append(rc_source_file.file.getPath(b)); - }, + for (frameworks.keys(), frameworks.values()) |name, info| { + if (info.needed) { + try zig_args.append("-needed_framework"); + } else if (info.weak) { + try zig_args.append("-weak_framework"); + } else { + try zig_args.append("-framework"); + } + try zig_args.append(name); } - } - if (self.win32_manifest) |manifest_file| { - try zig_args.append(manifest_file.getPath(b)); - } + if (self.is_linking_libcpp) { + try zig_args.append("-lc++"); + } - if (transitive_deps.is_linking_libcpp) { - try zig_args.append("-lc++"); + if (self.is_linking_libc) { + try zig_args.append("-lc"); + } } - if (transitive_deps.is_linking_libc) { - try zig_args.append("-lc"); + if (self.win32_manifest) |manifest_file| { + try zig_args.append(manifest_file.getPath(b)); } if (self.image_base) |image_base| { @@ -1701,16 +1436,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.generated_llvm_ir != null) try zig_args.append("-femit-llvm-ir"); if (self.generated_h != null) try zig_args.append("-femit-h"); - try addFlag(&zig_args, "strip", self.strip); - try addFlag(&zig_args, "unwind-tables", self.unwind_tables); - - if (self.dwarf_format) |dwarf_format| { - try zig_args.append(switch (dwarf_format) { - .@"32" => "-gdwarf32", - .@"64" => "-gdwarf64", - }); - } - switch (self.compress_debug_sections) { .none => {}, .zlib => try zig_args.append("--compress-debug-sections=zlib"), @@ -1767,11 +1492,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(libc_file); } - switch (self.optimize) { - .Debug => {}, // Skip since it's the default. - else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), - } - try zig_args.append("--cache-dir"); try zig_args.append(b.cache_root.path orelse "."); @@ -1791,11 +1511,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(b.fmt("{}", .{version})); } - if (self.target.isDarwin()) { + if (self.root_module.target_info.target.isDarwin()) { const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ - self.target.libPrefix(), + self.root_module.target_info.target.libPrefix(), self.name, - self.target.dynamicLibSuffix(), + self.root_module.target_info.target.dynamicLibSuffix(), }); try zig_args.append("-install_name"); try zig_args.append(install_name); @@ -1821,27 +1541,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); - try addFlag(&zig_args, "single-threaded", self.single_threaded); - if (self.disable_stack_probing) { - try zig_args.append("-fno-stack-check"); - } - try addFlag(&zig_args, "stack-protector", self.stack_protector); - if (self.red_zone) |red_zone| { - if (red_zone) { - try zig_args.append("-mred-zone"); - } else { - try zig_args.append("-mno-red-zone"); - } - } - try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); - - if (self.disable_sanitize_c) { - try zig_args.append("-fno-sanitize-c"); - } - if (self.sanitize_thread) { - try zig_args.append("-fsanitize-thread"); - } if (self.rdynamic) { try zig_args.append("-rdynamic"); } @@ -1873,29 +1573,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(b.fmt("--global-base={d}", .{global_base})); } - if (self.code_model != .default) { - try zig_args.append("-mcmodel"); - try zig_args.append(@tagName(self.code_model)); - } if (self.wasi_exec_model) |model| { try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); } - for (self.export_symbol_names) |symbol_name| { - try zig_args.append(b.fmt("--export={s}", .{symbol_name})); - } - - if (!self.target.isNative()) { - try zig_args.appendSlice(&.{ - "-target", try self.target.zigTriple(b.allocator), - "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()), - }); - - if (self.target.dynamic_linker.get()) |dynamic_linker| { - try zig_args.append("--dynamic-linker"); - try zig_args.append(dynamic_linker); - } - } - if (self.linker_script) |linker_script| { try zig_args.append("--script"); try zig_args.append(linker_script.getPath(b)); @@ -1921,97 +1601,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try self.appendModuleArgs(&zig_args); - for (self.include_dirs.items) |include_dir| { - switch (include_dir) { - .path => |include_path| { - try zig_args.append("-I"); - try zig_args.append(include_path.getPath(b)); - }, - .path_system => |include_path| { - try zig_args.append("-isystem"); - try zig_args.append(include_path.getPath(b)); - }, - .path_after => |include_path| { - try zig_args.append("-idirafter"); - try zig_args.append(include_path.getPath(b)); - }, - .framework_path => |include_path| { - try zig_args.append("-F"); - try zig_args.append(include_path.getPath2(b, step)); - }, - .framework_path_system => |include_path| { - try zig_args.append("-iframework"); - try zig_args.append(include_path.getPath2(b, step)); - }, - .other_step => |other| { - if (other.generated_h) |header| { - try zig_args.append("-isystem"); - try zig_args.append(fs.path.dirname(header.path.?).?); - } - if (other.installed_headers.items.len > 0) { - try zig_args.append("-I"); - try zig_args.append(b.pathJoin(&.{ - other.step.owner.install_prefix, "include", - })); - } - }, - .config_header_step => |config_header| { - const full_file_path = config_header.output_file.path.?; - const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len]; - try zig_args.appendSlice(&.{ "-I", header_dir_path }); - }, - } - } - - for (self.c_macros.items) |c_macro| { - try zig_args.append("-D"); - try zig_args.append(c_macro); - } - - try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len); - for (self.lib_paths.items) |lib_path| { - zig_args.appendAssumeCapacity("-L"); - zig_args.appendAssumeCapacity(lib_path.getPath2(b, step)); - } - - try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len); - for (self.rpaths.items) |rpath| { - zig_args.appendAssumeCapacity("-rpath"); - - if (self.target_info.target.isDarwin()) switch (rpath) { - .path, .cwd_relative => |path| { - // On Darwin, we should not try to expand special runtime paths such as - // * @executable_path - // * @loader_path - if (mem.startsWith(u8, path, "@executable_path") or - mem.startsWith(u8, path, "@loader_path")) - { - zig_args.appendAssumeCapacity(path); - continue; - } - }, - .generated, .dependency => {}, - }; - - zig_args.appendAssumeCapacity(rpath.getPath2(b, step)); - } - - { - var it = self.frameworks.iterator(); - while (it.next()) |entry| { - const name = entry.key_ptr.*; - const info = entry.value_ptr.*; - if (info.needed) { - try zig_args.append("-needed_framework"); - } else if (info.weak) { - try zig_args.append("-weak_framework"); - } else { - try zig_args.append("-framework"); - } - try zig_args.append(name); - } - } - if (b.sysroot) |sysroot| { try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); } @@ -2056,7 +1645,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(@tagName(self.rc_includes)); } - try addFlag(&zig_args, "valgrind", self.valgrind_support); try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); if (self.build_id) |build_id| { @@ -2073,12 +1661,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(dir.getPath(b)); } - if (self.main_mod_path) |dir| { - try zig_args.append("--main-mod-path"); - try zig_args.append(dir.getPath(b)); - } - - try addFlag(&zig_args, "PIC", self.force_pic); try addFlag(&zig_args, "PIE", self.pie); try addFlag(&zig_args, "lto", self.want_lto); @@ -2221,7 +1803,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and - self.version != null and self.target.wantSharedLibSymLinks()) + self.version != null and self.root_module.target.wantSharedLibSymLinks()) { try doAtomicSymLinks( step, @@ -2232,24 +1814,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } -fn isLibCLibrary(name: []const u8) bool { - const libc_libraries = [_][]const u8{ "c", "m", "dl", "rt", "pthread" }; - for (libc_libraries) |libc_lib_name| { - if (mem.eql(u8, name, libc_lib_name)) - return true; - } - return false; -} - -fn isLibCppLibrary(name: []const u8) bool { - const libcpp_libraries = [_][]const u8{ "c++", "stdc++" }; - for (libcpp_libraries) |libcpp_lib_name| { - if (mem.eql(u8, name, libcpp_lib_name)) - return true; - } - return false; -} - /// Returned slice must be freed by the caller. fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { const appdata_path = try fs.getAppDataDir(allocator, "vcpkg"); @@ -2343,67 +1907,6 @@ fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) } } -const TransitiveDeps = struct { - link_objects: ArrayList(LinkObject), - seen_system_libs: StringHashMap(void), - seen_steps: std.AutoHashMap(*const Step, void), - is_linking_libcpp: bool, - is_linking_libc: bool, - frameworks: *StringHashMap(FrameworkLinkInfo), - - fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { - try td.link_objects.ensureUnusedCapacity(link_objects.len); - - for (link_objects) |link_object| { - try td.link_objects.append(link_object); - switch (link_object) { - .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), - else => {}, - } - } - } - - fn addInner(td: *TransitiveDeps, other: *Compile, dyn: bool) !void { - // Inherit dependency on libc and libc++ - td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; - td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; - - // Inherit dependencies on darwin frameworks - if (!dyn) { - var it = other.frameworks.iterator(); - while (it.next()) |framework| { - try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); - } - } - - // Inherit dependencies on system libraries and static libraries. - for (other.link_objects.items) |other_link_object| { - switch (other_link_object) { - .system_lib => |system_lib| { - if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) - continue; - - if (dyn) - continue; - - try td.link_objects.append(other_link_object); - }, - .other_step => |inner_other| { - if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) - continue; - - const included_in_lib = (other.kind == .lib and inner_other.kind == .obj); - if (!dyn and !included_in_lib) - try td.link_objects.append(other_link_object); - - try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); - }, - else => continue, - } - } - } -}; - fn checkCompileErrors(self: *Compile) !void { // Clear this field so that it does not get printed by the build runner. const actual_eb = self.step.result_error_bundle; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 5555b03e94ef..855eabeae9ba 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -488,7 +488,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { man.hash.addBytes(file_path); }, .artifact => |artifact| { - if (artifact.target.isWindows()) { + if (artifact.root_module.target_info.target.os.tag == .windows) { // On Windows we don't have rpaths so we have to add .dll search paths to PATH self.addPathForDynLibs(artifact); } @@ -682,8 +682,9 @@ fn runCommand( else => break :interpret, } - const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; - switch (b.host.getExternalExecutor(&exe.target_info, .{ + const need_cross_glibc = exe.root_module.target_info.target.isGnuLibC() and + exe.is_linking_libc; + switch (b.host.getExternalExecutor(&exe.root_module.target_info, .{ .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, .link_libc = exe.is_linking_libc, })) { @@ -714,9 +715,9 @@ fn runCommand( // needs the directory to be called "i686" rather than // "x86" which is why we do it manually here. const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; - const cpu_arch = exe.target.getCpuArch(); - const os_tag = exe.target.getOsTag(); - const abi = exe.target.getAbi(); + const cpu_arch = exe.root_module.target_info.target.cpu.arch; + const os_tag = exe.root_module.target_info.target.os.tag; + const abi = exe.root_module.target_info.target.abi; const cpu_arch_name: []const u8 = if (cpu_arch == .x86) "i686" else @@ -769,7 +770,7 @@ fn runCommand( if (allow_skip) return error.MakeSkipped; const host_name = try b.host.target.zigTriple(b.allocator); - const foreign_name = try exe.target.zigTriple(b.allocator); + const foreign_name = try exe.root_module.target_info.target.zigTriple(b.allocator); return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ host_name, foreign_name, @@ -777,7 +778,7 @@ fn runCommand( }, } - if (exe.target.isWindows()) { + if (exe.root_module.target_info.target.os.tag == .windows) { // On Windows we don't have rpaths so we have to add .dll search paths to PATH self.addPathForDynLibs(exe); } @@ -1295,15 +1296,14 @@ fn evalGeneric(self: *Run, child: *std.process.Child) !StdIoResult { fn addPathForDynLibs(self: *Run, artifact: *Step.Compile) void { const b = self.step.owner; - for (artifact.link_objects.items) |link_object| { - switch (link_object) { - .other_step => |other| { - if (other.target.isWindows() and other.isDynamicLibrary()) { - addPathDir(self, fs.path.dirname(other.getEmittedBin().getPath(b)).?); - addPathForDynLibs(self, other); - } - }, - else => {}, + var it = artifact.root_module.iterateDependencies(artifact); + while (it.next()) |item| { + const other = item.compile.?; + if (item.module == &other.root_module) { + if (item.module.target_info.target.os.tag == .windows and other.isDynamicLibrary()) { + addPathDir(self, fs.path.dirname(other.getEmittedBin().getPath(b)).?); + addPathForDynLibs(self, other); + } } } } @@ -1321,7 +1321,7 @@ fn failForeign( const b = self.step.owner; const host_name = try b.host.target.zigTriple(b.allocator); - const foreign_name = try exe.target.zigTriple(b.allocator); + const foreign_name = try exe.root_module.target_info.target.zigTriple(b.allocator); return self.step.fail( \\unable to spawn foreign binary '{s}' ({s}) on host system ({s})