Commit e567abb339
Changed files (12)
lib
std
Build
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);
}
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;
}
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;
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;
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");
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);
-
- for (comp.objects) |obj| {
- parseInputReportingFailure(self, obj.path, obj.needed, obj.must_link);
- }
+ if (csu.crt0) |path| openParseObjectReportingFailure(self, path);
+ if (csu.crti) |path| openParseObjectReportingFailure(self, path);
+ if (csu.crtbegin) |path| openParseObjectReportingFailure(self, path);
+
+ // 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;
+ 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(path) catch |err| switch (err) {
+ 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,
+ 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,
- lib: SystemLib,
+ 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");
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;
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;
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<lib>`, 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
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;
+}
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, .{
- .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, .{
+ .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,
- });
+ .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;
+ // 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;
- },
- .only_compiler_rt => {
- warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name});
+ }
+ 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,
- },
- });
- 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.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 (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;
+ 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.
- 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 (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);
-
- 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;
+ // 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)});
+
+ 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});
-}
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