diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 48804cf7b1ccb51e8a37052686d0a9afe670d116..1e49fb5ce8a8e749ce3b79df01974c58585b8f61 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1145,16 +1145,16 @@ pub const LC = enum(u32) { }; /// the mach magic number -pub const MH_MAGIC = 0xfeedface; +pub const MH_MAGIC: u32 = 0xfeedface; /// NXSwapInt(MH_MAGIC) -pub const MH_CIGAM = 0xcefaedfe; +pub const MH_CIGAM: u32 = 0xcefaedfe; /// the 64-bit mach magic number -pub const MH_MAGIC_64 = 0xfeedfacf; +pub const MH_MAGIC_64: u32 = 0xfeedfacf; /// NXSwapInt(MH_MAGIC_64) -pub const MH_CIGAM_64 = 0xcffaedfe; +pub const MH_CIGAM_64: u32 = 0xcffaedfe; /// relocatable object file pub const MH_OBJECT = 0x1; @@ -1281,16 +1281,16 @@ pub const MH_DYLIB_IN_CACHE = 0x80000000; // Constants for the flags field of the fat_header /// the fat magic number -pub const FAT_MAGIC = 0xcafebabe; +pub const FAT_MAGIC: u32 = 0xcafebabe; /// NXSwapLong(FAT_MAGIC) -pub const FAT_CIGAM = 0xbebafeca; +pub const FAT_CIGAM: u32 = 0xbebafeca; /// the 64-bit fat magic number -pub const FAT_MAGIC_64 = 0xcafebabf; +pub const FAT_MAGIC_64: u32 = 0xcafebabf; /// NXSwapLong(FAT_MAGIC_64) -pub const FAT_CIGAM_64 = 0xbfbafeca; +pub const FAT_CIGAM_64: u32 = 0xbfbafeca; /// Segment flags /// The file contents for this segment is for the high part of the VM space, the low part @@ -2212,3 +2212,11 @@ pub const CompactUnwindEncoding = packed struct(u32) { has_lsda: u1, start: u1, }; + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +pub const ARMAG = "!\n"; +/// String in ar_fmag at the end of each header. +pub const ARFMAG = "`\n"; diff --git a/src/Compilation.zig b/src/Compilation.zig index 1259141064c2b9c2e7513d427d9029010c63bdb9..cb040f0ff9d15006d9a13c14e5c59472121b129f 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -83,7 +83,7 @@ zigc_strat: RtStrat, /// 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, +framework_dirs: []const Cache.Directory, /// 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.array_hash_map.String(void), @@ -1350,7 +1350,6 @@ pub const ClangPreprocessorMode = enum { version, }; -pub const Framework = link.File.MachO.Framework; pub const SystemLib = link.SystemLib; pub const CacheMode = enum { @@ -1487,6 +1486,7 @@ pub const CreateOptions = struct { /// The ELF implementation no longer uses this data, however the MachO and COFF /// implementations still do. lib_directories: []const Cache.Directory = &.{}, + framework_directories: []const Cache.Directory = &.{}, rpath_list: []const []const u8 = &[0][]const u8{}, symbol_wrap_set: std.array_hash_map.String(void) = .empty, c_source_files: []const CSourceFile = &.{}, @@ -1494,8 +1494,6 @@ pub const CreateOptions = struct { manifest_file: ?[]const u8 = null, rc_includes: std.zig.RcIncludes = .any, link_inputs: []const link.Input = &.{}, - framework_dirs: []const []const u8 = &[0][]const u8{}, - frameworks: []const Framework = &.{}, windows_lib_names: []const []const u8 = &.{}, /// This means that if the output mode is an executable it will be a /// Position Independent Executable. If the output mode is not an @@ -2146,7 +2144,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .ubsan_rt_strat = ubsan_rt_strat, .zigc_strat = zigc_strat, .link_inputs = options.link_inputs, - .framework_dirs = options.framework_dirs, + .framework_dirs = options.framework_directories, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, .skip_linker_dependencies = options.skip_linker_dependencies, .queued_jobs = .{}, @@ -2208,9 +2206,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .z_relro = options.linker_z_relro, .z_common_page_size = options.linker_z_common_page_size, .z_max_page_size = options.linker_z_max_page_size, - .frameworks = options.frameworks, .lib_directories = options.lib_directories, - .framework_dirs = options.framework_dirs, .rpath_list = options.rpath_list, .symbol_wrap_set = options.symbol_wrap_set, .repro = options.linker_repro orelse (options.root_mod.optimize_mode != .debug), @@ -3416,7 +3412,10 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.add(comp.zigc_strat); man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); - man.hash.addListOfBytes(comp.framework_dirs); + man.hash.add(comp.framework_dirs.len); + for (comp.framework_dirs) |framework_dir| { + man.hash.addBytes(framework_dir.path orelse "."); + } man.hash.addListOfBytes(comp.windows_libs.keys()); man.hash.addListOfBytes(comp.global_cc_argv); @@ -3482,7 +3481,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.add(opts.growable_table); // Mach-O specific stuff - try link.File.MachO.hashAddFrameworks(man, opts.frameworks); try man.addInputPathOptional(opts.entitlements, .{}); man.hash.addOptional(opts.pagezero_size); man.hash.addOptional(opts.headerpad_size); @@ -6460,7 +6458,7 @@ fn addCommonCCArgs( try argv.ensureUnusedCapacity(comp.framework_dirs.len * 2); for (comp.framework_dirs) |framework_dir| { - try argv.appendSlice(&.{ "-F", framework_dir }); + try argv.appendSlice(&.{ "-F", framework_dir.path orelse "." }); } } } diff --git a/src/link.zig b/src/link.zig index 34b020b677cce80c7f541c158276a507d031331e..c1ad89e817611968fa3c61b12cd5b19ad317fdfe 100644 --- a/src/link.zig +++ b/src/link.zig @@ -481,7 +481,6 @@ pub const File = struct { // TODO: remove this. libraries are resolved by the frontend. lib_directories: []const Directory, - framework_dirs: []const []const u8, rpath_list: []const []const u8, /// Zig compiler development linker flags. @@ -501,7 +500,6 @@ pub const File = struct { headerpad_max_install_names: bool, /// Remove dylibs that are unreachable by the entry point or exported symbols dead_strip_dylibs: bool, - frameworks: []const MachO.Framework, /// Force load all members of static archives that implement an /// Objective-C class or category force_load_objc: bool, @@ -1958,6 +1956,7 @@ pub const UnresolvedInput = union(enum) { /// Strings that come from GNU ld scripts. Is it a filename? Is it a path? /// Who knows! Fuck around and find out. ambiguous_name: AmbiguousNameQuery, + framework_query: FrameworkQuery, pub const NameQuery = struct { name: []const u8, @@ -1996,6 +1995,12 @@ pub const UnresolvedInput = union(enum) { } }; + pub const FrameworkQuery = struct { + name: []const u8, + needed: bool, + weak: bool, + }; + pub const SearchStrategy = enum { paths_first, mode_first, @@ -2118,6 +2123,7 @@ pub fn resolveInputs( /// Allocated with `gpa`. resolved_inputs: *std.ArrayList(Input), lib_directories: []const Cache.Directory, + framework_directories: []const Cache.Directory, color: std.zig.Color, ) Allocator.Error!void { var checked_paths: std.ArrayList(u8) = .empty; @@ -2129,6 +2135,7 @@ pub fn resolveInputs( var archive_dedup: ArchiveDedupMap = .empty; defer archive_dedup.deinit(gpa); + // Allocated with `arena`. var failed_libs: std.ArrayList(struct { name: []const u8, strategy: UnresolvedInput.SearchStrategy, @@ -2136,6 +2143,12 @@ pub fn resolveInputs( preferred_mode: std.lang.LinkMode, }) = .empty; + // Allocated with `arena`. + var failed_frameworks: std.ArrayList(struct { + name: []const u8, + checked_paths: []const u8, + }) = .empty; + // Convert external system libs into a stack so that items can be // pushed to it. // @@ -2337,17 +2350,47 @@ pub fn resolveInputs( } continue; }, + .framework_query => |framework_query| { + checked_paths.clearRetainingCapacity(); + for (framework_directories) |framework_directory| { + switch (try resolveFrameworkInput( + gpa, + arena, + io, + resolved_inputs, + &checked_paths, + &ld_script_bytes, + &archive_dedup, + framework_directory, + framework_query, + )) { + .ok => continue :syslib, + .no_match => {}, + } + } + try failed_frameworks.append(arena, .{ + .name = framework_query.name, + .checked_paths = try arena.dupe(u8, checked_paths.items), + }); + continue :syslib; + }, } comptime unreachable; } - if (failed_libs.items.len > 0) { + if (failed_libs.items.len > 0 or failed_frameworks.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 {t} system library {q} using strategy {t}. searched paths:{s}", .{ f.preferred_mode, f.name, f.strategy, searched_paths, }); } + for (failed_frameworks.items) |f| { + const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; + std.log.err("unable to find system framework {q}. searched paths:{s}", .{ + f.name, searched_paths, + }); + } std.process.exit(1); } } @@ -2437,16 +2480,15 @@ fn resolveLibInput( else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - name_query.query, - .basename, - ); + resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = name_query.query.needed, + .weak = name_query.query.weak, + .reexport = name_query.query.reexport, + .fallback_soname = .basename, + } }); + return .ok; } // In the case of MinGW, the main check will be .lib but we also need to @@ -2462,16 +2504,13 @@ fn resolveLibInput( else => |e| fatal("unable to search for static library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - name_query.query, - .basename, - ); + addResolvedStaticLibInput(io, resolved_inputs, archive_dedup, .{ + .path = test_path, + .file = file, + .must_link = name_query.query.must_link, + .hidden = name_query.query.hidden, + }); + return .ok; } // In the case of OpenBSD, dynamic libraries are always versioned, without @@ -2533,6 +2572,145 @@ fn resolveLibInput( return .no_match; } +fn resolveFrameworkInput( + gpa: Allocator, + arena: Allocator, + io: Io, + /// Allocated via `gpa`. + resolved_inputs: *std.ArrayList(Input), + /// Allocated via `gpa`. + checked_paths: *std.ArrayList(u8), + /// Allocated via `gpa`. + ld_script_bytes: *std.ArrayList(u8), + /// Allocated via `gpa`. + archive_dedup: *ArchiveDedupMap, + framework_directory: Directory, + framework_query: UnresolvedInput.FrameworkQuery, +) Allocator.Error!ResolveLibInputResult { + const sep = std.fs.path.sep_str; + + tbd: { + const test_path: Path = .{ + .root_dir = framework_directory, + .sub_path = try std.fmt.allocPrint( + arena, + "{s}.framework" ++ sep ++ "{s}.tbd", + .{ framework_query.name, framework_query.name }, + ), + }; + try checked_paths.print(gpa, "\n {f}", .{test_path}); + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :tbd, + else => |e| fatal("unable to search for tbd library {qf}: {t}", .{ test_path, e }), + }; + errdefer file.close(io); + resolved_inputs.appendAssumeCapacity(.{ .tbd = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + } }); + return .ok; + } + + dylib: { + const test_path: Path = .{ + .root_dir = framework_directory, + .sub_path = try std.fmt.allocPrint( + arena, + "{s}.framework" ++ sep ++ "{s}.dylib", + .{ framework_query.name, framework_query.name }, + ), + }; + try checked_paths.print(gpa, "\n {f}", .{test_path}); + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :dylib, + else => |e| fatal("unable to search for dynamic library {qf}: {t}", .{ test_path, e }), + }; + errdefer file.close(io); + resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + .fallback_soname = .basename, + } }); + return .ok; + } + + ambiguous: { + const test_path: Path = .{ + .root_dir = framework_directory, + .sub_path = try std.fmt.allocPrint( + arena, + "{s}.framework" ++ sep ++ "{s}", + .{ framework_query.name, framework_query.name }, + ), + }; + try checked_paths.print(gpa, "\n {f}", .{test_path}); + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :ambiguous, + else => |e| fatal("unable to search for framework library {qf}: {t}", .{ test_path, e }), + }; + errdefer file.close(io); + + const macho_magics: []const [4]u8 = &.{ + @bitCast(std.macho.MH_MAGIC), + @bitCast(std.macho.MH_CIGAM), + @bitCast(std.macho.MH_MAGIC_64), + @bitCast(std.macho.MH_CIGAM_64), + @bitCast(std.macho.FAT_MAGIC), + @bitCast(std.macho.FAT_CIGAM), + @bitCast(std.macho.FAT_MAGIC_64), + @bitCast(std.macho.FAT_CIGAM_64), + }; + try ld_script_bytes.resize(gpa, @max(4, std.macho.ARMAG.len)); + const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| + fatal("failed to read {qf}: {t}", .{ test_path, err }); + const buf = ld_script_bytes.items[0..n]; + + for (macho_magics) |*macho_magic| { + if (mem.startsWith(u8, buf, macho_magic)) { + // Appears to be a Mach-O file, so a dylib. + resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + .fallback_soname = .basename, + } }); + return .ok; + } + } + + if (mem.startsWith(u8, buf, std.macho.ARMAG)) { + // Appears to be an archive file. + addResolvedStaticLibInput(io, resolved_inputs, archive_dedup, .{ + .path = test_path, + .file = file, + .must_link = false, + .hidden = false, + }); + return .ok; + } + + // It doesn't look like a dylib or archive, so assume it's a tbd. + resolved_inputs.appendAssumeCapacity(.{ .tbd = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + } }); + return .ok; + } + + return .no_match; +} + /// Deduplicates static archive link inputs based on their path. This is done for efficiency, so /// that linker implementations do not need to open and scan the archive just to determine that they /// need not extract any objects. At the time of writing, it also helps avoid "multiple definitions @@ -2556,45 +2734,21 @@ const ArchiveDedupAdapter = struct { } }; -fn finishResolveLibInput( +fn addResolvedStaticLibInput( io: Io, resolved_inputs: *std.ArrayList(Input), archive_dedup: *ArchiveDedupMap, - path: Path, - file: Io.File, - link_mode: std.lang.LinkMode, - query: UnresolvedInput.Query, - fallback_soname: Input.Dso.FallbackSoname, -) ResolveLibInputResult { - switch (link_mode) { - .static => { - const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items }; - const gop = archive_dedup.getOrPutAssumeCapacityAdapted(path, ctx); - if (gop.found_existing) { - // Ignore duplicate archive input - file.close(io); - return .ok; - } - gop.key_ptr.* = @intCast(resolved_inputs.items.len); - 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, - .fallback_soname = fallback_soname, - }, - }), + archive: Input.Object, +) void { + const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items }; + const gop = archive_dedup.getOrPutAssumeCapacityAdapted(archive.path, ctx); + if (gop.found_existing) { + // Ignore duplicate archive input + archive.file.close(io); + } else { + gop.key_ptr.* = @intCast(resolved_inputs.items.len); + resolved_inputs.appendAssumeCapacity(.{ .archive = archive }); } - return .ok; } fn resolvePathInput( @@ -2690,19 +2844,24 @@ fn resolvePathInputLib( try archive_dedup.ensureUnusedCapacity(gpa, 1); const test_path: Path = pq.path; + + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => return .no_match, + else => |e| fatal("unable to search for {t} library {qf}: {t}", .{ + link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, + }), + }; + errdefer file.close(io); + // In the case of shared libraries, they might actually be "linker scripts" // that contain references to other libraries. - if (pq.query.allow_so_scripts and target.ofmt == .elf and switch (Compilation.classifyFileExt(test_path.sub_path)) { - .static_library, .shared_library => true, - else => false, - }) { - var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { - error.FileNotFound => return .no_match, - else => |e| fatal("unable to search for {t} library {qf}: {t}", .{ - link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, - }), - }; - errdefer file.close(io); + ld_script: { + if (!pq.query.allow_so_scripts) break :ld_script; + if (target.ofmt != .elf) break :ld_script; + switch (Compilation.classifyFileExt(test_path.sub_path)) { + .static_library, .shared_library => {}, + else => break :ld_script, + } try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len)); const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| fatal("failed to read {qf}: {t}", .{ test_path, err }); @@ -2712,16 +2871,7 @@ fn resolvePathInputLib( mem.startsWith(u8, buf, std.elf.ARMAG_THIN)) { // Appears to be an ELF or archive file. - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - pq.query, - fallback_soname, - ); + break :ld_script; } const stat = file.stat(io) catch |err| fatal("failed to stat {f}: {t}", .{ test_path, err }); @@ -2785,21 +2935,23 @@ fn resolvePathInputLib( return .ok; } - var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { - error.FileNotFound => return .no_match, - else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }), - }; - errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - pq.query, - fallback_soname, - ); + switch (link_mode) { + .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = pq.query.needed, + .weak = pq.query.weak, + .reexport = pq.query.reexport, + .fallback_soname = fallback_soname, + } }), + .static => addResolvedStaticLibInput(io, resolved_inputs, archive_dedup, .{ + .path = test_path, + .file = file, + .must_link = pq.query.must_link, + .hidden = pq.query.hidden, + }), + } + return .ok; } pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 03c350c210f3410994028f77a0cf97ae0ef30ae1..b2b90f4d5091feee962fe301dfe6346692faa1b1 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -119,10 +119,6 @@ dead_strip_dylibs: bool, undefined_treatment: UndefinedTreatment, /// 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 -frameworks: []const Framework, /// Install name for the dylib. /// TODO: unify with soname install_name: ?[]const u8, @@ -196,7 +192,6 @@ pub fn createEmpty( .headerpad_size = options.headerpad_size, .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, - .frameworks = options.frameworks, .install_name = options.install_name, .entitlements = options.entitlements, .compatibility_version = options.compatibility_version, @@ -211,7 +206,6 @@ pub fn createEmpty( .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error", // 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, .discard_local_symbols = options.discard_local_symbols, }; @@ -372,35 +366,6 @@ pub fn flush( diags.addParseError(input.path(), "failed to read input file: {t}", .{err}); } - // frameworks - for (self.frameworks) |framework| { - switch (Compilation.classifyFileExt(framework.path.sub_path)) { - .shared_library => { - const dso_input = try link.openDsoInput(io, diags, framework.path, framework.needed, framework.weak, false); - self.classifyInputFile(dso_input) catch |err| - diags.addParseError(framework.path, "failed to parse input file: {s}", .{@errorName(err)}); - }, - .static_library => { - const archive_input = try link.openArchiveInput(io, diags, framework.path, false, false); - self.classifyInputFile(archive_input) catch |err| - diags.addParseError(framework.path, "failed to parse input file: {s}", .{@errorName(err)}); - }, - else => { - dso: { - const dso_input = link.openDsoInput(io, diags, framework.path, framework.needed, framework.weak, false) catch break :dso; - self.classifyInputFile(dso_input) catch break :dso; - continue; - } - ar: { - const archive_input = link.openArchiveInput(io, diags, framework.path, false, false) catch break :ar; - self.classifyInputFile(archive_input) catch break :ar; - continue; - } - diags.addParseError(framework.path, "unknown file extension", .{}); - }, - } - } - try self.parseInputFiles(); self.parseDependentDylibs() catch |err| { switch (err) { @@ -716,20 +681,9 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { }, }; - for (self.framework_dirs) |f_dir| { + for (comp.framework_dirs) |f_dir| { try argv.append("-F"); - try argv.append(f_dir); - } - - for (self.frameworks) |framework| { - const name = framework.path.stem(); - const arg = if (framework.needed) - try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name}) - else if (framework.weak) - try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name}) - else - try std.fmt.allocPrint(arena, "-framework {s}", .{name}); - try argv.append(arg); + try argv.append(f_dir.path orelse "."); } if (comp.config.link_libcpp) { @@ -763,7 +717,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { log.debug("classifying input file {f}", .{path}); const fh = try self.addFileHandle(file); - var buffer: [Archive.SARMAG]u8 = undefined; + var buffer: [macho.ARMAG.len]u8 = undefined; const fat_arch: ?fat.Arch = try self.parseFatFile(file, path); const offset = if (fat_arch) |fa| fa.offset else 0; @@ -778,7 +732,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { return; } if (readArMagic(io, file, offset, &buffer) catch null) |ar_magic| blk: { - if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; + if (!mem.eql(u8, ar_magic, macho.ARMAG)) break :blk; try self.addArchive(input.archive, fh, fat_arch); return; } @@ -808,10 +762,10 @@ pub fn readMachHeader(io: Io, file: Io.File, offset: usize) !macho.mach_header_6 return hdr; } -pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { +pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[macho.ARMAG.len]u8) ![]const u8 { const nread = try file.readPositionalAll(io, buffer, offset); if (nread != buffer.len) return error.InputOutput; - return buffer[0..Archive.SARMAG]; + return buffer[0..macho.ARMAG.len]; } fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u64) !void { @@ -1034,10 +988,6 @@ fn parseDependentDylibs(self: *MachO) !void { const comp = self.base.comp; const gpa = comp.gpa; const io = comp.io; - 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(); @@ -1072,14 +1022,14 @@ fn parseDependentDylibs(self: *MachO) !void { const stem = fs.path.stem(id.name); // Framework - for (framework_dirs) |dir| { + for (comp.framework_dirs) |dir| { test_path.clearRetainingCapacity(); - if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items; + if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir.path orelse ".", stem)) break :full_path test_path.items; } // Library const lib_name = eatPrefix(stem, "lib") orelse stem; - for (lib_directories) |lib_directory| { + for (self.lib_directories) |lib_directory| { test_path.clearRetainingCapacity(); if (try accessLibPath(arena, io, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items; } diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 91860f45986b106bed46fbf37e9ac35133ff5b49..4c2ad4187a01830b5d93b7cc2ff8ddc68de3c6e3 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -17,7 +17,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File const offset = if (fat_arch) |ar| ar.offset else 0; const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size; - var pos: usize = offset + SARMAG; + var pos: usize = offset + macho.ARMAG.len; while (true) { if (pos >= end_pos) break; if (!mem.isAligned(pos, 2)) pos += 1; @@ -30,9 +30,9 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*; pos += @sizeOf(ar_hdr); - if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { + if (!mem.eql(u8, &hdr.ar_fmag, macho.ARFMAG)) { return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{ - std.ascii.hexEscape(ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower), + std.ascii.hexEscape(macho.ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower), }); } @@ -116,17 +116,6 @@ pub fn writeHeader( } } -// Archive files start with the ARMAG identifying string. Then follows a -// `struct ar_hdr', and as many bytes of member file data as its `ar_size' -// member indicates, for each member file. -/// String that begins an archive file. -pub const ARMAG: *const [SARMAG:0]u8 = "!\n"; -/// Size of that string. -pub const SARMAG: u4 = 8; - -/// String in ar_fmag at the end of each header. -const ARFMAG: *const [2:0]u8 = "`\n"; - pub const SYMDEF = "__.SYMDEF"; pub const SYMDEF64 = "__.SYMDEF_64"; pub const SYMDEF_SORTED = "__.SYMDEF SORTED"; @@ -146,7 +135,7 @@ pub const ar_hdr = extern struct { /// File size, in ASCII decimal. ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, /// Always contains ARFMAG. - ar_fmag: [2]u8 = ARFMAG.*, + ar_fmag: [2]u8 = macho.ARFMAG.*, fn date(self: ar_hdr) !u64 { const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 1b679cc9058932f34ea8098032ef75af49295059..4f3cc6c66cebd98a9d79dcaf5c5818f39cce00ce 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -151,7 +151,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void { // Update file offsets of contributing objects const total_size: usize = blk: { - var pos: usize = Archive.SARMAG; + var pos: usize = macho.ARMAG.len; pos += @sizeOf(Archive.ar_hdr); pos += Archive.SYMDEF.len + 1; pos = mem.alignForward(usize, pos, 8); @@ -194,7 +194,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void { var writer: Writer = .fixed(buffer); // Write magic - writer.writeAll(Archive.ARMAG) catch unreachable; + writer.writeAll(macho.ARMAG) catch unreachable; // Write symtab ar_symtab.write(format, macho_file, &writer) catch |err| diff --git a/src/main.zig b/src/main.zig index 5ad21b548e12b1453aa4e1345c74fa7c17af5036..34ff983b0328f7d603ae0ad8e456ab5d0b993634 100644 --- a/src/main.zig +++ b/src/main.zig @@ -893,13 +893,6 @@ const ArgsIterator = struct { } }; -/// Similar to `link.Framework` except it doesn't store yet unresolved -/// path to the framework. -const Framework = struct { - needed: bool = false, - weak: bool = false, -}; - const CliModule = struct { root_path: []const u8, root_src_path: []const u8, @@ -1117,12 +1110,12 @@ fn buildOutputType( .llvm_m_args = .empty, .sysroot = null, - .lib_directories = .empty, // populated by createModule() .lib_dir_args = .empty, // populated from CLI arg parsing + .lib_directories = .empty, // populated by createModule() + .framework_dir_args = .empty, // populated from CLI arg parsing + .framework_directories = .empty, // populated by createModule() .libc_installation = null, .want_native_include_dirs = false, - .frameworks = .empty, - .framework_dirs = .empty, .rpath_list = .empty, .each_lib_rpath = null, .libc_paths_file = EnvVar.ZIG_LIBC.get(environ_map), @@ -1275,13 +1268,25 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) { try create_module.lib_dir_args.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-F")) { - try create_module.framework_dirs.append(arena, args_iter.nextOrFatal()); + try create_module.framework_dir_args.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-framework")) { - try create_module.frameworks.put(arena, args_iter.nextOrFatal(), .{}); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = args_iter.nextOrFatal(), + .needed = false, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-weak_framework")) { - try create_module.frameworks.put(arena, args_iter.nextOrFatal(), .{ .weak = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = args_iter.nextOrFatal(), + .needed = false, + .weak = true, + } }); } else if (mem.eql(u8, arg, "-needed_framework")) { - try create_module.frameworks.put(arena, args_iter.nextOrFatal(), .{ .needed = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = args_iter.nextOrFatal(), + .needed = true, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-install_name")) { install_name = args_iter.nextOrFatal(); } else if (mem.cutPrefix(u8, arg, "--compress-debug-sections=")) |param| { @@ -1396,11 +1401,11 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-iframework")) { const path = args_iter.nextOrFatal(); try cssan.addIncludePath(arena, &cc_argv, .iframework, arg, path, false); - try create_module.framework_dirs.append(arena, path); // Forward to the backend as -F + try create_module.framework_dir_args.append(arena, path); // Forward to the backend as -F } else if (mem.eql(u8, arg, "-iframeworkwithsysroot")) { const path = args_iter.nextOrFatal(); try cssan.addIncludePath(arena, &cc_argv, .iframeworkwithsysroot, arg, path, false); - try create_module.framework_dirs.append(arena, path); // Forward to the backend as -F + try create_module.framework_dir_args.append(arena, path); // Forward to the backend as -F } else if (mem.eql(u8, arg, "--version")) { const next_arg = args_iter.nextOrFatal(); version = std.SemanticVersion.parse(next_arg) catch |err| { @@ -1828,7 +1833,7 @@ fn buildOutputType( } else if (mem.cutPrefix(u8, arg, "-L")) |rest| { try create_module.lib_dir_args.append(arena, rest); } else if (mem.cutPrefix(u8, arg, "-F")) |rest| { - try create_module.framework_dirs.append(arena, rest); + try create_module.framework_dir_args.append(arena, rest); } else if (mem.cutPrefix(u8, arg, "-l")) |name| { // We don't know whether this library is part of libc // or libc++ until we resolve the target, so we append @@ -2532,8 +2537,12 @@ fn buildOutputType( disable_c_depfile = true; try cc_argv.appendSlice(arena, it.other_args); }, - .framework_dir => try create_module.framework_dirs.append(arena, it.only_arg), - .framework => try create_module.frameworks.put(arena, it.only_arg, .{}), + .framework_dir => try create_module.framework_dir_args.append(arena, it.only_arg), + .framework => try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = it.only_arg, + .needed = false, + .weak = false, + } }), .nostdlibinc => create_module.want_native_include_dirs = false, .strip => mod_opts.strip = true, .exec_model => { @@ -2561,7 +2570,11 @@ fn buildOutputType( }, .name_done = false, } }), - .weak_framework => try create_module.frameworks.put(arena, it.only_arg, .{ .weak = true }), + .weak_framework => try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = it.only_arg, + .needed = false, + .weak = true, + } }), .headerpad_max_install_names => headerpad_max_install_names = true, .compress_debug_sections => { if (it.only_arg.len == 0) { @@ -2914,11 +2927,23 @@ fn buildOutputType( fatal("unable to parse minor subsystem version {q}: {t}", .{ minor, err }); }; } else if (mem.eql(u8, arg, "-framework")) { - try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{}); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = linker_args_it.nextOrFatal(), + .needed = false, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-weak_framework")) { - try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .weak = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = linker_args_it.nextOrFatal(), + .needed = false, + .weak = true, + } }); } else if (mem.eql(u8, arg, "-needed_framework")) { - try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .needed = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = linker_args_it.nextOrFatal(), + .needed = true, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-needed_library")) { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ .name = linker_args_it.nextOrFatal(), @@ -3400,59 +3425,6 @@ fn buildOutputType( } } - var resolved_frameworks = std.array_list.Managed(Compilation.Framework).init(arena); - - if (create_module.frameworks.keys().len > 0) { - var test_path = std.array_list.Managed(u8).init(gpa); - defer test_path.deinit(); - - var checked_paths = std.array_list.Managed(u8).init(gpa); - defer checked_paths.deinit(); - - var failed_frameworks = std.array_list.Managed(struct { - name: []const u8, - checked_paths: []const u8, - }).init(arena); - - framework: for (create_module.frameworks.keys(), create_module.frameworks.values()) |framework_name, info| { - checked_paths.clearRetainingCapacity(); - - for (create_module.framework_dirs.items) |framework_dir_path| { - if (try accessFrameworkPath( - io, - &test_path, - &checked_paths, - framework_dir_path, - framework_name, - )) { - const path = Path.initCwd(try arena.dupe(u8, test_path.items)); - try resolved_frameworks.append(.{ - .needed = info.needed, - .weak = info.weak, - .path = path, - }); - continue :framework; - } - } - - try failed_frameworks.append(.{ - .name = framework_name, - .checked_paths = try arena.dupe(u8, checked_paths.items), - }); - } - - if (failed_frameworks.items.len > 0) { - for (failed_frameworks.items) |f| { - const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; - std.log.err("unable to find framework {q}. searched paths: {s}", .{ - f.name, searched_paths, - }); - } - process.exit(1); - } - } - // After this point, resolved_frameworks is used instead of frameworks. - if (create_module.resolved_options.output_mode == .Obj and target.ofmt == .coff) { const total_obj_count = create_module.c_source_files.items.len + @intFromBool(root_src_file != null) + @@ -3689,6 +3661,7 @@ fn buildOutputType( .emit_docs = emit_docs_resolved, .emit_implib = emit_implib_resolved, .lib_directories = create_module.lib_directories.items, + .framework_directories = create_module.framework_directories.items, .rpath_list = create_module.rpath_list.items, .symbol_wrap_set = symbol_wrap_set, .c_source_files = create_module.c_source_files.items, @@ -3697,8 +3670,6 @@ fn buildOutputType( .rc_includes = rc_includes, .mingw_unicode_entry_point = mingw_unicode_entry_point, .link_inputs = create_module.link_inputs.items, - .framework_dirs = create_module.framework_dirs.items, - .frameworks = resolved_frameworks.items, .windows_lib_names = create_module.windows_libs.keys(), .want_compiler_rt = if (zig_cc_explicitly_link_compiler_rt) true else want_compiler_rt, .want_ubsan_rt = want_ubsan_rt, @@ -4041,13 +4012,13 @@ const CreateModule = struct { /// CPU features. llvm_m_args: std.ArrayList([]const u8), sysroot: ?[]const u8, - lib_directories: std.ArrayList(Directory), lib_dir_args: std.ArrayList([]const u8), + lib_directories: std.ArrayList(Directory), + framework_dir_args: std.ArrayList([]const u8), + framework_directories: std.ArrayList(Directory), libc_installation: ?LibCInstallation, want_native_include_dirs: bool, - frameworks: std.array_hash_map.String(Framework), native_system_include_paths: []const []const u8, - framework_dirs: std.ArrayList([]const u8), rpath_list: std.ArrayList([]const u8), each_lib_rpath: ?bool, libc_paths_file: ?[]const u8, @@ -4158,7 +4129,7 @@ fn createModule( var unresolved_link_inputs: std.ArrayList(link.UnresolvedInput) = .empty; defer unresolved_link_inputs.deinit(gpa); try unresolved_link_inputs.ensureUnusedCapacity(gpa, create_module.cli_link_inputs.items.len); - var any_name_queries_remaining = false; + var any_named_library_queries = false; for (create_module.cli_link_inputs.items) |cli_link_input| switch (cli_link_input) { .name_query => |nq| { const lib_name = nq.name; @@ -4199,39 +4170,52 @@ fn createModule( } unresolved_link_inputs.appendAssumeCapacity(cli_link_input); - any_name_queries_remaining = true; + any_named_library_queries = true; + }, + .framework_query => { + unresolved_link_inputs.appendAssumeCapacity(cli_link_input); + any_named_library_queries = 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; + if (any_named_library_queries) 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| { - 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.parsePath(lib_dir_arg).root.len..]; - const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir }); - addLibDirectoryWarn(io, &create_module.lib_directories, full_path); - } else { - addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg); + for (create_module.lib_dir_args.items) |dir_arg| { + const path: []const u8 = path: { + if (fs.path.isAbsolute(dir_arg)) { + if (create_module.sysroot) |sysroot| { + // Change the path root to the given sysroot. + const stripped_dir = dir_arg[fs.path.parsePath(dir_arg).root.len..]; + break :path try fs.path.join(arena, &.{ sysroot, stripped_dir }); + } } - } - } else { - for (create_module.lib_dir_args.items) |lib_dir_arg| { - addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg); - } + break :path dir_arg; + }; + appendLibDirOrWarn(io, &create_module.lib_directories, path); } create_module.lib_dir_args = undefined; // From here we use lib_directories instead. - if (resolved_target.is_native_os and target.os.tag.isDarwin()) { - // If we want to link against frameworks, we need system headers. - if (create_module.frameworks.count() > 0) - create_module.want_native_include_dirs = true; + // Likewise for framework path arguments. + try create_module.framework_directories.ensureUnusedCapacity(arena, create_module.framework_dir_args.items.len); + for (create_module.framework_dir_args.items) |dir_arg| { + const path: []const u8 = path: { + if (fs.path.isAbsolute(dir_arg)) { + if (create_module.sysroot) |sysroot| { + // Change the path root to the given sysroot. + const stripped_dir = dir_arg[fs.path.parsePath(dir_arg).root.len..]; + break :path try fs.path.join(arena, &.{ sysroot, stripped_dir }); + } + } + break :path dir_arg; + }; + appendLibDirOrWarn(io, &create_module.framework_directories, path); } + create_module.framework_dir_args = undefined; // From here we use framework_directories instead. if (create_module.each_lib_rpath orelse resolved_target.is_native_os) { try create_module.rpath_list.ensureUnusedCapacity(arena, create_module.lib_directories.items.len); @@ -4253,11 +4237,17 @@ 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.rpath_list.appendSlice(arena, paths.rpaths.items); try create_module.lib_directories.ensureUnusedCapacity(arena, paths.lib_dirs.items.len); - for (paths.lib_dirs.items) |path| addLibDirectoryWarn2(io, &create_module.lib_directories, path, true); + for (paths.lib_dirs.items) |path| { + appendLibDirOrWarnAllowMissing(io, &create_module.lib_directories, path); + } + + try create_module.framework_directories.ensureUnusedCapacity(arena, paths.framework_dirs.items.len); + for (paths.framework_dirs.items) |path| { + appendLibDirOrWarnAllowMissing(io, &create_module.framework_directories, path); + } } if (create_module.libc_paths_file) |paths_file| { @@ -4266,7 +4256,7 @@ fn createModule( } if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and - any_name_queries_remaining) + any_named_library_queries) { if (create_module.libc_installation == null) { create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ @@ -4277,9 +4267,19 @@ fn createModule( fatal("unable to find native libc installation: {t}", .{err}); }; } + try create_module.lib_directories.ensureUnusedCapacity(arena, 2); - addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?); - addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?); + + appendLibDirOrWarn( + io, + &create_module.lib_directories, + create_module.libc_installation.?.msvc_lib_dir.?, + ); + appendLibDirOrWarn( + io, + &create_module.lib_directories, + create_module.libc_installation.?.kernel32_lib_dir.?, + ); } // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`. @@ -4291,6 +4291,7 @@ fn createModule( &unresolved_link_inputs, &create_module.link_inputs, create_module.lib_directories.items, + create_module.framework_directories.items, color, ) catch |err| fatal("failed to resolve link inputs: {t}", .{err}); @@ -6085,33 +6086,6 @@ const ClangSearchSanitizer = struct { }; }; -fn accessFrameworkPath( - io: Io, - test_path: *std.array_list.Managed(u8), - checked_paths: *std.array_list.Managed(u8), - framework_dir_path: []const u8, - framework_name: []const u8, -) !bool { - const sep = fs.path.sep_str; - - for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { - test_path.clearRetainingCapacity(); - try test_path.print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}{s}", .{ - framework_dir_path, framework_name, framework_name, ext, - }); - try checked_paths.print("\n {s}", .{test_path.items}); - Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| fatal("unable to search for {s} framework {q}: {t}", .{ - ext, test_path.items, e, - }), - }; - return true; - } - - return false; -} - fn parseRcIncludes(arg: []const u8) std.zig.RcIncludes { return stringToEnum(std.zig.RcIncludes, arg) orelse fatal("unsupported rc includes type: {q}", .{arg}); @@ -6222,24 +6196,26 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool { return false; } -fn addLibDirectoryWarn(io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8) void { - return addLibDirectoryWarn2(io, lib_directories, path, false); +fn appendLibDirOrWarn(io: Io, dirs: *std.ArrayList(Directory), path: []const u8) void { + if (Io.Dir.cwd().openDir(io, path, .{})) |handle| { + dirs.appendAssumeCapacity(.{ + .path = path, + .handle = handle, + }); + } else |err| { + warn("unable to open library directory {q}: {t}", .{ path, err }); + } } - -fn addLibDirectoryWarn2( - io: Io, - lib_directories: *std.ArrayList(Directory), - path: []const u8, - ignore_not_found: bool, -) void { - lib_directories.appendAssumeCapacity(.{ - .handle = Io.Dir.cwd().openDir(io, path, .{}) catch |err| { - if (err == error.FileNotFound and ignore_not_found) return; - warn("unable to open library directory {q}: {t}", .{ path, err }); - return; - }, - .path = path, - }); +fn appendLibDirOrWarnAllowMissing(io: Io, dirs: *std.ArrayList(Directory), path: []const u8) void { + if (Io.Dir.cwd().openDir(io, path, .{})) |handle| { + dirs.appendAssumeCapacity(.{ + .path = path, + .handle = handle, + }); + } else |err| switch (err) { + error.FileNotFound => {}, // ignore + else => |e| warn("unable to open library directory {q}: {t}", .{ path, e }), + } } const IoImpl = switch (build_options.io_mode) {