diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index bd98634ba8..7f8a6d3159 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -142,6 +142,9 @@ pub const hasher_init: Hasher = Hasher.init(&[_]u8{ pub const File = struct { prefixed_path: PrefixedPath, max_file_size: ?usize, + /// Populated if the user calls `addOpenedFile`. + /// The handle is not owned here. + handle: ?fs.File, stat: Stat, bin_digest: BinDigest, contents: ?[]const u8, @@ -173,6 +176,11 @@ pub const File = struct { const new = new_max_size orelse return; file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new; } + + pub fn updateHandle(file: *File, new_handle: ?fs.File) void { + const handle = new_handle orelse return; + file.handle = handle; + } }; pub const HashHelper = struct { @@ -363,15 +371,20 @@ pub const Manifest = struct { /// var file_contents = cache_hash.files.keys()[file_index].contents.?; /// ``` pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize { + return addOpenedFile(m, file_path, null, max_file_size); + } + + /// Same as `addFilePath` except the file has already been opened. + pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?fs.File, max_file_size: ?usize) !usize { const gpa = m.cache.gpa; try m.files.ensureUnusedCapacity(gpa, 1); const resolved_path = try fs.path.resolve(gpa, &.{ - file_path.root_dir.path orelse ".", - file_path.subPathOrDot(), + path.root_dir.path orelse ".", + path.subPathOrDot(), }); errdefer gpa.free(resolved_path); const prefixed_path = try m.cache.findPrefixResolved(resolved_path); - return addFileInner(m, prefixed_path, max_file_size); + return addFileInner(m, prefixed_path, handle, max_file_size); } /// Deprecated; use `addFilePath`. @@ -383,13 +396,14 @@ pub const Manifest = struct { const prefixed_path = try self.cache.findPrefix(file_path); errdefer gpa.free(prefixed_path.sub_path); - return addFileInner(self, prefixed_path, max_file_size); + return addFileInner(self, prefixed_path, null, max_file_size); } - fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, max_file_size: ?usize) !usize { + fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?fs.File, max_file_size: ?usize) usize { const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); if (gop.found_existing) { gop.key_ptr.updateMaxSize(max_file_size); + gop.key_ptr.updateHandle(handle); return gop.index; } gop.key_ptr.* = .{ @@ -398,6 +412,7 @@ pub const Manifest = struct { .max_file_size = max_file_size, .stat = undefined, .bin_digest = undefined, + .handle = handle, }; self.hash.add(prefixed_path.prefix); @@ -565,6 +580,7 @@ pub const Manifest = struct { }, .contents = null, .max_file_size = null, + .handle = null, .stat = .{ .size = stat_size, .inode = stat_inode, @@ -708,12 +724,19 @@ pub const Manifest = struct { } fn populateFileHash(self: *Manifest, ch_file: *File) !void { - const pp = ch_file.prefixed_path; - const dir = self.cache.prefixes()[pp.prefix].handle; - const file = try dir.openFile(pp.sub_path, .{}); - defer file.close(); + if (ch_file.handle) |handle| { + return populateFileHashHandle(self, ch_file, handle); + } else { + const pp = ch_file.prefixed_path; + const dir = self.cache.prefixes()[pp.prefix].handle; + const handle = try dir.openFile(pp.sub_path, .{}); + defer handle.close(); + return populateFileHashHandle(self, ch_file, handle); + } + } - const actual_stat = try file.stat(); + fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void { + const actual_stat = try handle.stat(); ch_file.stat = .{ .size = actual_stat.size, .mtime = actual_stat.mtime, @@ -739,8 +762,7 @@ pub const Manifest = struct { var hasher = hasher_init; var off: usize = 0; while (true) { - // give me everything you've got, captain - const bytes_read = try file.read(contents[off..]); + const bytes_read = try handle.pread(contents[off..], off); if (bytes_read == 0) break; hasher.update(contents[off..][0..bytes_read]); off += bytes_read; @@ -749,7 +771,7 @@ pub const Manifest = struct { ch_file.contents = contents; } else { - try hashFile(file, &ch_file.bin_digest); + try hashFile(handle, &ch_file.bin_digest); } self.hash.hasher.update(&ch_file.bin_digest); @@ -813,6 +835,7 @@ pub const Manifest = struct { gop.key_ptr.* = .{ .prefixed_path = prefixed_path, .max_file_size = null, + .handle = null, .stat = undefined, .bin_digest = undefined, .contents = null, @@ -851,6 +874,7 @@ pub const Manifest = struct { new_file.* = .{ .prefixed_path = prefixed_path, .max_file_size = null, + .handle = null, .stat = stat, .bin_digest = undefined, .contents = null, @@ -1067,6 +1091,7 @@ pub const Manifest = struct { gop.key_ptr.* = .{ .prefixed_path = prefixed_path, .max_file_size = file.max_file_size, + .handle = file.handle, .stat = file.stat, .bin_digest = file.bin_digest, .contents = null, @@ -1103,14 +1128,14 @@ pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void { var buf: [1024]u8 = undefined; - var hasher = hasher_init; + var off: u64 = 0; while (true) { - const bytes_read = try file.read(&buf); + const bytes_read = try file.pread(&buf, off); if (bytes_read == 0) break; hasher.update(buf[0..bytes_read]); + off += bytes_read; } - hasher.final(bin_digest); } diff --git a/src/Compilation.zig b/src/Compilation.zig index d4b187915c..06ad1604c6 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -76,12 +76,13 @@ implib_emit: ?Path, docs_emit: ?Path, root_name: [:0]const u8, include_compiler_rt: bool, -objects: []Compilation.LinkObject, +/// Resolved into known paths, any GNU ld scripts already resolved. +link_inputs: []const link.Input, /// Needed only for passing -F args to clang. framework_dirs: []const []const u8, -/// These are *always* dynamically linked. Static libraries will be -/// provided as positional arguments. -system_libs: std.StringArrayHashMapUnmanaged(SystemLib), +/// These are only for DLLs dependencies fulfilled by the `.def` files shipped +/// with Zig. Static libraries are provided as `link.Input` values. +windows_libs: std.StringArrayHashMapUnmanaged(void), version: ?std.SemanticVersion, libc_installation: ?*const LibCInstallation, skip_linker_dependencies: bool, @@ -384,7 +385,7 @@ const Job = union(enum) { /// one of WASI libc static objects wasi_libc_crt_file: wasi_libc.CrtFile, - /// The value is the index into `system_libs`. + /// The value is the index into `windows_libs`. windows_import_lib: usize, const Tag = @typeInfo(Job).@"union".tag_type.?; @@ -999,25 +1000,6 @@ const CacheUse = union(CacheMode) { } }; -pub const LinkObject = struct { - path: Path, - must_link: bool = false, - needed: bool = false, - weak: bool = false, - /// When the library is passed via a positional argument, it will be - /// added as a full path. If it's `-l`, then just the basename. - /// - /// Consistent with `withLOption` variable name in lld ELF driver. - loption: bool = false, - - pub fn isObject(lo: LinkObject) bool { - return switch (classifyFileExt(lo.path.sub_path)) { - .object => true, - else => false, - }; - } -}; - pub const CreateOptions = struct { zig_lib_directory: Directory, local_cache_directory: Directory, @@ -1065,18 +1047,17 @@ pub const CreateOptions = struct { /// This field is intended to be removed. /// The ELF implementation no longer uses this data, however the MachO and COFF /// implementations still do. - lib_dirs: []const []const u8 = &[0][]const u8{}, + lib_directories: []const Directory = &.{}, rpath_list: []const []const u8 = &[0][]const u8{}, symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .empty, c_source_files: []const CSourceFile = &.{}, rc_source_files: []const RcSourceFile = &.{}, manifest_file: ?[]const u8 = null, rc_includes: RcIncludes = .any, - link_objects: []LinkObject = &[0]LinkObject{}, + link_inputs: []const link.Input = &.{}, framework_dirs: []const []const u8 = &[0][]const u8{}, frameworks: []const Framework = &.{}, - system_lib_names: []const []const u8 = &.{}, - system_lib_infos: []const SystemLib = &.{}, + windows_lib_names: []const []const u8 = &.{}, /// These correspond to the WASI libc emulated subcomponents including: /// * process clocks /// * getpid @@ -1459,12 +1440,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; errdefer if (opt_zcu) |zcu| zcu.deinit(); - var system_libs = try std.StringArrayHashMapUnmanaged(SystemLib).init( - gpa, - options.system_lib_names, - options.system_lib_infos, - ); - errdefer system_libs.deinit(gpa); + var windows_libs = try std.StringArrayHashMapUnmanaged(void).init(gpa, options.windows_lib_names, &.{}); + errdefer windows_libs.deinit(gpa); comp.* = .{ .gpa = gpa, @@ -1526,11 +1503,11 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .libcxx_abi_version = options.libcxx_abi_version, .root_name = root_name, .sysroot = sysroot, - .system_libs = system_libs, + .windows_libs = windows_libs, .version = options.version, .libc_installation = libc_dirs.libc_installation, .include_compiler_rt = include_compiler_rt, - .objects = options.link_objects, + .link_inputs = options.link_inputs, .framework_dirs = options.framework_dirs, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, .skip_linker_dependencies = options.skip_linker_dependencies, @@ -1568,7 +1545,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .z_max_page_size = options.linker_z_max_page_size, .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, .frameworks = options.frameworks, - .lib_dirs = options.lib_dirs, + .lib_directories = options.lib_directories, .framework_dirs = options.framework_dirs, .rpath_list = options.rpath_list, .symbol_wrap_set = options.symbol_wrap_set, @@ -1851,17 +1828,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }); // When linking mingw-w64 there are some import libs we always need. - for (mingw.always_link_libs) |name| { - try comp.system_libs.put(comp.gpa, name, .{ - .needed = false, - .weak = false, - .path = null, - }); - } + try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len); + for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(name, {}); } // Generate Windows import libs. if (target.os.tag == .windows) { - const count = comp.system_libs.count(); + const count = comp.windows_libs.count(); for (0..count) |i| { try comp.queueJob(.{ .windows_import_lib = i }); } @@ -1930,7 +1902,7 @@ pub fn destroy(comp: *Compilation) void { comp.embed_file_work_queue.deinit(); const gpa = comp.gpa; - comp.system_libs.deinit(gpa); + comp.windows_libs.deinit(gpa); { var it = comp.crt_files.iterator(); @@ -2563,13 +2535,7 @@ fn addNonIncrementalStuffToCacheManifest( cache_helpers.addModule(&man.hash, comp.root_mod); } - for (comp.objects) |obj| { - _ = try man.addFilePath(obj.path, null); - man.hash.add(obj.must_link); - man.hash.add(obj.needed); - man.hash.add(obj.weak); - man.hash.add(obj.loption); - } + try link.hashInputs(man, comp.link_inputs); for (comp.c_object_table.keys()) |key| { _ = try man.addFile(key.src.src_path, null); @@ -2606,7 +2572,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.addListOfBytes(comp.framework_dirs); - try link.hashAddSystemLibs(man, comp.system_libs); + man.hash.addListOfBytes(comp.windows_libs.keys()); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); @@ -2625,12 +2591,16 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addOptional(opts.image_base); man.hash.addOptional(opts.gc_sections); man.hash.add(opts.emit_relocs); - man.hash.addListOfBytes(opts.lib_dirs); + const target = comp.root_mod.resolved_target.result; + if (target.ofmt == .macho or target.ofmt == .coff) { + // TODO remove this, libraries need to be resolved by the frontend. this is already + // done by ELF. + for (opts.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); + } man.hash.addListOfBytes(opts.rpath_list); man.hash.addListOfBytes(opts.symbol_wrap_set.keys()); if (comp.config.link_libc) { man.hash.add(comp.libc_installation != null); - const target = comp.root_mod.resolved_target.result; if (comp.libc_installation) |libc_installation| { man.hash.addOptionalBytes(libc_installation.crt_dir); if (target.abi == .msvc or target.abi == .itanium) { @@ -3798,7 +3768,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre const named_frame = tracy.namedFrame("windows_import_lib"); defer named_frame.end(); - const link_lib = comp.system_libs.keys()[index]; + const link_lib = comp.windows_libs.keys()[index]; mingw.buildImportLib(comp, link_lib) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( @@ -4711,7 +4681,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // file and building an object we need to link them together, but with just one it should go // directly to the output file. const direct_o = comp.c_source_files.len == 1 and comp.zcu == null and - comp.config.output_mode == .Obj and comp.objects.len == 0; + comp.config.output_mode == .Obj and !link.anyObjectInputs(comp.link_inputs); const o_basename_noext = if (direct_o) comp.root_name else @@ -6516,24 +6486,14 @@ pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void { // then when we create a sub-Compilation for zig libc, it also tries to // build kernel32.lib. if (comp.skip_linker_dependencies) return; + const target = comp.root_mod.resolved_target.result; + if (target.os.tag != .windows or target.ofmt == .c) return; // This happens when an `extern "foo"` function is referenced. // If we haven't seen this library yet and we're targeting Windows, we need // to queue up a work item to produce the DLL import library for this. - const gop = try comp.system_libs.getOrPut(comp.gpa, lib_name); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .needed = true, - .weak = false, - .path = null, - }; - const target = comp.root_mod.resolved_target.result; - if (target.os.tag == .windows and target.ofmt != .c) { - try comp.queueJob(.{ - .windows_import_lib = comp.system_libs.count() - 1, - }); - } - } + const gop = try comp.windows_libs.getOrPut(comp.gpa, lib_name); + if (!gop.found_existing) try comp.queueJob(.{ .windows_import_lib = comp.windows_libs.count() - 1 }); } /// This decides the optimization mode for all zig-provided libraries, including diff --git a/src/Sema.zig b/src/Sema.zig index cd32c989ee..5e2760cf1f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9595,7 +9595,7 @@ fn resolveGenericBody( } /// Given a library name, examines if the library name should end up in -/// `link.File.Options.system_libs` table (for example, libc is always +/// `link.File.Options.windows_libs` table (for example, libc is always /// specified via dedicated flag `link_libc` instead), /// and puts it there if it doesn't exist. /// It also dupes the library name which can then be saved as part of the diff --git a/src/link.zig b/src/link.zig index 463115540f..d78600783e 100644 --- a/src/link.zig +++ b/src/link.zig @@ -12,6 +12,7 @@ const Air = @import("Air.zig"); const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const Path = std.Build.Cache.Path; +const Directory = std.Build.Cache.Directory; const Compilation = @import("Compilation.zig"); const LibCInstallation = std.zig.LibCInstallation; const Liveness = @import("Liveness.zig"); @@ -26,19 +27,6 @@ const dev = @import("dev.zig"); pub const LdScript = @import("link/LdScript.zig"); -/// When adding a new field, remember to update `hashAddSystemLibs`. -/// These are *always* dynamically linked. Static libraries will be -/// provided as positional arguments. -pub const SystemLib = struct { - needed: bool, - weak: bool, - /// This can be null in two cases right now: - /// 1. Windows DLLs that zig ships such as advapi32. - /// 2. extern "foo" fn declarations where we find out about libraries too late - /// TODO: make this non-optional and resolve those two cases somehow. - path: ?Path, -}; - pub const Diags = struct { /// Stored here so that function definitions can distinguish between /// needing an allocator for things besides error reporting. @@ -355,19 +343,6 @@ pub const Diags = struct { } }; -pub fn hashAddSystemLibs( - man: *Cache.Manifest, - hm: std.StringArrayHashMapUnmanaged(SystemLib), -) !void { - const keys = hm.keys(); - man.hash.addListOfBytes(keys); - for (hm.values()) |value| { - man.hash.add(value.needed); - man.hash.add(value.weak); - if (value.path) |p| _ = try man.addFilePath(p, null); - } -} - pub const producer_string = if (builtin.is_test) "zig test" else "zig " ++ build_options.version; pub const File = struct { @@ -455,7 +430,7 @@ pub const File = struct { compatibility_version: ?std.SemanticVersion, // TODO: remove this. libraries are resolved by the frontend. - lib_dirs: []const []const u8, + lib_directories: []const Directory, framework_dirs: []const []const u8, rpath_list: []const []const u8, @@ -1027,7 +1002,6 @@ pub const File = struct { defer tracy.end(); const comp = base.comp; - const gpa = comp.gpa; const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); @@ -1059,7 +1033,7 @@ pub const File = struct { var man: Cache.Manifest = undefined; defer if (!base.disable_lld_caching) man.deinit(); - const objects = comp.objects; + const link_inputs = comp.link_inputs; var digest: [Cache.hex_digest_len]u8 = undefined; @@ -1069,11 +1043,8 @@ pub const File = struct { // We are about to obtain this lock, so here we give other processes a chance first. base.releaseLock(); - for (objects) |obj| { - _ = try man.addFilePath(obj.path, null); - man.hash.add(obj.must_link); - man.hash.add(obj.loption); - } + try hashInputs(&man, link_inputs); + for (comp.c_object_table.keys()) |key| { _ = try man.addFilePath(key.status.success.object_path, null); } @@ -1109,26 +1080,24 @@ pub const File = struct { }; } - const win32_resource_table_len = comp.win32_resource_table.count(); - const num_object_files = objects.len + comp.c_object_table.count() + win32_resource_table_len + 2; - var object_files = try std.ArrayList([*:0]const u8).initCapacity(gpa, num_object_files); - defer object_files.deinit(); + var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty; - for (objects) |obj| { - object_files.appendAssumeCapacity(try obj.path.toStringZ(arena)); + try object_files.ensureUnusedCapacity(arena, link_inputs.len); + for (link_inputs) |input| { + object_files.appendAssumeCapacity(try input.path().?.toStringZ(arena)); } + + try object_files.ensureUnusedCapacity(arena, comp.c_object_table.count() + + comp.win32_resource_table.count() + 2); + for (comp.c_object_table.keys()) |key| { object_files.appendAssumeCapacity(try key.status.success.object_path.toStringZ(arena)); } for (comp.win32_resource_table.keys()) |key| { object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path)); } - if (zcu_obj_path) |p| { - object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); - } - if (compiler_rt_path) |p| { - object_files.appendAssumeCapacity(try p.toStringZ(arena)); - } + if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); + if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); if (comp.verbose_link) { std.debug.print("ar rcs {s}", .{full_out_path_z}); @@ -1404,3 +1373,676 @@ pub fn spawnLld( if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr}); } + +/// Provided by the CLI, processed into `LinkInput` instances at the start of +/// the compilation pipeline. +pub const UnresolvedInput = union(enum) { + /// A library name that could potentially be dynamic or static depending on + /// query parameters, resolved according to library directories. + /// This could potentially resolve to a GNU ld script, resulting in more + /// library dependencies. + name_query: NameQuery, + /// When a file path is provided, query info is still needed because the + /// path may point to a .so file which may actually be a GNU ld script that + /// references library names which need to be resolved. + path_query: PathQuery, + /// Strings that come from GNU ld scripts. Is it a filename? Is it a path? + /// Who knows! Fuck around and find out. + ambiguous_name: NameQuery, + /// Put exactly this string in the dynamic section, no rpath. + dso_exact: Input.DsoExact, + + ///// Relocatable. + //object: Input.Object, + ///// Static library. + //archive: Input.Object, + ///// Windows resource file. + //winres: Path, + + pub const NameQuery = struct { + name: []const u8, + query: Query, + }; + + pub const PathQuery = struct { + path: Path, + query: Query, + }; + + pub const Query = struct { + needed: bool = false, + weak: bool = false, + reexport: bool = false, + must_link: bool = false, + hidden: bool = false, + allow_so_scripts: bool = false, + preferred_mode: std.builtin.LinkMode, + search_strategy: SearchStrategy, + + fn fallbackMode(q: Query) std.builtin.LinkMode { + assert(q.search_strategy != .no_fallback); + return switch (q.preferred_mode) { + .dynamic => .static, + .static => .dynamic, + }; + } + }; + + pub const SearchStrategy = enum { + paths_first, + mode_first, + no_fallback, + }; +}; + +pub const Input = union(enum) { + object: Object, + archive: Object, + res: Res, + /// May not be a GNU ld script. Those are resolved when converting from + /// `UnresolvedInput` to `Input` values. + dso: Dso, + dso_exact: DsoExact, + + pub const Object = struct { + path: Path, + file: fs.File, + must_link: bool, + hidden: bool, + }; + + pub const Res = struct { + path: Path, + file: fs.File, + }; + + pub const Dso = struct { + path: Path, + file: fs.File, + needed: bool, + weak: bool, + reexport: bool, + }; + + pub const DsoExact = struct { + /// Includes the ":" prefix. This is intended to be put into the DSO + /// section verbatim with no corresponding rpaths. + name: []const u8, + }; + + /// Returns `null` in the case of `dso_exact`. + pub fn path(input: Input) ?Path { + return switch (input) { + .object, .archive => |obj| obj.path, + inline .res, .dso => |x| x.path, + .dso_exact => null, + }; + } + + /// Returns `null` in the case of `dso_exact`. + pub fn pathAndFile(input: Input) ?struct { Path, fs.File } { + return switch (input) { + .object, .archive => |obj| .{ obj.path, obj.file }, + inline .res, .dso => |x| .{ x.path, x.file }, + .dso_exact => null, + }; + } +}; + +pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { + for (link_inputs) |link_input| { + man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input)); + switch (link_input) { + .object, .archive => |obj| { + _ = try man.addOpenedFile(obj.path, obj.file, null); + man.hash.add(obj.must_link); + man.hash.add(obj.hidden); + }, + .res => |res| { + _ = try man.addOpenedFile(res.path, res.file, null); + }, + .dso => |dso| { + _ = try man.addOpenedFile(dso.path, dso.file, null); + man.hash.add(dso.needed); + man.hash.add(dso.weak); + man.hash.add(dso.reexport); + }, + .dso_exact => |dso_exact| { + man.hash.addBytes(dso_exact.name); + }, + } + } +} + +pub fn resolveInputs( + gpa: Allocator, + arena: Allocator, + target: std.Target, + /// This function mutates this array but does not take ownership. + /// Allocated with `gpa`. + unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput), + /// Allocated with `gpa`. + resolved_inputs: *std.ArrayListUnmanaged(Input), + lib_directories: []const Cache.Directory, + color: std.zig.Color, +) Allocator.Error!void { + var checked_paths: std.ArrayListUnmanaged(u8) = .empty; + defer checked_paths.deinit(gpa); + + var ld_script_bytes: std.ArrayListUnmanaged(u8) = .empty; + defer ld_script_bytes.deinit(gpa); + + var failed_libs: std.ArrayListUnmanaged(struct { + name: []const u8, + strategy: UnresolvedInput.SearchStrategy, + checked_paths: []const u8, + preferred_mode: std.builtin.LinkMode, + }) = .empty; + + // Convert external system libs into a stack so that items can be + // pushed to it. + // + // This is necessary because shared objects might turn out to be + // "linker scripts" that in fact resolve to one or more other + // external system libs, including parameters such as "needed". + // + // Unfortunately, such files need to be detected immediately, so + // that this library search logic can be applied to them. + mem.reverse(UnresolvedInput, unresolved_inputs.items); + + syslib: while (unresolved_inputs.popOrNull()) |unresolved_input| { + const name_query: UnresolvedInput.NameQuery = switch (unresolved_input) { + .name_query => |nq| nq, + .ambiguous_name => |an| an: { + const lib_name, const link_mode = stripLibPrefixAndSuffix(an.name, target) orelse { + try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, .{ + .path = Path.initCwd(an.name), + .query = an.query, + }, color); + continue; + }; + break :an .{ + .name = lib_name, + .query = .{ + .needed = an.query.needed, + .weak = an.query.weak, + .reexport = an.query.reexport, + .must_link = an.query.must_link, + .hidden = an.query.hidden, + .preferred_mode = link_mode, + .search_strategy = .no_fallback, + }, + }; + }, + .path_query => |pq| { + try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, pq, color); + continue; + }, + .dso_exact => |dso_exact| { + try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact }); + continue; + }, + }; + const query = name_query.query; + + // Checked in the first pass above while looking for libc libraries. + assert(!fs.path.isAbsolute(name_query.name)); + + checked_paths.clearRetainingCapacity(); + + switch (query.search_strategy) { + .mode_first, .no_fallback => { + // check for preferred mode + for (lib_directories) |lib_directory| switch (try resolveLibInput( + gpa, + arena, + unresolved_inputs, + resolved_inputs, + &checked_paths, + &ld_script_bytes, + lib_directory, + name_query, + target, + query.preferred_mode, + color, + )) { + .ok => continue :syslib, + .no_match => {}, + }; + // check for fallback mode + if (query.search_strategy == .no_fallback) { + try failed_libs.append(arena, .{ + .name = name_query.name, + .strategy = query.search_strategy, + .checked_paths = try arena.dupe(u8, checked_paths.items), + .preferred_mode = query.preferred_mode, + }); + continue :syslib; + } + for (lib_directories) |lib_directory| switch (try resolveLibInput( + gpa, + arena, + unresolved_inputs, + resolved_inputs, + &checked_paths, + &ld_script_bytes, + lib_directory, + name_query, + target, + query.fallbackMode(), + color, + )) { + .ok => continue :syslib, + .no_match => {}, + }; + try failed_libs.append(arena, .{ + .name = name_query.name, + .strategy = query.search_strategy, + .checked_paths = try arena.dupe(u8, checked_paths.items), + .preferred_mode = query.preferred_mode, + }); + continue :syslib; + }, + .paths_first => { + for (lib_directories) |lib_directory| { + // check for preferred mode + switch (try resolveLibInput( + gpa, + arena, + unresolved_inputs, + resolved_inputs, + &checked_paths, + &ld_script_bytes, + lib_directory, + name_query, + target, + query.preferred_mode, + color, + )) { + .ok => continue :syslib, + .no_match => {}, + } + + // check for fallback mode + switch (try resolveLibInput( + gpa, + arena, + unresolved_inputs, + resolved_inputs, + &checked_paths, + &ld_script_bytes, + lib_directory, + name_query, + target, + query.fallbackMode(), + color, + )) { + .ok => continue :syslib, + .no_match => {}, + } + } + try failed_libs.append(arena, .{ + .name = name_query.name, + .strategy = query.search_strategy, + .checked_paths = try arena.dupe(u8, checked_paths.items), + .preferred_mode = query.preferred_mode, + }); + continue :syslib; + }, + } + @compileError("unreachable"); + } + + if (failed_libs.items.len > 0) { + for (failed_libs.items) |f| { + const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; + std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{ + @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths, + }); + } + std.process.exit(1); + } +} + +const AccessLibPathResult = enum { ok, no_match }; +const fatal = std.process.fatal; + +fn resolveLibInput( + gpa: Allocator, + arena: Allocator, + /// Allocated via `gpa`. + unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput), + /// Allocated via `gpa`. + resolved_inputs: *std.ArrayListUnmanaged(Input), + /// Allocated via `gpa`. + checked_paths: *std.ArrayListUnmanaged(u8), + /// Allocated via `gpa`. + ld_script_bytes: *std.ArrayListUnmanaged(u8), + lib_directory: Directory, + name_query: UnresolvedInput.NameQuery, + target: std.Target, + link_mode: std.builtin.LinkMode, + color: std.zig.Color, +) Allocator.Error!AccessLibPathResult { + try resolved_inputs.ensureUnusedCapacity(gpa, 1); + + const lib_name = name_query.name; + + if (target.isDarwin() and link_mode == .dynamic) tbd: { + // Prefer .tbd over .dylib. + const test_path: Path = .{ + .root_dir = lib_directory, + .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}), + }; + try checked_paths.writer(gpa).print("\n {}", .{test_path}); + var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :tbd, + else => |e| fatal("unable to search for tbd library '{}': {s}", .{ test_path, @errorName(e) }), + }; + errdefer file.close(); + return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, name_query.query); + } + + { + const test_path: Path = .{ + .root_dir = lib_directory, + .sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{ + target.libPrefix(), lib_name, switch (link_mode) { + .static => target.staticLibSuffix(), + .dynamic => target.dynamicLibSuffix(), + }, + }), + }; + try checked_paths.writer(gpa).print("\n {}", .{test_path}); + switch (try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{ + .path = test_path, + .query = name_query.query, + }, link_mode, color)) { + .no_match => {}, + .ok => return .ok, + } + } + + // In the case of Darwin, the main check will be .dylib, so here we + // additionally check for .so files. + if (target.isDarwin() and link_mode == .dynamic) so: { + const test_path: Path = .{ + .root_dir = lib_directory, + .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}), + }; + try checked_paths.writer(gpa).print("\n {}", .{test_path}); + var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :so, + else => |e| fatal("unable to search for so library '{}': {s}", .{ + test_path, @errorName(e), + }), + }; + errdefer file.close(); + return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, name_query.query); + } + + // In the case of MinGW, the main check will be .lib but we also need to + // look for `libfoo.a`. + if (target.isMinGW() and link_mode == .static) mingw: { + const test_path: Path = .{ + .root_dir = lib_directory, + .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}), + }; + try checked_paths.writer(gpa).print("\n {}", .{test_path}); + var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :mingw, + else => |e| fatal("unable to search for static library '{}': {s}", .{ test_path, @errorName(e) }), + }; + errdefer file.close(); + return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, name_query.query); + } + + return .no_match; +} + +fn finishAccessLibPath( + resolved_inputs: *std.ArrayListUnmanaged(Input), + path: Path, + file: std.fs.File, + link_mode: std.builtin.LinkMode, + query: UnresolvedInput.Query, +) AccessLibPathResult { + switch (link_mode) { + .static => resolved_inputs.appendAssumeCapacity(.{ .archive = .{ + .path = path, + .file = file, + .must_link = query.must_link, + .hidden = query.hidden, + } }), + .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = path, + .file = file, + .needed = query.needed, + .weak = query.weak, + .reexport = query.reexport, + } }), + } + return .ok; +} + +fn resolvePathInput( + gpa: Allocator, + arena: Allocator, + /// Allocated with `gpa`. + unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput), + /// Allocated with `gpa`. + resolved_inputs: *std.ArrayListUnmanaged(Input), + /// Allocated via `gpa`. + ld_script_bytes: *std.ArrayListUnmanaged(u8), + target: std.Target, + pq: UnresolvedInput.PathQuery, + color: std.zig.Color, +) Allocator.Error!void { + switch (switch (Compilation.classifyFileExt(pq.path.sub_path)) { + .static_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color), + .shared_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), + .object => { + var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| + fatal("failed to open object {}: {s}", .{ pq.path, @errorName(err) }); + errdefer file.close(); + try resolved_inputs.append(gpa, .{ .object = .{ + .path = pq.path, + .file = file, + .must_link = pq.query.must_link, + .hidden = pq.query.hidden, + } }); + return; + }, + .res => { + var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| + fatal("failed to open windows resource {}: {s}", .{ pq.path, @errorName(err) }); + errdefer file.close(); + try resolved_inputs.append(gpa, .{ .res = .{ + .path = pq.path, + .file = file, + } }); + return; + }, + else => fatal("{}: unrecognized file extension", .{pq.path}), + }) { + .ok => {}, + .no_match => fatal("{}: file not found", .{pq.path}), + } +} + +fn resolvePathInputLib( + gpa: Allocator, + arena: Allocator, + /// Allocated with `gpa`. + unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput), + /// Allocated with `gpa`. + resolved_inputs: *std.ArrayListUnmanaged(Input), + /// Allocated via `gpa`. + ld_script_bytes: *std.ArrayListUnmanaged(u8), + target: std.Target, + pq: UnresolvedInput.PathQuery, + link_mode: std.builtin.LinkMode, + color: std.zig.Color, +) Allocator.Error!AccessLibPathResult { + const test_path: Path = pq.path; + // In the case of .so files, they might actually be "linker scripts" + // that contain references to other libraries. + if (pq.query.allow_so_scripts and target.ofmt == .elf and mem.endsWith(u8, test_path.sub_path, ".so")) { + var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => return .no_match, + else => |e| fatal("unable to search for {s} library '{'}': {s}", .{ + @tagName(link_mode), test_path, @errorName(e), + }), + }; + errdefer file.close(); + try ld_script_bytes.resize(gpa, @sizeOf(std.elf.Elf64_Ehdr)); + const n = file.preadAll(ld_script_bytes.items, 0) catch |err| fatal("failed to read '{'}': {s}", .{ + test_path, @errorName(err), + }); + elf_file: { + if (n != ld_script_bytes.items.len) break :elf_file; + if (!mem.eql(u8, ld_script_bytes.items[0..4], "\x7fELF")) break :elf_file; + // Appears to be an ELF file. + return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, pq.query); + } + const stat = file.stat() catch |err| + fatal("failed to stat {}: {s}", .{ test_path, @errorName(err) }); + const size = std.math.cast(u32, stat.size) orelse + fatal("{}: linker script too big", .{test_path}); + try ld_script_bytes.resize(gpa, size); + const buf = ld_script_bytes.items[n..]; + const n2 = file.preadAll(buf, n) catch |err| + fatal("failed to read {}: {s}", .{ test_path, @errorName(err) }); + if (n2 != buf.len) fatal("failed to read {}: unexpected end of file", .{test_path}); + var diags = Diags.init(gpa); + defer diags.deinit(); + const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items); + if (diags.hasErrors()) { + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + + try diags.addMessagesToBundle(&wip_errors); + + var error_bundle = try wip_errors.toOwnedBundle(""); + defer error_bundle.deinit(gpa); + + error_bundle.renderToStdErr(color.renderOptions()); + + std.process.exit(1); + } + + var ld_script = ld_script_result catch |err| + fatal("{}: failed to parse linker script: {s}", .{ test_path, @errorName(err) }); + defer ld_script.deinit(gpa); + + try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len); + for (ld_script.args) |arg| { + const query: UnresolvedInput.Query = .{ + .needed = arg.needed or pq.query.needed, + .weak = pq.query.weak, + .reexport = pq.query.reexport, + .preferred_mode = pq.query.preferred_mode, + .search_strategy = pq.query.search_strategy, + .allow_so_scripts = pq.query.allow_so_scripts, + }; + if (mem.startsWith(u8, arg.path, "-l")) { + unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{ + .name = try arena.dupe(u8, arg.path["-l".len..]), + .query = query, + } }); + } else { + unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{ + .name = try arena.dupe(u8, arg.path), + .query = query, + } }); + } + } + file.close(); + return .ok; + } + + var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => return .no_match, + else => |e| fatal("unable to search for {s} library {}: {s}", .{ + @tagName(link_mode), test_path, @errorName(e), + }), + }; + errdefer file.close(); + return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, pq.query); +} + +pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object { + var file = try path.root_dir.handle.openFile(path.sub_path, .{}); + errdefer file.close(); + return .{ + .path = path, + .file = file, + .must_link = must_link, + .hidden = hidden, + }; +} + +pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { + var file = try path.root_dir.handle.openFile(path.sub_path, .{}); + errdefer file.close(); + return .{ + .path = path, + .file = file, + .needed = needed, + .weak = weak, + .reexport = reexport, + }; +} + +pub fn openObjectInput(diags: *Diags, path: Path) error{LinkFailure}!Input { + return .{ .object = openObject(path, false, false) catch |err| { + return diags.failParse(path, "failed to open {}: {s}", .{ path, @errorName(err) }); + } }; +} + +pub fn openArchiveInput(diags: *Diags, path: Path) error{LinkFailure}!Input { + return .{ .archive = openObject(path, false, false) catch |err| { + return diags.failParse(path, "failed to open {}: {s}", .{ path, @errorName(err) }); + } }; +} + +fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) ?struct { []const u8, std.builtin.LinkMode } { + const prefix = target.libPrefix(); + const static_suffix = target.staticLibSuffix(); + const dynamic_suffix = target.dynamicLibSuffix(); + const basename = fs.path.basename(path); + const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else return null; + if (mem.endsWith(u8, unlibbed, static_suffix)) return .{ + unlibbed[0 .. unlibbed.len - static_suffix.len], .static, + }; + if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{ + unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic, + }; + return null; +} + +/// Returns true if and only if there is at least one input of type object, +/// archive, or Windows resource file. +pub fn anyObjectInputs(inputs: []const Input) bool { + return countObjectInputs(inputs) != 0; +} + +/// Returns the number of inputs of type object, archive, or Windows resource file. +pub fn countObjectInputs(inputs: []const Input) usize { + var count: usize = 0; + for (inputs) |input| switch (input) { + .dso, .dso_exact => continue, + .res, .object, .archive => count += 1, + }; + return count; +} + +/// Returns the first input of type object or archive. +pub fn firstObjectInput(inputs: []const Input) ?Input.Object { + for (inputs) |input| switch (input) { + .object, .archive => |obj| return obj, + .res, .dso, .dso_exact => continue, + }; + return null; +} diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7d01e64d78..aa75432bc1 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -16,7 +16,7 @@ dynamicbase: bool, /// default or populated together. They should not be separate fields. major_subsystem_version: u16, minor_subsystem_version: u16, -lib_dirs: []const []const u8, +lib_directories: []const Directory, entry: link.File.OpenOptions.Entry, entry_addr: ?u32, module_definition_file: ?[]const u8, @@ -297,7 +297,7 @@ pub fn createEmpty( .dynamicbase = options.dynamicbase, .major_subsystem_version = options.major_subsystem_version orelse 6, .minor_subsystem_version = options.minor_subsystem_version orelse 0, - .lib_dirs = options.lib_dirs, + .lib_directories = options.lib_directories, .entry_addr = math.cast(u32, options.entry_addr orelse 0) orelse return error.EntryAddressTooBig, .module_definition_file = options.module_definition_file, @@ -2727,6 +2727,7 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; +const Directory = std.Build.Cache.Directory; const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig index cf09174e88..de9ec66177 100644 --- a/src/link/Coff/lld.zig +++ b/src/link/Coff/lld.zig @@ -8,6 +8,7 @@ const log = std.log.scoped(.link); const mem = std.mem; const Cache = std.Build.Cache; const Path = std.Build.Cache.Path; +const Directory = std.Build.Cache.Directory; const mingw = @import("../../mingw.zig"); const link = @import("../../link.zig"); @@ -74,10 +75,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no comptime assert(Compilation.link_hash_implementation_version == 14); - for (comp.objects) |obj| { - _ = try man.addFilePath(obj.path, null); - man.hash.add(obj.must_link); - } + try link.hashInputs(&man, comp.link_inputs); for (comp.c_object_table.keys()) |key| { _ = try man.addFilePath(key.status.success.object_path, null); } @@ -88,7 +86,10 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no man.hash.addOptionalBytes(entry_name); man.hash.add(self.base.stack_size); man.hash.add(self.image_base); - man.hash.addListOfBytes(self.lib_dirs); + { + // TODO remove this, libraries must instead be resolved by the frontend. + for (self.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); + } man.hash.add(comp.skip_linker_dependencies); if (comp.config.link_libc) { man.hash.add(comp.libc_installation != null); @@ -100,7 +101,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no } } } - try link.hashAddSystemLibs(&man, comp.system_libs); + man.hash.addListOfBytes(comp.windows_libs.keys()); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.addOptional(self.subsystem); man.hash.add(comp.config.is_test); @@ -148,8 +149,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no // here. TODO: think carefully about how we can avoid this redundant operation when doing // build-obj. See also the corresponding TODO in linkAsArchive. const the_object_path = blk: { - if (comp.objects.len != 0) - break :blk comp.objects[0].path; + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; @@ -266,18 +266,24 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no } } - for (self.lib_dirs) |lib_dir| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); + for (self.lib_directories) |lib_directory| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."})); } - try argv.ensureUnusedCapacity(comp.objects.len); - for (comp.objects) |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Path, obj.path)})); - } else { - argv.appendAssumeCapacity(try obj.path.toString(arena)); - } - } + try argv.ensureUnusedCapacity(comp.link_inputs.len); + for (comp.link_inputs) |link_input| switch (link_input) { + .dso_exact => unreachable, // not applicable to PE/COFF + inline .dso, .res => |x| { + argv.appendAssumeCapacity(try x.path.toString(arena)); + }, + .object, .archive => |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Path, obj.path)})); + } else { + argv.appendAssumeCapacity(try obj.path.toString(arena)); + } + }, + }; for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); @@ -484,20 +490,20 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); } - try argv.ensureUnusedCapacity(comp.system_libs.count()); - for (comp.system_libs.keys()) |key| { + try argv.ensureUnusedCapacity(comp.windows_libs.count()); + for (comp.windows_libs.keys()) |key| { const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); if (comp.crt_files.get(lib_basename)) |crt_file| { argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); continue; } - if (try findLib(arena, lib_basename, self.lib_dirs)) |full_path| { + if (try findLib(arena, lib_basename, self.lib_directories)) |full_path| { argv.appendAssumeCapacity(full_path); continue; } if (target.abi.isGnu()) { const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try findLib(arena, fallback_name, self.lib_dirs)) |full_path| { + if (try findLib(arena, fallback_name, self.lib_directories)) |full_path| { argv.appendAssumeCapacity(full_path); continue; } @@ -530,14 +536,13 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no } } -fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 { - for (lib_dirs) |lib_dir| { - const full_path = try fs.path.join(arena, &.{ lib_dir, name }); - fs.cwd().access(full_path, .{}) catch |err| switch (err) { +fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Directory) !?[]const u8 { + for (lib_directories) |lib_directory| { + lib_directory.handle.access(name, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; - return full_path; + return try lib_directory.join(arena, &.{name}); } return null; } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 35b5a30349..56571be5b3 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -796,44 +796,55 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod const csu = try comp.getCrtPaths(arena); // csu prelude - if (csu.crt0) |path| parseObjectReportingFailure(self, path); - if (csu.crti) |path| parseObjectReportingFailure(self, path); - if (csu.crtbegin) |path| parseObjectReportingFailure(self, path); + if (csu.crt0) |path| openParseObjectReportingFailure(self, path); + if (csu.crti) |path| openParseObjectReportingFailure(self, path); + if (csu.crtbegin) |path| openParseObjectReportingFailure(self, path); - for (comp.objects) |obj| { - parseInputReportingFailure(self, obj.path, obj.needed, obj.must_link); - } + // objects and archives + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive => parseInputReportingFailure(self, link_input), + .dso_exact => @panic("TODO"), + .dso => continue, // handled below + .res => unreachable, + }; // This is a set of object files emitted by clang in a single `build-exe` invocation. // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up // in this set. for (comp.c_object_table.keys()) |key| { - parseObjectReportingFailure(self, key.status.success.object_path); + openParseObjectReportingFailure(self, key.status.success.object_path); } - if (module_obj_path) |path| parseObjectReportingFailure(self, path); + if (module_obj_path) |path| openParseObjectReportingFailure(self, path); - if (comp.config.any_sanitize_thread) parseCrtFileReportingFailure(self, comp.tsan_lib.?); - if (comp.config.any_fuzz) parseCrtFileReportingFailure(self, comp.fuzzer_lib.?); + if (comp.config.any_sanitize_thread) + openParseArchiveReportingFailure(self, comp.tsan_lib.?.full_object_path); + + if (comp.config.any_fuzz) + openParseArchiveReportingFailure(self, comp.fuzzer_lib.?.full_object_path); // libc if (!comp.skip_linker_dependencies and !comp.config.link_libc) { - if (comp.libc_static_lib) |lib| parseCrtFileReportingFailure(self, lib); + if (comp.libc_static_lib) |lib| + openParseArchiveReportingFailure(self, lib.full_object_path); } - for (comp.system_libs.values()) |lib_info| { - parseInputReportingFailure(self, lib_info.path.?, lib_info.needed, false); - } + // dynamic libraries + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive, .dso_exact => continue, // handled above + .dso => parseInputReportingFailure(self, link_input), + .res => unreachable, + }; // libc++ dep if (comp.config.link_libcpp) { - parseInputReportingFailure(self, comp.libcxxabi_static_lib.?.full_object_path, false, false); - parseInputReportingFailure(self, comp.libcxx_static_lib.?.full_object_path, false, false); + openParseArchiveReportingFailure(self, comp.libcxxabi_static_lib.?.full_object_path); + openParseArchiveReportingFailure(self, comp.libcxx_static_lib.?.full_object_path); } // libunwind dep if (comp.config.link_libunwind) { - parseInputReportingFailure(self, comp.libunwind_static_lib.?.full_object_path, false, false); + openParseArchiveReportingFailure(self, comp.libunwind_static_lib.?.full_object_path); } // libc dep @@ -853,7 +864,10 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod lc.crt_dir.?, lib_name, suffix, }); const resolved_path = Path.initCwd(lib_path); - parseInputReportingFailure(self, resolved_path, false, false); + switch (comp.config.link_mode) { + .static => openParseArchiveReportingFailure(self, resolved_path), + .dynamic => openParseDsoReportingFailure(self, resolved_path), + } } } else if (target.isGnuLibC()) { for (glibc.libs) |lib| { @@ -864,15 +878,19 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod const lib_path = Path.initCwd(try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, })); - parseInputReportingFailure(self, lib_path, false, false); + openParseDsoReportingFailure(self, lib_path); } - parseInputReportingFailure(self, try comp.get_libc_crt_file(arena, "libc_nonshared.a"), false, false); + const crt_file_path = try comp.get_libc_crt_file(arena, "libc_nonshared.a"); + openParseArchiveReportingFailure(self, crt_file_path); } else if (target.isMusl()) { const path = try comp.get_libc_crt_file(arena, switch (link_mode) { .static => "libc.a", .dynamic => "libc.so", }); - parseInputReportingFailure(self, path, false, false); + switch (link_mode) { + .static => openParseArchiveReportingFailure(self, path), + .dynamic => openParseDsoReportingFailure(self, path), + } } else { diags.flags.missing_libc = true; } @@ -884,14 +902,14 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod // to be after the shared libraries, so they are picked up from the shared // libraries, not libcompiler_rt. if (comp.compiler_rt_lib) |crt_file| { - parseInputReportingFailure(self, crt_file.full_object_path, false, false); + openParseArchiveReportingFailure(self, crt_file.full_object_path); } else if (comp.compiler_rt_obj) |crt_file| { - parseObjectReportingFailure(self, crt_file.full_object_path); + openParseObjectReportingFailure(self, crt_file.full_object_path); } // csu postlude - if (csu.crtend) |path| parseObjectReportingFailure(self, path); - if (csu.crtn) |path| parseObjectReportingFailure(self, path); + if (csu.crtend) |path| openParseObjectReportingFailure(self, path); + if (csu.crtn) |path| openParseObjectReportingFailure(self, path); if (diags.hasErrors()) return error.FlushFailure; @@ -1087,9 +1105,15 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void { try argv.append(full_out_path); if (self.base.isRelocatable()) { - for (comp.objects) |obj| { - try argv.append(try obj.path.toString(arena)); - } + for (self.base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, + .dso => |dso| try argv.append(try dso.path.toString(arena)), + .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), + .dso_exact => |dso_exact| { + assert(dso_exact.name[0] == ':'); + try argv.appendSlice(&.{ "-l", dso_exact.name }); + }, + }; for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); @@ -1186,20 +1210,26 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void { } var whole_archive = false; - for (comp.objects) |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - if (obj.loption) { - try argv.append("-l"); - } - try argv.append(try obj.path.toString(arena)); - } + for (self.base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, + .dso => continue, + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso_exact => |dso_exact| { + assert(dso_exact.name[0] == ':'); + try argv.appendSlice(&.{ "-l", dso_exact.name }); + }, + }; + if (whole_archive) { try argv.append("-no-whole-archive"); whole_archive = false; @@ -1231,25 +1261,28 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void { // Shared libraries. // Worst-case, we need an --as-needed argument for every lib, as well // as one before and one after. - try argv.ensureUnusedCapacity(self.base.comp.system_libs.keys().len * 2 + 2); argv.appendAssumeCapacity("--as-needed"); var as_needed = true; - for (self.base.comp.system_libs.values()) |lib_info| { - const lib_as_needed = !lib_info.needed; - switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { - 0b00, 0b11 => {}, - 0b01 => { - argv.appendAssumeCapacity("--no-as-needed"); - as_needed = false; - }, - 0b10 => { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - }, - } - argv.appendAssumeCapacity(try lib_info.path.?.toString(arena)); - } + for (self.base.comp.link_inputs) |link_input| switch (link_input) { + .object, .archive, .dso_exact => continue, + .dso => |dso| { + const lib_as_needed = !dso.needed; + switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { + 0b00, 0b11 => {}, + 0b01 => { + try argv.append("--no-as-needed"); + as_needed = false; + }, + 0b10 => { + try argv.append("--as-needed"); + as_needed = true; + }, + } + argv.appendAssumeCapacity(try dso.path.toString(arena)); + }, + .res => unreachable, + }; if (!as_needed) { argv.appendAssumeCapacity("--as-needed"); @@ -1321,59 +1354,51 @@ pub const ParseError = error{ UnknownFileType, } || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError; -fn parseCrtFileReportingFailure(self: *Elf, crt_file: Compilation.CrtFile) void { - parseInputReportingFailure(self, crt_file.full_object_path, false, false); -} - -pub fn parseInputReportingFailure(self: *Elf, path: Path, needed: bool, must_link: bool) void { +pub fn parseInputReportingFailure(self: *Elf, input: link.Input) void { const gpa = self.base.comp.gpa; const diags = &self.base.comp.link_diags; const target = self.getTarget(); - switch (Compilation.classifyFileExt(path.sub_path)) { - .object => parseObjectReportingFailure(self, path), - .shared_library => parseSharedObject(gpa, diags, .{ - .path = path, - .needed = needed, - }, &self.shared_objects, &self.files, target) catch |err| switch (err) { - error.LinkFailure => return, // already reported - error.BadMagic, error.UnexpectedEndOfFile => { - var notes = diags.addErrorWithNotes(2) catch return diags.setAllocFailure(); - notes.addMsg("failed to parse shared object: {s}", .{@errorName(err)}) catch return diags.setAllocFailure(); - notes.addNote("while parsing {}", .{path}) catch return diags.setAllocFailure(); - notes.addNote("{s}", .{@as([]const u8, "the file may be a GNU ld script, in which case it is not an ELF file but a text file referencing other libraries to link. In this case, avoid depending on the library, convince your system administrators to refrain from using this kind of file, or pass -fallow-so-scripts to force the compiler to check every shared library in case it is an ld script.")}) catch return diags.setAllocFailure(); - }, - else => |e| diags.addParseError(path, "failed to parse shared object: {s}", .{@errorName(e)}), - }, - .static_library => parseArchive(self, path, must_link) catch |err| switch (err) { - error.LinkFailure => return, // already reported - else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), - }, - else => diags.addParseError(path, "unrecognized file type", .{}), + switch (input) { + .res => unreachable, + .dso_exact => unreachable, + .object => |obj| parseObjectReportingFailure(self, obj), + .archive => |obj| parseArchiveReportingFailure(self, obj), + .dso => |dso| parseDsoReportingFailure(gpa, diags, dso, &self.shared_objects, &self.files, target), } } -pub fn parseObjectReportingFailure(self: *Elf, path: Path) void { +pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { const diags = &self.base.comp.link_diags; - self.parseObject(path) catch |err| switch (err) { + const obj = link.openObject(path, false, false) catch |err| { + switch (diags.failParse(path, "failed to open object {}: {s}", .{ path, @errorName(err) })) { + error.LinkFailure => return, + } + }; + self.parseObjectReportingFailure(obj); +} + +pub fn parseObjectReportingFailure(self: *Elf, obj: link.Input.Object) void { + const diags = &self.base.comp.link_diags; + self.parseObject(obj) catch |err| switch (err) { error.LinkFailure => return, // already reported - else => |e| diags.addParseError(path, "unable to parse object: {s}", .{@errorName(e)}), + else => |e| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}), }; } -fn parseObject(self: *Elf, path: Path) ParseError!void { +fn parseObject(self: *Elf, obj: link.Input.Object) ParseError!void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; - const handle = try path.root_dir.handle.openFile(path.sub_path, .{}); + const handle = obj.file; const fh = try self.addFileHandle(handle); const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .object = .{ .path = .{ - .root_dir = path.root_dir, - .sub_path = try gpa.dupe(u8, path.sub_path), + .root_dir = obj.path.root_dir, + .sub_path = try gpa.dupe(u8, obj.path.sub_path), }, .file_handle = fh, .index = index, @@ -1384,17 +1409,35 @@ fn parseObject(self: *Elf, path: Path) ParseError!void { try object.parse(self); } -fn parseArchive(self: *Elf, path: Path, must_link: bool) ParseError!void { +pub fn openParseArchiveReportingFailure(self: *Elf, path: Path) void { + const diags = &self.base.comp.link_diags; + const obj = link.openObject(path, false, false) catch |err| { + switch (diags.failParse(path, "failed to open archive {}: {s}", .{ path, @errorName(err) })) { + error.LinkFailure => return, + } + }; + parseArchiveReportingFailure(self, obj); +} + +pub fn parseArchiveReportingFailure(self: *Elf, obj: link.Input.Object) void { + const diags = &self.base.comp.link_diags; + self.parseArchive(obj) catch |err| switch (err) { + error.LinkFailure => return, // already reported + else => |e| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}), + }; +} + +fn parseArchive(self: *Elf, obj: link.Input.Object) ParseError!void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; - const handle = try path.root_dir.handle.openFile(path.sub_path, .{}); + const handle = obj.file; const fh = try self.addFileHandle(handle); var archive: Archive = .{}; defer archive.deinit(gpa); - try archive.parse(self, path, fh); + try archive.parse(self, obj.path, fh); const objects = try archive.objects.toOwnedSlice(gpa); defer gpa.free(objects); @@ -1404,16 +1447,48 @@ fn parseArchive(self: *Elf, path: Path, must_link: bool) ParseError!void { self.files.set(index, .{ .object = extracted }); const object = &self.files.items(.data)[index].object; object.index = index; - object.alive = must_link; + object.alive = obj.must_link; try object.parse(self); try self.objects.append(gpa, index); } } -fn parseSharedObject( +fn openParseDsoReportingFailure(self: *Elf, path: Path) void { + const diags = &self.base.comp.link_diags; + const target = self.getTarget(); + const dso = link.openDso(path, false, false, false) catch |err| { + switch (diags.failParse(path, "failed to open shared object {}: {s}", .{ path, @errorName(err) })) { + error.LinkFailure => return, + } + }; + const gpa = self.base.comp.gpa; + parseDsoReportingFailure(gpa, diags, dso, &self.shared_objects, &self.files, target); +} + +fn parseDsoReportingFailure( gpa: Allocator, diags: *Diags, - lib: SystemLib, + dso: link.Input.Dso, + shared_objects: *std.StringArrayHashMapUnmanaged(File.Index), + files: *std.MultiArrayList(File.Entry), + target: std.Target, +) void { + parseDso(gpa, diags, dso, shared_objects, files, target) catch |err| switch (err) { + error.LinkFailure => return, // already reported + error.BadMagic, error.UnexpectedEndOfFile => { + var notes = diags.addErrorWithNotes(2) catch return diags.setAllocFailure(); + notes.addMsg("failed to parse shared object: {s}", .{@errorName(err)}) catch return diags.setAllocFailure(); + notes.addNote("while parsing {}", .{dso.path}) catch return diags.setAllocFailure(); + notes.addNote("{s}", .{@as([]const u8, "the file may be a GNU ld script, in which case it is not an ELF file but a text file referencing other libraries to link. In this case, avoid depending on the library, convince your system administrators to refrain from using this kind of file, or pass -fallow-so-scripts to force the compiler to check every shared library in case it is an ld script.")}) catch return diags.setAllocFailure(); + }, + else => |e| diags.addParseError(dso.path, "failed to parse shared object: {s}", .{@errorName(e)}), + }; +} + +fn parseDso( + gpa: Allocator, + diags: *Diags, + dso: link.Input.Dso, shared_objects: *std.StringArrayHashMapUnmanaged(File.Index), files: *std.MultiArrayList(File.Entry), target: std.Target, @@ -1421,14 +1496,14 @@ fn parseSharedObject( const tracy = trace(@src()); defer tracy.end(); - const handle = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{}); + const handle = dso.file; defer handle.close(); const stat = Stat.fromFs(try handle.stat()); - var header = try SharedObject.parseHeader(gpa, diags, lib.path, handle, stat, target); + var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target); defer header.deinit(gpa); - const soname = header.soname() orelse lib.path.basename(); + const soname = header.soname() orelse dso.path.basename(); const gop = try shared_objects.getOrPut(gpa, soname); if (gop.found_existing) { @@ -1446,8 +1521,8 @@ fn parseSharedObject( errdefer parsed.deinit(gpa); const duped_path: Path = .{ - .root_dir = lib.path.root_dir, - .sub_path = try gpa.dupe(u8, lib.path.sub_path), + .root_dir = dso.path.root_dir, + .sub_path = try gpa.dupe(u8, dso.path.sub_path), }; errdefer gpa.free(duped_path.sub_path); @@ -1456,8 +1531,8 @@ fn parseSharedObject( .parsed = parsed, .path = duped_path, .index = index, - .needed = lib.needed, - .alive = lib.needed, + .needed = dso.needed, + .alive = dso.needed, .aliases = null, .symbols = .empty, .symbols_extra = .empty, @@ -1824,11 +1899,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s try man.addOptionalFile(self.version_script); man.hash.add(self.allow_undefined_version); man.hash.addOptional(self.enable_new_dtags); - for (comp.objects) |obj| { - _ = try man.addFilePath(obj.path, null); - man.hash.add(obj.must_link); - man.hash.add(obj.loption); - } + try link.hashInputs(&man, comp.link_inputs); for (comp.c_object_table.keys()) |key| { _ = try man.addFilePath(key.status.success.object_path, null); } @@ -1875,7 +1946,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s } man.hash.addOptionalBytes(self.soname); man.hash.addOptional(comp.version); - try link.hashAddSystemLibs(&man, comp.system_libs); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.add(self.base.allow_shlib_undefined); man.hash.add(self.bind_global_refs_locally); @@ -1922,8 +1992,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s // here. TODO: think carefully about how we can avoid this redundant operation when doing // build-obj. See also the corresponding TODO in linkAsArchive. const the_object_path = blk: { - if (comp.objects.len != 0) - break :blk comp.objects[0].path; + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; @@ -2178,21 +2247,26 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s // Positional arguments to the linker such as object files. var whole_archive = false; - for (comp.objects) |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - if (obj.loption) { - assert(obj.path.sub_path[0] == ':'); - try argv.append("-l"); - } - try argv.append(try obj.path.toString(arena)); - } + for (self.base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, // Windows-only + .dso => continue, + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso_exact => |dso_exact| { + assert(dso_exact.name[0] == ':'); + try argv.appendSlice(&.{ "-l", dso_exact.name }); + }, + }; + if (whole_archive) { try argv.append("-no-whole-archive"); whole_archive = false; @@ -2228,35 +2302,35 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s // Shared libraries. if (is_exe_or_dyn_lib) { - const system_libs = comp.system_libs.keys(); - const system_libs_values = comp.system_libs.values(); - // Worst-case, we need an --as-needed argument for every lib, as well // as one before and one after. - try argv.ensureUnusedCapacity(system_libs.len * 2 + 2); - argv.appendAssumeCapacity("--as-needed"); + try argv.append("--as-needed"); var as_needed = true; - for (system_libs_values) |lib_info| { - const lib_as_needed = !lib_info.needed; - switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { - 0b00, 0b11 => {}, - 0b01 => { - argv.appendAssumeCapacity("--no-as-needed"); - as_needed = false; - }, - 0b10 => { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - }, - } + for (self.base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, // Windows-only + .object, .archive, .dso_exact => continue, + .dso => |dso| { + const lib_as_needed = !dso.needed; + switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { + 0b00, 0b11 => {}, + 0b01 => { + argv.appendAssumeCapacity("--no-as-needed"); + as_needed = false; + }, + 0b10 => { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + }, + } - // By this time, we depend on these libs being dynamically linked - // libraries and not static libraries (the check for that needs to be earlier), - // but they could be full paths to .so files, in which case we - // want to avoid prepending "-l". - argv.appendAssumeCapacity(try lib_info.path.?.toString(arena)); - } + // By this time, we depend on these libs being dynamically linked + // libraries and not static libraries (the check for that needs to be earlier), + // but they could be full paths to .so files, in which case we + // want to avoid prepending "-l". + argv.appendAssumeCapacity(try dso.path.toString(arena)); + }, + }; if (!as_needed) { argv.appendAssumeCapacity("--as-needed"); diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index c88e95fec0..82f62356e9 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -2,13 +2,13 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?Path const gpa = comp.gpa; const diags = &comp.link_diags; - for (comp.objects) |obj| { - switch (Compilation.classifyFileExt(obj.path.sub_path)) { - .object => parseObjectStaticLibReportingFailure(elf_file, obj.path), - .static_library => parseArchiveStaticLibReportingFailure(elf_file, obj.path), - else => diags.addParseError(obj.path, "unrecognized file extension", .{}), - } - } + for (comp.link_inputs) |link_input| switch (link_input) { + .object => |obj| parseObjectStaticLibReportingFailure(elf_file, obj.path), + .archive => |obj| parseArchiveStaticLibReportingFailure(elf_file, obj.path), + .dso_exact => unreachable, + .res => unreachable, + .dso => unreachable, + }; for (comp.c_object_table.keys()) |key| { parseObjectStaticLibReportingFailure(elf_file, key.status.success.object_path); @@ -153,18 +153,18 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?Path pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void { const diags = &comp.link_diags; - for (comp.objects) |obj| { - elf_file.parseInputReportingFailure(obj.path, false, obj.must_link); + for (comp.link_inputs) |link_input| { + elf_file.parseInputReportingFailure(link_input); } // This is a set of object files emitted by clang in a single `build-exe` invocation. // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up // in this set. for (comp.c_object_table.keys()) |key| { - elf_file.parseObjectReportingFailure(key.status.success.object_path); + elf_file.openParseObjectReportingFailure(key.status.success.object_path); } - if (module_obj_path) |path| elf_file.parseObjectReportingFailure(path); + if (module_obj_path) |path| elf_file.openParseObjectReportingFailure(path); if (diags.hasErrors()) return error.FlushFailure; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 0563f0fb5e..4af02ee7a8 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1,3 +1,7 @@ +pub const Atom = @import("MachO/Atom.zig"); +pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); +pub const Relocation = @import("MachO/Relocation.zig"); + base: link.File, rpath_list: []const []const u8, @@ -114,8 +118,8 @@ headerpad_max_install_names: bool, dead_strip_dylibs: bool, /// Treatment of undefined symbols undefined_treatment: UndefinedTreatment, -/// Resolved list of library search directories -lib_dirs: []const []const u8, +/// TODO: delete this, libraries need to be resolved by the frontend instead +lib_directories: []const Directory, /// Resolved list of framework search directories framework_dirs: []const []const u8, /// List of input frameworks @@ -213,7 +217,8 @@ pub fn createEmpty( .platform = Platform.fromTarget(target), .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null, .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error", - .lib_dirs = options.lib_dirs, + // TODO delete this, directories must instead be resolved by the frontend + .lib_directories = options.lib_directories, .framework_dirs = options.framework_dirs, .force_load_objc = options.force_load_objc, }; @@ -371,48 +376,44 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); - var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); + var positionals = std.ArrayList(link.Input).init(gpa); defer positionals.deinit(); - try positionals.ensureUnusedCapacity(comp.objects.len); - positionals.appendSliceAssumeCapacity(comp.objects); + try positionals.ensureUnusedCapacity(comp.link_inputs.len); + + for (comp.link_inputs) |link_input| switch (link_input) { + .dso => continue, // handled below + .object, .archive => positionals.appendAssumeCapacity(link_input), + .dso_exact => @panic("TODO"), + .res => unreachable, + }; // This is a set of object files emitted by clang in a single `build-exe` invocation. // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up // in this set. try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len); for (comp.c_object_table.keys()) |key| { - positionals.appendAssumeCapacity(.{ .path = key.status.success.object_path }); + positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(.{ .path = path }); + if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); if (comp.config.any_sanitize_thread) { - try positionals.append(.{ .path = comp.tsan_lib.?.full_object_path }); + try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path)); } if (comp.config.any_fuzz) { - try positionals.append(.{ .path = comp.fuzzer_lib.?.full_object_path }); + try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path)); } - for (positionals.items) |obj| { - self.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err| - diags.addParseError(obj.path, "failed to read input file: {s}", .{@errorName(err)}); + for (positionals.items) |link_input| { + self.classifyInputFile(link_input) catch |err| + diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); } var system_libs = std.ArrayList(SystemLib).init(gpa); defer system_libs.deinit(); - // libs - try system_libs.ensureUnusedCapacity(comp.system_libs.values().len); - for (comp.system_libs.values()) |info| { - system_libs.appendAssumeCapacity(.{ - .needed = info.needed, - .weak = info.weak, - .path = info.path.?, - }); - } - // frameworks try system_libs.ensureUnusedCapacity(self.frameworks.len); for (self.frameworks) |info| { @@ -436,20 +437,24 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n else => |e| return e, // TODO: convert into an error }; - for (system_libs.items) |lib| { - self.classifyInputFile(lib.path, lib, false) catch |err| - diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); - } + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive, .dso_exact => continue, + .res => unreachable, + .dso => { + self.classifyInputFile(link_input) catch |err| + diags.addParseError(link_input.path().?, "failed to parse input file: {s}", .{@errorName(err)}); + }, + }; // Finally, link against compiler_rt. - const compiler_rt_path: ?Path = blk: { - if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; - if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - if (compiler_rt_path) |path| { - self.classifyInputFile(path, .{ .path = path }, false) catch |err| - diags.addParseError(path, "failed to parse input file: {s}", .{@errorName(err)}); + if (comp.compiler_rt_lib) |crt_file| { + const path = crt_file.full_object_path; + self.classifyInputFile(try link.openArchiveInput(diags, path)) catch |err| + diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); + } else if (comp.compiler_rt_obj) |crt_file| { + const path = crt_file.full_object_path; + self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| + diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } try self.parseInputFiles(); @@ -596,9 +601,12 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { } if (self.base.isRelocatable()) { - for (comp.objects) |obj| { - try argv.append(try obj.path.toString(arena)); - } + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), + .res => |res| try argv.append(try res.path.toString(arena)), + .dso => |dso| try argv.append(try dso.path.toString(arena)), + .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }), + }; for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); @@ -678,13 +686,15 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append("dynamic_lookup"); } - for (comp.objects) |obj| { - // TODO: verify this - if (obj.must_link) { - try argv.append("-force_load"); - } - try argv.append(try obj.path.toString(arena)); - } + for (comp.link_inputs) |link_input| switch (link_input) { + .dso => continue, // handled below + .res => unreachable, // windows only + .object, .archive => |obj| { + if (obj.must_link) try argv.append("-force_load"); // TODO: verify this + try argv.append(try obj.path.toString(arena)); + }, + .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }), + }; for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); @@ -703,21 +713,25 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); } - for (self.lib_dirs) |lib_dir| { - const arg = try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}); + for (self.lib_directories) |lib_directory| { + // TODO delete this, directories must instead be resolved by the frontend + const arg = try std.fmt.allocPrint(arena, "-L{s}", .{lib_directory.path orelse "."}); try argv.append(arg); } - for (comp.system_libs.keys()) |l_name| { - const info = comp.system_libs.get(l_name).?; - const arg = if (info.needed) - try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name}) - else if (info.weak) - try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name}) - else - try std.fmt.allocPrint(arena, "-l{s}", .{l_name}); - try argv.append(arg); - } + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive, .dso_exact => continue, // handled above + .res => unreachable, // windows only + .dso => |dso| { + if (dso.needed) { + try argv.appendSlice(&.{ "-needed-l", try dso.path.toString(arena) }); + } else if (dso.weak) { + try argv.appendSlice(&.{ "-weak-l", try dso.path.toString(arena) }); + } else { + try argv.appendSlice(&.{ "-l", try dso.path.toString(arena) }); + } + }, + }; for (self.framework_dirs) |f_dir| { try argv.append("-F"); @@ -751,6 +765,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { Compilation.dump_argv(argv.items); } +/// TODO delete this, libsystem must be resolved when setting up the compilationt pipeline pub fn resolveLibSystem( self: *MachO, arena: Allocator, @@ -774,8 +789,8 @@ pub fn resolveLibSystem( }, }; - for (self.lib_dirs) |dir| { - if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success; + for (self.lib_directories) |directory| { + if (try accessLibPath(arena, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success; } diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); @@ -789,13 +804,14 @@ pub fn resolveLibSystem( }); } -pub fn classifyInputFile(self: *MachO, path: Path, lib: SystemLib, must_link: bool) !void { +pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const tracy = trace(@src()); defer tracy.end(); + const path, const file = input.pathAndFile().?; + // TODO don't classify now, it's too late. The input file has already been classified log.debug("classifying input file {}", .{path}); - const file = try path.root_dir.handle.openFile(path.sub_path, .{}); const fh = try self.addFileHandle(file); var buffer: [Archive.SARMAG]u8 = undefined; @@ -806,17 +822,17 @@ pub fn classifyInputFile(self: *MachO, path: Path, lib: SystemLib, must_link: bo if (h.magic != macho.MH_MAGIC_64) break :blk; switch (h.filetype) { macho.MH_OBJECT => try self.addObject(path, fh, offset), - macho.MH_DYLIB => _ = try self.addDylib(lib, true, fh, offset), + macho.MH_DYLIB => _ = try self.addDylib(.fromLinkInput(input), true, fh, offset), else => return error.UnknownFileType, } return; } if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: { if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; - try self.addArchive(lib, must_link, fh, fat_arch); + try self.addArchive(input.archive, fh, fat_arch); return; } - _ = try self.addTbd(lib, true, fh); + _ = try self.addTbd(.fromLinkInput(input), true, fh); } fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch { @@ -903,7 +919,7 @@ fn parseInputFileWorker(self: *MachO, file: File) void { }; } -fn addArchive(self: *MachO, lib: SystemLib, must_link: bool, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void { +fn addArchive(self: *MachO, lib: link.Input.Object, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void { const tracy = trace(@src()); defer tracy.end(); @@ -918,7 +934,7 @@ fn addArchive(self: *MachO, lib: SystemLib, must_link: bool, handle: File.Handle self.files.set(index, .{ .object = unpacked }); const object = &self.files.items(.data)[index].object; object.index = index; - object.alive = must_link or lib.needed; // TODO: or self.options.all_load; + object.alive = lib.must_link; // TODO: or self.options.all_load; object.hidden = lib.hidden; try self.objects.append(gpa, index); } @@ -993,6 +1009,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool { return false; } +/// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline fn accessLibPath( arena: Allocator, test_path: *std.ArrayList(u8), @@ -1051,9 +1068,11 @@ fn parseDependentDylibs(self: *MachO) !void { if (self.dylibs.items.len == 0) return; const gpa = self.base.comp.gpa; - const lib_dirs = self.lib_dirs; const framework_dirs = self.framework_dirs; + // TODO delete this, directories must instead be resolved by the frontend + const lib_directories = self.lib_directories; + var arena_alloc = std.heap.ArenaAllocator.init(gpa); defer arena_alloc.deinit(); const arena = arena_alloc.allocator(); @@ -1094,9 +1113,9 @@ fn parseDependentDylibs(self: *MachO) !void { // Library const lib_name = eatPrefix(stem, "lib") orelse stem; - for (lib_dirs) |dir| { + for (lib_directories) |lib_directory| { test_path.clearRetainingCapacity(); - if (try accessLibPath(arena, &test_path, &checked_paths, dir, lib_name)) break :full_path test_path.items; + if (try accessLibPath(arena, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items; } } @@ -4366,6 +4385,24 @@ const SystemLib = struct { hidden: bool = false, reexport: bool = false, must_link: bool = false, + + fn fromLinkInput(link_input: link.Input) SystemLib { + return switch (link_input) { + .dso_exact => unreachable, + .res => unreachable, + .object, .archive => |obj| .{ + .path = obj.path, + .must_link = obj.must_link, + .hidden = obj.hidden, + }, + .dso => |dso| .{ + .path = dso.path, + .needed = dso.needed, + .weak = dso.weak, + .reexport = dso.reexport, + }, + }; + } }; pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout; @@ -5303,17 +5340,16 @@ const Air = @import("../Air.zig"); const Alignment = Atom.Alignment; const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); -pub const Atom = @import("MachO/Atom.zig"); const AtomicBool = std.atomic.Value(bool); const Bind = bind.Bind; const Cache = std.Build.Cache; -const Path = Cache.Path; const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); const DataInCode = synthetic.DataInCode; -pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); +const Directory = Cache.Directory; const Dylib = @import("MachO/Dylib.zig"); const ExportTrie = @import("MachO/dyld_info/Trie.zig"); +const Path = Cache.Path; const File = @import("MachO/file.zig").File; const GotSection = synthetic.GotSection; const Hash = std.hash.Wyhash; @@ -5329,7 +5365,6 @@ const Md5 = std.crypto.hash.Md5; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const Rebase = @import("MachO/dyld_info/Rebase.zig"); -pub const Relocation = @import("MachO/Relocation.zig"); const StringTable = @import("StringTable.zig"); const StubsSection = synthetic.StubsSection; const StubsHelperSection = synthetic.StubsHelperSection; diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index b27e82c793..497969ab90 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -3,16 +3,16 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat const diags = &macho_file.base.comp.link_diags; // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. - var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); + var positionals = std.ArrayList(link.Input).init(gpa); defer positionals.deinit(); - try positionals.ensureUnusedCapacity(comp.objects.len); - positionals.appendSliceAssumeCapacity(comp.objects); + try positionals.ensureUnusedCapacity(comp.link_inputs.len); + positionals.appendSliceAssumeCapacity(comp.link_inputs); for (comp.c_object_table.keys()) |key| { - try positionals.append(.{ .path = key.status.success.object_path }); + try positionals.append(try link.openObjectInput(diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(.{ .path = path }); + if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); if (macho_file.getZigObject() == null and positionals.items.len == 1) { // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all @@ -20,7 +20,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat // the *only* input file over. // TODO: in the future, when we implement `dsymutil` alternative directly in the Zig // compiler, investigate if we can get rid of this `if` prong here. - const path = positionals.items[0].path; + const path = positionals.items[0].path().?; const in_file = try path.root_dir.handle.openFile(path.sub_path, .{}); const stat = try in_file.stat(); const amt = try in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size); @@ -28,9 +28,9 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat return; } - for (positionals.items) |obj| { - macho_file.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err| - diags.addParseError(obj.path, "failed to read input file: {s}", .{@errorName(err)}); + for (positionals.items) |link_input| { + macho_file.classifyInputFile(link_input) catch |err| + diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); } if (diags.hasErrors()) return error.FlushFailure; @@ -72,25 +72,25 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? const gpa = comp.gpa; const diags = &macho_file.base.comp.link_diags; - var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); + var positionals = std.ArrayList(link.Input).init(gpa); defer positionals.deinit(); - try positionals.ensureUnusedCapacity(comp.objects.len); - positionals.appendSliceAssumeCapacity(comp.objects); + try positionals.ensureUnusedCapacity(comp.link_inputs.len); + positionals.appendSliceAssumeCapacity(comp.link_inputs); for (comp.c_object_table.keys()) |key| { - try positionals.append(.{ .path = key.status.success.object_path }); + try positionals.append(try link.openObjectInput(diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(.{ .path = path }); + if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); if (comp.include_compiler_rt) { - try positionals.append(.{ .path = comp.compiler_rt_obj.?.full_object_path }); + try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path)); } - for (positionals.items) |obj| { - macho_file.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err| - diags.addParseError(obj.path, "failed to read input file: {s}", .{@errorName(err)}); + for (positionals.items) |link_input| { + macho_file.classifyInputFile(link_input) catch |err| + diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); } if (diags.hasErrors()) return error.FlushFailure; @@ -745,20 +745,15 @@ fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { try macho_file.base.file.?.pwriteAll(mem.asBytes(&header), 0); } +const std = @import("std"); +const Path = std.Build.Cache.Path; +const WaitGroup = std.Thread.WaitGroup; const assert = std.debug.assert; -const build_options = @import("build_options"); -const eh_frame = @import("eh_frame.zig"); -const fat = @import("fat.zig"); -const link = @import("../../link.zig"); -const load_commands = @import("load_commands.zig"); const log = std.log.scoped(.link); const macho = std.macho; const math = std.math; const mem = std.mem; const state_log = std.log.scoped(.link_state); -const std = @import("std"); -const trace = @import("../../tracy.zig").trace; -const Path = std.Build.Cache.Path; const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); @@ -767,3 +762,9 @@ const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); +const build_options = @import("build_options"); +const eh_frame = @import("eh_frame.zig"); +const fat = @import("fat.zig"); +const link = @import("../../link.zig"); +const load_commands = @import("load_commands.zig"); +const trace = @import("../../tracy.zig").trace; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index df4131b7fe..daccd4eef5 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -637,14 +637,6 @@ fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) ! return loc; } -fn parseInputFiles(wasm: *Wasm, files: []const []const u8) !void { - for (files) |path| { - if (try wasm.parseObjectFile(path)) continue; - if (try wasm.parseArchive(path, false)) continue; // load archives lazily - log.warn("Unexpected file format at path: '{s}'", .{path}); - } -} - /// Parses the object file from given path. Returns true when the given file was an object /// file and parsed successfully. Returns false when file is not an object file. /// May return an error instead when parsing failed. @@ -2522,7 +2514,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no // Positional arguments to the linker such as object files and static archives. // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. var positionals = std.ArrayList([]const u8).init(arena); - try positionals.ensureUnusedCapacity(comp.objects.len); + try positionals.ensureUnusedCapacity(comp.link_inputs.len); const target = comp.root_mod.resolved_target.result; const output_mode = comp.config.output_mode; @@ -2566,9 +2558,12 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no try positionals.append(path); } - for (comp.objects) |object| { - try positionals.append(try object.path.toString(arena)); - } + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive => |obj| try positionals.append(try obj.path.toString(arena)), + .dso => |dso| try positionals.append(try dso.path.toString(arena)), + .dso_exact => unreachable, // forbidden by frontend + .res => unreachable, // windows only + }; for (comp.c_object_table.keys()) |c_object| { try positionals.append(try c_object.status.success.object_path.toString(arena)); @@ -2577,7 +2572,11 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no if (comp.compiler_rt_lib) |lib| try positionals.append(try lib.full_object_path.toString(arena)); if (comp.compiler_rt_obj) |obj| try positionals.append(try obj.full_object_path.toString(arena)); - try wasm.parseInputFiles(positionals.items); + for (positionals.items) |path| { + if (try wasm.parseObjectFile(path)) continue; + if (try wasm.parseArchive(path, false)) continue; // load archives lazily + log.warn("Unexpected file format at path: '{s}'", .{path}); + } if (wasm.zig_object_index != .null) { try wasm.resolveSymbolsInObject(wasm.zig_object_index); @@ -3401,10 +3400,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: comptime assert(Compilation.link_hash_implementation_version == 14); - for (comp.objects) |obj| { - _ = try man.addFilePath(obj.path, null); - man.hash.add(obj.must_link); - } + try link.hashInputs(&man, comp.link_inputs); for (comp.c_object_table.keys()) |key| { _ = try man.addFilePath(key.status.success.object_path, null); } @@ -3458,8 +3454,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: // here. TODO: think carefully about how we can avoid this redundant operation when doing // build-obj. See also the corresponding TODO in linkAsArchive. const the_object_path = blk: { - if (comp.objects.len != 0) - break :blk comp.objects[0].path; + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; @@ -3621,16 +3616,23 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: // Positional arguments to the linker such as object files. var whole_archive = false; - for (comp.objects) |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - try argv.append(try obj.path.toString(arena)); - } + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso => |dso| { + try argv.append(try dso.path.toString(arena)); + }, + .dso_exact => unreachable, + .res => unreachable, + }; if (whole_archive) { try argv.append("-no-whole-archive"); whole_archive = false; diff --git a/src/main.zig b/src/main.zig index c68e4b7562..39be246b1d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -15,6 +15,7 @@ const cleanExit = std.process.cleanExit; const native_os = builtin.os.tag; const Cache = std.Build.Cache; const Path = std.Build.Cache.Path; +const Directory = std.Build.Cache.Directory; const EnvVar = std.zig.EnvVar; const LibCInstallation = std.zig.LibCInstallation; const AstGen = std.zig.AstGen; @@ -55,7 +56,7 @@ pub fn wasi_cwd() std.os.wasi.fd_t { return cwd_fd; } -fn getWasiPreopen(name: []const u8) Compilation.Directory { +fn getWasiPreopen(name: []const u8) Directory { return .{ .path = name, .handle = .{ @@ -768,27 +769,6 @@ const ArgsIterator = struct { } }; -/// In contrast to `link.SystemLib`, this stores arguments that may need to be -/// resolved into static libraries so that we can pass only dynamic libraries -/// as system libs to `Compilation`. -const SystemLib = struct { - needed: bool, - weak: bool, - - preferred_mode: std.builtin.LinkMode, - search_strategy: SearchStrategy, - - const SearchStrategy = enum { paths_first, mode_first, no_fallback }; - - fn fallbackMode(this: SystemLib) std.builtin.LinkMode { - assert(this.search_strategy != .no_fallback); - return switch (this.preferred_mode) { - .dynamic => .static, - .static => .dynamic, - }; - } -}; - /// Similar to `link.Framework` except it doesn't store yet unresolved /// path to the framework. const Framework = struct { @@ -869,6 +849,7 @@ fn buildOutputType( var linker_gc_sections: ?bool = null; var linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null; var linker_allow_shlib_undefined: ?bool = null; + var allow_so_scripts: bool = false; var linker_bind_global_refs_locally: ?bool = null; var linker_import_symbols: bool = false; var linker_import_table: bool = false; @@ -921,7 +902,7 @@ fn buildOutputType( var hash_style: link.File.Elf.HashStyle = .both; var entitlements: ?[]const u8 = null; var pagezero_size: ?u64 = null; - var lib_search_strategy: SystemLib.SearchStrategy = .paths_first; + var lib_search_strategy: link.UnresolvedInput.SearchStrategy = .paths_first; var lib_preferred_mode: std.builtin.LinkMode = .dynamic; var headerpad_size: ?u32 = null; var headerpad_max_install_names: bool = false; @@ -985,8 +966,10 @@ fn buildOutputType( // Populated in the call to `createModule` for the root module. .resolved_options = undefined, - .system_libs = .{}, - .resolved_system_libs = .{}, + .cli_link_inputs = .empty, + .windows_libs = .empty, + .link_inputs = .empty, + .wasi_emulated_libs = .{}, .c_source_files = .{}, @@ -994,7 +977,7 @@ fn buildOutputType( .llvm_m_args = .{}, .sysroot = null, - .lib_dirs = .{}, // populated by createModule() + .lib_directories = .{}, // populated by createModule() .lib_dir_args = .{}, // populated from CLI arg parsing .libc_installation = null, .want_native_include_dirs = false, @@ -1003,9 +986,7 @@ fn buildOutputType( .rpath_list = .{}, .each_lib_rpath = null, .libc_paths_file = try EnvVar.ZIG_LIBC.get(arena), - .link_objects = .{}, .native_system_include_paths = &.{}, - .allow_so_scripts = false, }; // before arg parsing, check for the NO_COLOR and CLICOLOR_FORCE environment variables @@ -1240,30 +1221,42 @@ fn buildOutputType( // We don't know whether this library is part of libc // or libc++ until we resolve the target, so we append // to the list for now. - try create_module.system_libs.put(arena, args_iter.nextOrFatal(), .{ - .needed = false, - .weak = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = args_iter.nextOrFatal(), + .query = .{ + .needed = false, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l") or mem.eql(u8, arg, "-needed_library")) { const next_arg = args_iter.nextOrFatal(); - try create_module.system_libs.put(arena, next_arg, .{ - .needed = true, - .weak = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = next_arg, + .query = .{ + .needed = true, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) { - try create_module.system_libs.put(arena, args_iter.nextOrFatal(), .{ - .needed = false, - .weak = true, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = args_iter.nextOrFatal(), + .query = .{ + .needed = false, + .weak = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.eql(u8, arg, "-D")) { try cc_argv.appendSlice(arena, &.{ arg, args_iter.nextOrFatal() }); } else if (mem.eql(u8, arg, "-I")) { @@ -1577,9 +1570,9 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-fno-allow-shlib-undefined")) { linker_allow_shlib_undefined = false; } else if (mem.eql(u8, arg, "-fallow-so-scripts")) { - create_module.allow_so_scripts = true; + allow_so_scripts = true; } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) { - create_module.allow_so_scripts = false; + allow_so_scripts = false; } else if (mem.eql(u8, arg, "-z")) { const z_arg = args_iter.nextOrFatal(); if (mem.eql(u8, z_arg, "nodelete")) { @@ -1687,26 +1680,38 @@ fn buildOutputType( // We don't know whether this library is part of libc // or libc++ until we resolve the target, so we append // to the list for now. - try create_module.system_libs.put(arena, arg["-l".len..], .{ - .needed = false, - .weak = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = arg["-l".len..], + .query = .{ + .needed = false, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.startsWith(u8, arg, "-needed-l")) { - try create_module.system_libs.put(arena, arg["-needed-l".len..], .{ - .needed = true, - .weak = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = arg["-needed-l".len..], + .query = .{ + .needed = true, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.startsWith(u8, arg, "-weak-l")) { - try create_module.system_libs.put(arena, arg["-weak-l".len..], .{ - .needed = false, - .weak = true, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = arg["-weak-l".len..], + .query = .{ + .needed = false, + .weak = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.startsWith(u8, arg, "-D")) { try cc_argv.append(arena, arg); } else if (mem.startsWith(u8, arg, "-I")) { @@ -1731,15 +1736,28 @@ fn buildOutputType( fatal("unrecognized parameter: '{s}'", .{arg}); } } else switch (file_ext orelse Compilation.classifyFileExt(arg)) { - .shared_library => { - try create_module.link_objects.append(arena, .{ .path = Path.initCwd(arg) }); - create_module.opts.any_dyn_libs = true; - }, - .object, .static_library => { - try create_module.link_objects.append(arena, .{ .path = Path.initCwd(arg) }); + .shared_library, .object, .static_library => { + try create_module.cli_link_inputs.append(arena, .{ .path_query = .{ + .path = Path.initCwd(arg), + .query = .{ + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); + // We do not set `any_dyn_libs` yet because a .so file + // may actually resolve to a GNU ld script which ends + // up being a static library. }, .res => { - try create_module.link_objects.append(arena, .{ .path = Path.initCwd(arg) }); + try create_module.cli_link_inputs.append(arena, .{ .path_query = .{ + .path = Path.initCwd(arg), + .query = .{ + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); contains_res_file = true; }, .manifest => { @@ -1792,6 +1810,7 @@ fn buildOutputType( // some functionality that depend on it, such as C++ exceptions and // DWARF-based stack traces. link_eh_frame_hdr = true; + allow_so_scripts = true; const COutMode = enum { link, @@ -1851,24 +1870,32 @@ fn buildOutputType( .ext = file_ext, // duped while parsing the args. }); }, - .shared_library => { - try create_module.link_objects.append(arena, .{ + .unknown, .object, .static_library, .shared_library => { + try create_module.cli_link_inputs.append(arena, .{ .path_query = .{ .path = Path.initCwd(it.only_arg), - .must_link = must_link, - }); - create_module.opts.any_dyn_libs = true; - }, - .unknown, .object, .static_library => { - try create_module.link_objects.append(arena, .{ - .path = Path.initCwd(it.only_arg), - .must_link = must_link, - }); + .query = .{ + .must_link = must_link, + .needed = needed, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); + // We do not set `any_dyn_libs` yet because a .so file + // may actually resolve to a GNU ld script which ends + // up being a static library. }, .res => { - try create_module.link_objects.append(arena, .{ + try create_module.cli_link_inputs.append(arena, .{ .path_query = .{ .path = Path.initCwd(it.only_arg), - .must_link = must_link, - }); + .query = .{ + .must_link = must_link, + .needed = needed, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); contains_res_file = true; }, .manifest => { @@ -1900,19 +1927,21 @@ fn buildOutputType( // -l :path/to/filename is used when callers need // more control over what's in the resulting // binary: no extra rpaths and DSO filename exactly - // as provided. Hello, Go. - try create_module.link_objects.append(arena, .{ - .path = Path.initCwd(it.only_arg), - .must_link = must_link, - .loption = true, - }); + // as provided. CGo compilation depends on this. + try create_module.cli_link_inputs.append(arena, .{ .dso_exact = .{ + .name = it.only_arg, + } }); } else { - try create_module.system_libs.put(arena, it.only_arg, .{ - .needed = needed, - .weak = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = it.only_arg, + .query = .{ + .needed = needed, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } }, .ignore => {}, @@ -2181,12 +2210,16 @@ fn buildOutputType( }, .force_load_objc => force_load_objc = true, .mingw_unicode_entry_point => mingw_unicode_entry_point = true, - .weak_library => try create_module.system_libs.put(arena, it.only_arg, .{ - .needed = false, - .weak = true, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }), + .weak_library => try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = it.only_arg, + .query = .{ + .needed = false, + .weak = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }), .weak_framework => try create_module.frameworks.put(arena, it.only_arg, .{ .weak = true }), .headerpad_max_install_names => headerpad_max_install_names = true, .compress_debug_sections => { @@ -2489,26 +2522,38 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-needed_framework")) { try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .needed = true }); } else if (mem.eql(u8, arg, "-needed_library")) { - try create_module.system_libs.put(arena, linker_args_it.nextOrFatal(), .{ - .weak = false, - .needed = true, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = linker_args_it.nextOrFatal(), + .query = .{ + .weak = false, + .needed = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.startsWith(u8, arg, "-weak-l")) { - try create_module.system_libs.put(arena, arg["-weak-l".len..], .{ - .weak = true, - .needed = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = arg["-weak-l".len..], + .query = .{ + .weak = true, + .needed = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.eql(u8, arg, "-weak_library")) { - try create_module.system_libs.put(arena, linker_args_it.nextOrFatal(), .{ - .weak = true, - .needed = false, - .preferred_mode = lib_preferred_mode, - .search_strategy = lib_search_strategy, - }); + try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ + .name = linker_args_it.nextOrFatal(), + .query = .{ + .weak = true, + .needed = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + .allow_so_scripts = allow_so_scripts, + }, + } }); } else if (mem.eql(u8, arg, "-compatibility_version")) { const compat_version = linker_args_it.nextOrFatal(); compatibility_version = std.SemanticVersion.parse(compat_version) catch |err| { @@ -2539,10 +2584,14 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-install_name")) { install_name = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "-force_load")) { - try create_module.link_objects.append(arena, .{ + try create_module.cli_link_inputs.append(arena, .{ .path_query = .{ .path = Path.initCwd(linker_args_it.nextOrFatal()), - .must_link = true, - }); + .query = .{ + .must_link = true, + .preferred_mode = .static, + .search_strategy = .no_fallback, + }, + } }); } else if (mem.eql(u8, arg, "-hash-style") or mem.eql(u8, arg, "--hash-style")) { @@ -2672,7 +2721,7 @@ fn buildOutputType( }, } if (create_module.c_source_files.items.len == 0 and - create_module.link_objects.items.len == 0 and + !link.anyObjectInputs(create_module.link_inputs.items) and root_src_file == null) { // For example `zig cc` and no args should print the "no input files" message. @@ -2714,8 +2763,9 @@ fn buildOutputType( if (create_module.c_source_files.items.len >= 1) break :b create_module.c_source_files.items[0].src_path; - if (create_module.link_objects.items.len >= 1) - break :b create_module.link_objects.items[0].path.sub_path; + for (create_module.link_inputs.items) |link_input| { + if (link_input.path()) |path| break :b path.sub_path; + } if (emit_bin == .yes) break :b emit_bin.yes; @@ -2801,7 +2851,7 @@ fn buildOutputType( fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); }; - var zig_lib_directory: Compilation.Directory = d: { + var zig_lib_directory: Directory = d: { if (override_lib_dir) |unresolved_lib_dir| { const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir); break :d .{ @@ -2822,7 +2872,7 @@ fn buildOutputType( }; defer zig_lib_directory.handle.close(); - var global_cache_directory: Compilation.Directory = l: { + var global_cache_directory: Directory = l: { if (override_global_cache_dir) |p| { break :l .{ .handle = try fs.cwd().makeOpenPath(p, .{}), @@ -2852,7 +2902,7 @@ fn buildOutputType( var builtin_modules: std.StringHashMapUnmanaged(*Package.Module) = .empty; // `builtin_modules` allocated into `arena`, so no deinit - const main_mod = try createModule(gpa, arena, &create_module, 0, null, zig_lib_directory, &builtin_modules); + const main_mod = try createModule(gpa, arena, &create_module, 0, null, zig_lib_directory, &builtin_modules, color); for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| { if (cli_mod.resolved == null) fatal("module '{s}' declared but not used", .{key}); @@ -2946,7 +2996,6 @@ fn buildOutputType( } } - // We now repeat part of the process for frameworks. var resolved_frameworks = std.ArrayList(Compilation.Framework).init(arena); if (create_module.frameworks.keys().len > 0) { @@ -3003,7 +3052,7 @@ fn buildOutputType( const total_obj_count = create_module.c_source_files.items.len + @intFromBool(root_src_file != null) + create_module.rc_source_files.items.len + - create_module.link_objects.items.len; + link.countObjectInputs(create_module.link_inputs.items); if (total_obj_count > 1) { fatal("{s} does not support linking multiple objects into one", .{@tagName(target.ofmt)}); } @@ -3219,7 +3268,7 @@ fn buildOutputType( var cleanup_local_cache_dir: ?fs.Dir = null; defer if (cleanup_local_cache_dir) |*dir| dir.close(); - var local_cache_directory: Compilation.Directory = l: { + var local_cache_directory: Directory = l: { if (override_local_cache_dir) |local_cache_dir_path| { const dir = try fs.cwd().makeOpenPath(local_cache_dir_path, .{}); cleanup_local_cache_dir = dir; @@ -3356,7 +3405,7 @@ fn buildOutputType( .emit_llvm_bc = emit_llvm_bc_resolved.data, .emit_docs = emit_docs_resolved.data, .emit_implib = emit_implib_resolved.data, - .lib_dirs = create_module.lib_dirs.items, + .lib_directories = create_module.lib_directories.items, .rpath_list = create_module.rpath_list.items, .symbol_wrap_set = symbol_wrap_set, .c_source_files = create_module.c_source_files.items, @@ -3364,11 +3413,10 @@ fn buildOutputType( .manifest_file = manifest_file, .rc_includes = rc_includes, .mingw_unicode_entry_point = mingw_unicode_entry_point, - .link_objects = create_module.link_objects.items, + .link_inputs = create_module.link_inputs.items, .framework_dirs = create_module.framework_dirs.items, .frameworks = resolved_frameworks.items, - .system_lib_names = create_module.resolved_system_libs.items(.name), - .system_lib_infos = create_module.resolved_system_libs.items(.lib), + .windows_lib_names = create_module.windows_libs.keys(), .wasi_emulated_libs = create_module.wasi_emulated_libs.items, .want_compiler_rt = want_compiler_rt, .hash_style = hash_style, @@ -3625,28 +3673,6 @@ fn buildOutputType( return cleanExit(); } -const LinkerInput = union(enum) { - /// An argument like: -l[name] - named: Named, - /// When a file path is provided. - path: struct { - path: Path, - /// We still need all this info because the path may point to a .so - /// file which may actually be a "linker script" that references - /// library names which need to be resolved. - info: SystemLib, - }, - /// Put exactly this string in the dynamic section, no rpath. - exact: struct { - name: []const u8, - }, - - const Named = struct { - name: []const u8, - info: SystemLib, - }; -}; - const CreateModule = struct { global_cache_directory: Cache.Directory, modules: std.StringArrayHashMapUnmanaged(CliModule), @@ -3659,12 +3685,14 @@ const CreateModule = struct { /// This one is used while collecting CLI options. The set of libs is used /// directly after computing the target and used to compute link_libc, /// link_libcpp, and then the libraries are filtered into - /// `external_system_libs` and `resolved_system_libs`. - system_libs: std.StringArrayHashMapUnmanaged(SystemLib), - resolved_system_libs: std.MultiArrayList(struct { - name: []const u8, - lib: Compilation.SystemLib, - }), + /// `unresolved_linker_inputs` and `windows_libs`. + cli_link_inputs: std.ArrayListUnmanaged(link.UnresolvedInput), + windows_libs: std.StringArrayHashMapUnmanaged(void), + /// The local variable `unresolved_link_inputs` is fed into library + /// resolution, mutating the input array, and producing this data as + /// output. Allocated with gpa. + link_inputs: std.ArrayListUnmanaged(link.Input), + wasi_emulated_libs: std.ArrayListUnmanaged(wasi_libc.CrtFile), c_source_files: std.ArrayListUnmanaged(Compilation.CSourceFile), @@ -3675,7 +3703,7 @@ const CreateModule = struct { /// CPU features. llvm_m_args: std.ArrayListUnmanaged([]const u8), sysroot: ?[]const u8, - lib_dirs: std.ArrayListUnmanaged([]const u8), + lib_directories: std.ArrayListUnmanaged(Directory), lib_dir_args: std.ArrayListUnmanaged([]const u8), libc_installation: ?LibCInstallation, want_native_include_dirs: bool, @@ -3685,8 +3713,6 @@ const CreateModule = struct { rpath_list: std.ArrayListUnmanaged([]const u8), each_lib_rpath: ?bool, libc_paths_file: ?[]const u8, - link_objects: std.ArrayListUnmanaged(Compilation.LinkObject), - allow_so_scripts: bool, }; fn createModule( @@ -3697,6 +3723,7 @@ fn createModule( parent: ?*Package.Module, zig_lib_directory: Cache.Directory, builtin_modules: *std.StringHashMapUnmanaged(*Package.Module), + color: std.zig.Color, ) Allocator.Error!*Package.Module { const cli_mod = &create_module.modules.values()[index]; if (cli_mod.resolved) |m| return m; @@ -3790,82 +3817,101 @@ fn createModule( // First, remove libc, libc++, and compiler_rt libraries from the system libraries list. // We need to know whether the set of system libraries contains anything besides these // to decide whether to trigger native path detection logic. - var external_linker_inputs: std.ArrayListUnmanaged(LinkerInput) = .empty; - for (create_module.system_libs.keys(), create_module.system_libs.values()) |lib_name, info| { - if (std.zig.target.isLibCLibName(target, lib_name)) { - create_module.opts.link_libc = true; - continue; - } - if (std.zig.target.isLibCxxLibName(target, lib_name)) { - create_module.opts.link_libcpp = true; - continue; - } - switch (target_util.classifyCompilerRtLibName(target, lib_name)) { - .none => {}, - .only_libunwind, .both => { - create_module.opts.link_libunwind = true; - continue; - }, - .only_compiler_rt => { - warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name}); - continue; - }, - } - - if (target.isMinGW()) { - const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| { - fatal("failed to check zig installation for DLL import libs: {s}", .{ - @errorName(err), - }); - }; - if (exists) { - try create_module.resolved_system_libs.append(arena, .{ - .name = lib_name, - .lib = .{ - .needed = true, - .weak = false, - .path = null, - }, - }); + // Preserves linker input order. + var unresolved_link_inputs: std.ArrayListUnmanaged(link.UnresolvedInput) = .empty; + try unresolved_link_inputs.ensureUnusedCapacity(arena, create_module.cli_link_inputs.items.len); + var any_name_queries_remaining = false; + for (create_module.cli_link_inputs.items) |cli_link_input| switch (cli_link_input) { + .name_query => |nq| { + const lib_name = nq.name; + if (std.zig.target.isLibCLibName(target, lib_name)) { + create_module.opts.link_libc = true; continue; } - } - - if (fs.path.isAbsolute(lib_name)) { - fatal("cannot use absolute path as a system library: {s}", .{lib_name}); - } - - if (target.os.tag == .wasi) { - if (wasi_libc.getEmulatedLibCrtFile(lib_name)) |crt_file| { - try create_module.wasi_emulated_libs.append(arena, crt_file); + if (std.zig.target.isLibCxxLibName(target, lib_name)) { + create_module.opts.link_libcpp = true; continue; } - } + switch (target_util.classifyCompilerRtLibName(target, lib_name)) { + .none => {}, + .only_libunwind, .both => { + create_module.opts.link_libunwind = true; + continue; + }, + .only_compiler_rt => { + warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name}); + continue; + }, + } - try external_linker_inputs.append(arena, .{ .named = .{ - .name = lib_name, - .info = info, - } }); - } - // After this point, external_linker_inputs is used instead of system_libs. - if (external_linker_inputs.items.len != 0) - create_module.want_native_include_dirs = true; + if (target.isMinGW()) { + const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| { + fatal("failed to check zig installation for DLL import libs: {s}", .{ + @errorName(err), + }); + }; + if (exists) { + try create_module.windows_libs.put(arena, lib_name, {}); + continue; + } + } + + if (fs.path.isAbsolute(lib_name)) { + fatal("cannot use absolute path as a system library: {s}", .{lib_name}); + } + + if (target.os.tag == .wasi) { + if (wasi_libc.getEmulatedLibCrtFile(lib_name)) |crt_file| { + try create_module.wasi_emulated_libs.append(arena, crt_file); + continue; + } + } + unresolved_link_inputs.appendAssumeCapacity(cli_link_input); + any_name_queries_remaining = true; + }, + else => { + unresolved_link_inputs.appendAssumeCapacity(cli_link_input); + }, + }; // After this point, unresolved_link_inputs is used instead of cli_link_inputs. + + if (any_name_queries_remaining) create_module.want_native_include_dirs = true; // Resolve the library path arguments with respect to sysroot. + try create_module.lib_directories.ensureUnusedCapacity(arena, create_module.lib_dir_args.items.len); if (create_module.sysroot) |root| { - try create_module.lib_dirs.ensureUnusedCapacity(arena, create_module.lib_dir_args.items.len * 2); - for (create_module.lib_dir_args.items) |dir| { - if (fs.path.isAbsolute(dir)) { - const stripped_dir = dir[fs.path.diskDesignator(dir).len..]; + for (create_module.lib_dir_args.items) |lib_dir_arg| { + if (fs.path.isAbsolute(lib_dir_arg)) { + const stripped_dir = lib_dir_arg[fs.path.diskDesignator(lib_dir_arg).len..]; const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir }); - create_module.lib_dirs.appendAssumeCapacity(full_path); + create_module.lib_directories.appendAssumeCapacity(.{ + .handle = fs.cwd().openDir(full_path, .{}) catch |err| { + warn("unable to open library directory {s}: {s}", .{ full_path, @errorName(err) }); + continue; + }, + .path = full_path, + }); + } else { + create_module.lib_directories.appendAssumeCapacity(.{ + .handle = fs.cwd().openDir(lib_dir_arg, .{}) catch |err| { + warn("unable to open library directory {s}: {s}", .{ lib_dir_arg, @errorName(err) }); + continue; + }, + .path = lib_dir_arg, + }); } - create_module.lib_dirs.appendAssumeCapacity(dir); } } else { - create_module.lib_dirs = create_module.lib_dir_args; + for (create_module.lib_dir_args.items) |lib_dir_arg| { + create_module.lib_directories.appendAssumeCapacity(.{ + .handle = fs.cwd().openDir(lib_dir_arg, .{}) catch |err| { + warn("unable to open library directory {s}: {s}", .{ lib_dir_arg, @errorName(err) }); + continue; + }, + .path = lib_dir_arg, + }); + } } - create_module.lib_dir_args = undefined; // From here we use lib_dirs instead. + create_module.lib_dir_args = undefined; // From here we use lib_directories instead. if (resolved_target.is_native_os and target.isDarwin()) { // If we want to link against frameworks, we need system headers. @@ -3874,7 +3920,10 @@ fn createModule( } if (create_module.each_lib_rpath orelse resolved_target.is_native_os) { - try create_module.rpath_list.appendSlice(arena, create_module.lib_dirs.items); + try create_module.rpath_list.ensureUnusedCapacity(arena, create_module.lib_directories.items.len); + for (create_module.lib_directories.items) |lib_directory| { + create_module.rpath_list.appendAssumeCapacity(lib_directory.path.?); + } } // Trigger native system library path detection if necessary. @@ -3892,8 +3941,18 @@ fn createModule( create_module.native_system_include_paths = try paths.include_dirs.toOwnedSlice(arena); try create_module.framework_dirs.appendSlice(arena, paths.framework_dirs.items); - try create_module.lib_dirs.appendSlice(arena, paths.lib_dirs.items); try create_module.rpath_list.appendSlice(arena, paths.rpaths.items); + + try create_module.lib_directories.ensureUnusedCapacity(arena, paths.lib_dirs.items.len); + for (paths.lib_dirs.items) |lib_dir| { + create_module.lib_directories.appendAssumeCapacity(.{ + .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { + warn("unable to open library directory {s}: {s}", .{ lib_dir, @errorName(err) }); + continue; + }, + .path = lib_dir, + }); + } } if (create_module.libc_paths_file) |paths_file| { @@ -3905,7 +3964,7 @@ fn createModule( } if (builtin.target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and - external_linker_inputs.items.len != 0) + any_name_queries_remaining) { if (create_module.libc_installation == null) { create_module.libc_installation = LibCInstallation.findNative(.{ @@ -3916,204 +3975,32 @@ fn createModule( fatal("unable to find native libc installation: {s}", .{@errorName(err)}); }; - try create_module.lib_dirs.appendSlice(arena, &.{ + try create_module.lib_directories.appendSlice(arena, &.{ create_module.libc_installation.?.msvc_lib_dir.?, create_module.libc_installation.?.kernel32_lib_dir.?, }); } } - // If any libs in this list are statically provided, we omit them from the - // resolved list and populate the link_objects array instead. - { - var test_path: std.ArrayListUnmanaged(u8) = .empty; - defer test_path.deinit(gpa); + // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`. + link.resolveInputs( + gpa, + arena, + target, + &unresolved_link_inputs, + &create_module.link_inputs, + create_module.lib_directories.items, + color, + ) catch |err| fatal("failed to resolve link inputs: {s}", .{@errorName(err)}); - var checked_paths: std.ArrayListUnmanaged(u8) = .empty; - defer checked_paths.deinit(gpa); - - var ld_script_bytes: std.ArrayListUnmanaged(u8) = .empty; - defer ld_script_bytes.deinit(gpa); - - var failed_libs: std.ArrayListUnmanaged(struct { - name: []const u8, - strategy: SystemLib.SearchStrategy, - checked_paths: []const u8, - preferred_mode: std.builtin.LinkMode, - }) = .empty; - - // Convert external system libs into a stack so that items can be - // pushed to it. - // - // This is necessary because shared objects might turn out to be - // "linker scripts" that in fact resolve to one or more other - // external system libs, including parameters such as "needed". - // - // Unfortunately, such files need to be detected immediately, so - // that this library search logic can be applied to them. - mem.reverse(LinkerInput, external_linker_inputs.items); - - syslib: while (external_linker_inputs.popOrNull()) |external_linker_input| { - const external_system_lib: LinkerInput.Named = switch (external_linker_input) { - .named => |named| named, - .path => |p| p: { - if (fs.path.isAbsolute(p.path.sub_path)) { - try create_module.link_objects.append(arena, .{ - .path = p.path, - .needed = p.info.needed, - .weak = p.info.weak, - }); - continue; - } - const lib_name, const link_mode = stripLibPrefixAndSuffix(p.path.sub_path, target); - break :p .{ - .name = lib_name, - .info = .{ - .needed = p.info.needed, - .weak = p.info.weak, - .preferred_mode = link_mode, - .search_strategy = .no_fallback, - }, - }; - }, - .exact => |exact| { - try create_module.link_objects.append(arena, .{ - .path = Path.initCwd(exact.name), - .loption = true, - }); - continue; - }, - }; - const lib_name = external_system_lib.name; - const info = external_system_lib.info; - - // Checked in the first pass above while looking for libc libraries. - assert(!fs.path.isAbsolute(lib_name)); - - checked_paths.clearRetainingCapacity(); - - switch (info.search_strategy) { - .mode_first, .no_fallback => { - // check for preferred mode - for (create_module.lib_dirs.items) |lib_dir_path| switch (try accessLibPath( - gpa, - arena, - &test_path, - &checked_paths, - &external_linker_inputs, - create_module, - &ld_script_bytes, - lib_dir_path, - lib_name, - target, - info.preferred_mode, - info, - )) { - .ok => continue :syslib, - .no_match => {}, - }; - // check for fallback mode - if (info.search_strategy == .no_fallback) { - try failed_libs.append(arena, .{ - .name = lib_name, - .strategy = info.search_strategy, - .checked_paths = try arena.dupe(u8, checked_paths.items), - .preferred_mode = info.preferred_mode, - }); - continue :syslib; - } - for (create_module.lib_dirs.items) |lib_dir_path| switch (try accessLibPath( - gpa, - arena, - &test_path, - &checked_paths, - &external_linker_inputs, - create_module, - &ld_script_bytes, - lib_dir_path, - lib_name, - target, - info.fallbackMode(), - info, - )) { - .ok => continue :syslib, - .no_match => {}, - }; - try failed_libs.append(arena, .{ - .name = lib_name, - .strategy = info.search_strategy, - .checked_paths = try arena.dupe(u8, checked_paths.items), - .preferred_mode = info.preferred_mode, - }); - continue :syslib; - }, - .paths_first => { - for (create_module.lib_dirs.items) |lib_dir_path| { - // check for preferred mode - switch (try accessLibPath( - gpa, - arena, - &test_path, - &checked_paths, - &external_linker_inputs, - create_module, - &ld_script_bytes, - lib_dir_path, - lib_name, - target, - info.preferred_mode, - info, - )) { - .ok => continue :syslib, - .no_match => {}, - } - - // check for fallback mode - switch (try accessLibPath( - gpa, - arena, - &test_path, - &checked_paths, - &external_linker_inputs, - create_module, - &ld_script_bytes, - lib_dir_path, - lib_name, - target, - info.fallbackMode(), - info, - )) { - .ok => continue :syslib, - .no_match => {}, - } - } - try failed_libs.append(arena, .{ - .name = lib_name, - .strategy = info.search_strategy, - .checked_paths = try arena.dupe(u8, checked_paths.items), - .preferred_mode = info.preferred_mode, - }); - continue :syslib; - }, - } - @compileError("unreachable"); - } - - if (failed_libs.items.len > 0) { - for (failed_libs.items) |f| { - const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; - std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{ - @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths, - }); - } - process.exit(1); - } - } - // After this point, create_module.resolved_system_libs is used instead - // of external_linker_inputs. - - if (create_module.resolved_system_libs.len != 0) - create_module.opts.any_dyn_libs = true; + if (create_module.windows_libs.count() != 0) create_module.opts.any_dyn_libs = true; + if (!create_module.opts.any_dyn_libs) for (create_module.link_inputs.items) |item| switch (item) { + .dso, .dso_exact => { + create_module.opts.any_dyn_libs = true; + break; + }, + else => {}, + }; create_module.resolved_options = Compilation.Config.resolve(create_module.opts) catch |err| switch (err) { error.WasiExecModelRequiresWasi => fatal("only WASI OS targets support execution model", .{}), @@ -4181,7 +4068,7 @@ fn createModule( for (cli_mod.deps) |dep| { const dep_index = create_module.modules.getIndex(dep.value) orelse fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key }); - const dep_mod = try createModule(gpa, arena, create_module, dep_index, mod, zig_lib_directory, builtin_modules); + const dep_mod = try createModule(gpa, arena, create_module, dep_index, mod, zig_lib_directory, builtin_modules, color); try mod.deps.put(arena, dep.key, dep_mod); } @@ -5046,7 +4933,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { process.raiseFileDescriptorLimit(); - var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| .{ + var zig_lib_directory: Directory = if (override_lib_dir) |lib_dir| .{ .path = lib_dir, .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { fatal("unable to open zig lib directory from 'zig-lib-dir' argument: '{s}': {s}", .{ lib_dir, @errorName(err) }); @@ -5065,7 +4952,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { }); child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path; - var global_cache_directory: Compilation.Directory = l: { + var global_cache_directory: Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); break :l .{ .handle = try fs.cwd().makeOpenPath(p, .{}), @@ -5076,7 +4963,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { child_argv.items[argv_index_global_cache_dir] = global_cache_directory.path orelse cwd_path; - var local_cache_directory: Compilation.Directory = l: { + var local_cache_directory: Directory = l: { if (override_local_cache_dir) |local_cache_dir_path| { break :l .{ .handle = try fs.cwd().makeOpenPath(local_cache_dir_path, .{}), @@ -5510,7 +5397,7 @@ fn jitCmd( const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); - var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| .{ + var zig_lib_directory: Directory = if (override_lib_dir) |lib_dir| .{ .path = lib_dir, .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { fatal("unable to open zig lib directory from 'zig-lib-dir' argument: '{s}': {s}", .{ lib_dir, @errorName(err) }); @@ -5520,7 +5407,7 @@ fn jitCmd( }; defer zig_lib_directory.handle.close(); - var global_cache_directory: Compilation.Directory = l: { + var global_cache_directory: Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); break :l .{ .handle = try fs.cwd().makeOpenPath(p, .{}), @@ -6907,197 +6794,6 @@ const ClangSearchSanitizer = struct { }; }; -const AccessLibPathResult = enum { ok, no_match }; - -fn accessLibPath( - gpa: Allocator, - arena: Allocator, - /// Allocated via `gpa`. - test_path: *std.ArrayListUnmanaged(u8), - /// Allocated via `gpa`. - checked_paths: *std.ArrayListUnmanaged(u8), - /// Allocated via `arena`. - external_linker_inputs: *std.ArrayListUnmanaged(LinkerInput), - create_module: *CreateModule, - /// Allocated via `gpa`. - ld_script_bytes: *std.ArrayListUnmanaged(u8), - lib_dir_path: []const u8, - lib_name: []const u8, - target: std.Target, - link_mode: std.builtin.LinkMode, - parent: SystemLib, -) Allocator.Error!AccessLibPathResult { - const sep = fs.path.sep_str; - - if (target.isDarwin() and link_mode == .dynamic) tbd: { - // Prefer .tbd over .dylib. - test_path.clearRetainingCapacity(); - try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.tbd", .{ lib_dir_path, lib_name }); - try checked_paths.writer(gpa).print("\n {s}", .{test_path.items}); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => break :tbd, - else => |e| fatal("unable to search for tbd library '{s}': {s}", .{ - test_path.items, @errorName(e), - }), - }; - return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name); - } - - main_check: { - test_path.clearRetainingCapacity(); - try test_path.writer(gpa).print("{s}" ++ sep ++ "{s}{s}{s}", .{ - lib_dir_path, - target.libPrefix(), - lib_name, - switch (link_mode) { - .static => target.staticLibSuffix(), - .dynamic => target.dynamicLibSuffix(), - }, - }); - try checked_paths.writer(gpa).print("\n {s}", .{test_path.items}); - - // In the case of .so files, they might actually be "linker scripts" - // that contain references to other libraries. - if (create_module.allow_so_scripts and target.ofmt == .elf and mem.endsWith(u8, test_path.items, ".so")) { - var file = fs.cwd().openFile(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => break :main_check, - else => |e| fatal("unable to search for {s} library '{s}': {s}", .{ - @tagName(link_mode), test_path.items, @errorName(e), - }), - }; - defer file.close(); - try ld_script_bytes.resize(gpa, @sizeOf(std.elf.Elf64_Ehdr)); - const n = file.readAll(ld_script_bytes.items) catch |err| fatal("failed to read {s}: {s}", .{ - test_path.items, @errorName(err), - }); - elf_file: { - if (n != ld_script_bytes.items.len) break :elf_file; - if (!mem.eql(u8, ld_script_bytes.items[0..4], "\x7fELF")) break :elf_file; - // Appears to be an ELF file. - return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name); - } - const stat = file.stat() catch |err| - fatal("failed to stat {s}: {s}", .{ test_path.items, @errorName(err) }); - const size = std.math.cast(u32, stat.size) orelse - fatal("{s}: linker script too big", .{test_path.items}); - try ld_script_bytes.resize(gpa, size); - const buf = ld_script_bytes.items[n..]; - const n2 = file.readAll(buf) catch |err| - fatal("failed to read {s}: {s}", .{ test_path.items, @errorName(err) }); - if (n2 != buf.len) fatal("failed to read {s}: unexpected end of file", .{test_path.items}); - var diags = link.Diags.init(gpa); - defer diags.deinit(); - const ld_script_result = link.LdScript.parse(gpa, &diags, Path.initCwd(test_path.items), ld_script_bytes.items); - if (diags.hasErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); - defer wip_errors.deinit(); - - try diags.addMessagesToBundle(&wip_errors); - - var error_bundle = try wip_errors.toOwnedBundle(""); - defer error_bundle.deinit(gpa); - - const color: Color = .auto; - error_bundle.renderToStdErr(color.renderOptions()); - - process.exit(1); - } - - var ld_script = ld_script_result catch |err| - fatal("{s}: failed to parse linker script: {s}", .{ test_path.items, @errorName(err) }); - defer ld_script.deinit(gpa); - - try external_linker_inputs.ensureUnusedCapacity(arena, ld_script.args.len); - for (ld_script.args) |arg| { - const syslib: SystemLib = .{ - .needed = arg.needed or parent.needed, - .weak = parent.weak, - .preferred_mode = parent.preferred_mode, - .search_strategy = parent.search_strategy, - }; - if (mem.startsWith(u8, arg.path, "-l")) { - external_linker_inputs.appendAssumeCapacity(.{ .named = .{ - .name = try arena.dupe(u8, arg.path["-l".len..]), - .info = syslib, - } }); - } else { - external_linker_inputs.appendAssumeCapacity(.{ .path = .{ - .path = Path.initCwd(try arena.dupe(u8, arg.path)), - .info = syslib, - } }); - } - } - return .ok; - } - - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => break :main_check, - else => |e| fatal("unable to search for {s} library '{s}': {s}", .{ - @tagName(link_mode), test_path.items, @errorName(e), - }), - }; - return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name); - } - - // In the case of Darwin, the main check will be .dylib, so here we - // additionally check for .so files. - if (target.isDarwin() and link_mode == .dynamic) so: { - test_path.clearRetainingCapacity(); - try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, lib_name }); - try checked_paths.writer(gpa).print("\n {s}", .{test_path.items}); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => break :so, - else => |e| fatal("unable to search for so library '{s}': {s}", .{ - test_path.items, @errorName(e), - }), - }; - return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name); - } - - // In the case of MinGW, the main check will be .lib but we also need to - // look for `libfoo.a`. - if (target.isMinGW() and link_mode == .static) mingw: { - test_path.clearRetainingCapacity(); - try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.a", .{ - lib_dir_path, lib_name, - }); - try checked_paths.writer(gpa).print("\n {s}", .{test_path.items}); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => break :mingw, - else => |e| fatal("unable to search for static library '{s}': {s}", .{ - test_path.items, @errorName(e), - }), - }; - return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name); - } - - return .no_match; -} - -fn finishAccessLibPath( - arena: Allocator, - create_module: *CreateModule, - test_path: *std.ArrayListUnmanaged(u8), - link_mode: std.builtin.LinkMode, - parent: SystemLib, - lib_name: []const u8, -) Allocator.Error!AccessLibPathResult { - const path = Path.initCwd(try arena.dupe(u8, test_path.items)); - switch (link_mode) { - .static => try create_module.link_objects.append(arena, .{ .path = path }), - .dynamic => try create_module.resolved_system_libs.append(arena, .{ - .name = lib_name, - .lib = .{ - .needed = parent.needed, - .weak = parent.weak, - .path = path, - }, - }), - } - return .ok; -} - fn accessFrameworkPath( test_path: *std.ArrayList(u8), checked_paths: *std.ArrayList(u8), @@ -7218,7 +6914,7 @@ fn cmdFetch( }); defer root_prog_node.end(); - var global_cache_directory: Compilation.Directory = l: { + var global_cache_directory: Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); break :l .{ .handle = try fs.cwd().makeOpenPath(p, .{}), @@ -7795,18 +7491,3 @@ fn handleModArg( c_source_files_owner_index.* = create_module.c_source_files.items.len; rc_source_files_owner_index.* = create_module.rc_source_files.items.len; } - -fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) struct { []const u8, std.builtin.LinkMode } { - const prefix = target.libPrefix(); - const static_suffix = target.staticLibSuffix(); - const dynamic_suffix = target.dynamicLibSuffix(); - const basename = fs.path.basename(path); - const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else basename; - if (mem.endsWith(u8, unlibbed, static_suffix)) return .{ - unlibbed[0 .. unlibbed.len - static_suffix.len], .static, - }; - if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{ - unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic, - }; - fatal("unrecognized library path: {s}", .{path}); -}