Skip to content

Commit 8e6c038

Browse files
authored
Merge pull request #10208 from ziglang/zld-frameworks
zld: resolve frameworks in BFS order and handle additional macOS flags
2 parents f5c0c08 + 0c1d610 commit 8e6c038

File tree

5 files changed

+106
-75
lines changed

5 files changed

+106
-75
lines changed

src/Compilation.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ pub const InitOptions = struct {
758758
image_base_override: ?u64 = null,
759759
self_exe_path: ?[]const u8 = null,
760760
version: ?std.builtin.Version = null,
761+
compatibility_version: ?std.builtin.Version = null,
761762
libc_installation: ?*const LibCInstallation = null,
762763
machine_code_model: std.builtin.CodeModel = .default,
763764
clang_preprocessor_mode: ClangPreprocessorMode = .no,
@@ -1439,6 +1440,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
14391440
.extra_lld_args = options.lld_argv,
14401441
.soname = options.soname,
14411442
.version = options.version,
1443+
.compatibility_version = options.compatibility_version,
14421444
.libc_installation = libc_dirs.libc_installation,
14431445
.pic = pic,
14441446
.pie = pie,

src/link.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ pub const Options = struct {
143143
rpath_list: []const []const u8,
144144

145145
version: ?std.builtin.Version,
146+
compatibility_version: ?std.builtin.Version,
146147
libc_installation: ?*const LibCInstallation,
147148

148149
/// WASI-only. Type of WASI execution model ("command" or "reactor").

src/link/MachO.zig

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -842,8 +842,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
842842
Compilation.dump_argv(argv.items);
843843
}
844844

845-
try self.parseInputFiles(positionals.items, self.base.options.sysroot);
846-
try self.parseLibs(libs.items, self.base.options.sysroot);
845+
var dependent_libs = std.fifo.LinearFifo(Dylib.Id, .Dynamic).init(self.base.allocator);
846+
defer dependent_libs.deinit();
847+
try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);
848+
try self.parseLibs(libs.items, self.base.options.sysroot, &dependent_libs);
849+
try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);
847850
}
848851

849852
if (self.bss_section_index) |idx| {
@@ -1161,7 +1164,8 @@ const ParseDylibError = error{
11611164
} || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError;
11621165

11631166
const DylibCreateOpts = struct {
1164-
syslibroot: ?[]const u8 = null,
1167+
syslibroot: ?[]const u8,
1168+
dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),
11651169
id: ?Dylib.Id = null,
11661170
is_dependent: bool = false,
11671171
};
@@ -1181,7 +1185,7 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
11811185
.file = file,
11821186
};
11831187

1184-
dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1188+
dylib.parse(self.base.allocator, self.base.options.target, opts.dependent_libs) catch |err| switch (err) {
11851189
error.EndOfStream, error.NotDylib => {
11861190
try file.seekTo(0);
11871191

@@ -1191,7 +1195,7 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
11911195
};
11921196
defer lib_stub.deinit();
11931197

1194-
try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub);
1198+
try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub, opts.dependent_libs);
11951199
},
11961200
else => |e| return e,
11971201
};
@@ -1218,14 +1222,10 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
12181222
try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
12191223
}
12201224

1221-
// TODO this should not be performed if the user specifies `-flat_namespace` flag.
1222-
// See ld64 manpages.
1223-
try dylib.parseDependentLibs(self, opts.syslibroot);
1224-
12251225
return true;
12261226
}
12271227

1228-
fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {
1228+
fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
12291229
for (files) |file_name| {
12301230
const full_path = full_path: {
12311231
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
@@ -1239,24 +1239,70 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
12391239
if (try self.parseArchive(full_path)) continue;
12401240
if (try self.parseDylib(full_path, .{
12411241
.syslibroot = syslibroot,
1242+
.dependent_libs = dependent_libs,
12421243
})) continue;
12431244

12441245
log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
12451246
}
12461247
}
12471248

1248-
fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {
1249+
fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
12491250
for (libs) |lib| {
12501251
log.debug("parsing lib path '{s}'", .{lib});
12511252
if (try self.parseDylib(lib, .{
12521253
.syslibroot = syslibroot,
1254+
.dependent_libs = dependent_libs,
12531255
})) continue;
12541256
if (try self.parseArchive(lib)) continue;
12551257

12561258
log.warn("unknown filetype for a library: '{s}'", .{lib});
12571259
}
12581260
}
12591261

1262+
fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
1263+
// At this point, we can now parse dependents of dylibs preserving the inclusion order of:
1264+
// 1) anything on the linker line is parsed first
1265+
// 2) afterwards, we parse dependents of the included dylibs
1266+
// TODO this should not be performed if the user specifies `-flat_namespace` flag.
1267+
// See ld64 manpages.
1268+
var arena_alloc = std.heap.ArenaAllocator.init(self.base.allocator);
1269+
const arena = &arena_alloc.allocator;
1270+
defer arena_alloc.deinit();
1271+
1272+
while (dependent_libs.readItem()) |*id| {
1273+
defer id.deinit(self.base.allocator);
1274+
1275+
if (self.dylibs_map.contains(id.name)) continue;
1276+
1277+
const has_ext = blk: {
1278+
const basename = fs.path.basename(id.name);
1279+
break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
1280+
};
1281+
const extension = if (has_ext) fs.path.extension(id.name) else "";
1282+
const without_ext = if (has_ext) blk: {
1283+
const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable;
1284+
break :blk id.name[0..index];
1285+
} else id.name;
1286+
1287+
for (&[_][]const u8{ extension, ".tbd" }) |ext| {
1288+
const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ without_ext, ext });
1289+
const full_path = if (syslibroot) |root| try fs.path.join(arena, &.{ root, with_ext }) else with_ext;
1290+
1291+
log.debug("trying dependency at fully resolved path {s}", .{full_path});
1292+
1293+
const did_parse_successfully = try self.parseDylib(full_path, .{
1294+
.id = id.*,
1295+
.syslibroot = syslibroot,
1296+
.is_dependent = true,
1297+
.dependent_libs = dependent_libs,
1298+
});
1299+
if (did_parse_successfully) break;
1300+
} else {
1301+
log.warn("unable to resolve dependency {s}", .{id.name});
1302+
}
1303+
}
1304+
}
1305+
12601306
pub const MatchingSection = struct {
12611307
seg: u16,
12621308
sect: u16,
@@ -3992,12 +4038,16 @@ pub fn populateMissingMetadata(self: *MachO) !void {
39924038
self.base.options.emit.?.sub_path,
39934039
});
39944040
defer self.base.allocator.free(install_name);
4041+
const current_version = self.base.options.version orelse
4042+
std.builtin.Version{ .major = 1, .minor = 0, .patch = 0 };
4043+
const compat_version = self.base.options.compatibility_version orelse
4044+
std.builtin.Version{ .major = 1, .minor = 0, .patch = 0 };
39954045
var dylib_cmd = try commands.createLoadDylibCommand(
39964046
self.base.allocator,
39974047
install_name,
39984048
2,
3999-
0x10000, // TODO forward user-provided versions
4000-
0x10000,
4049+
current_version.major << 16 | current_version.minor << 8 | current_version.patch,
4050+
compat_version.major << 16 | compat_version.minor << 8 | compat_version.patch,
40014051
);
40024052
errdefer dylib_cmd.deinit(self.base.allocator);
40034053
dylib_cmd.inner.cmd = macho.LC_ID_DYLIB;

src/link/MachO/Dylib.zig

Lines changed: 16 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ id: ?Id = null,
3838
/// a symbol is referenced by an object file.
3939
symbols: std.StringArrayHashMapUnmanaged(void) = .{},
4040

41-
/// Array list of all dependent libs of this dylib.
42-
dependent_libs: std.ArrayListUnmanaged(Id) = .{},
43-
4441
pub const Id = struct {
4542
name: []const u8,
4643
timestamp: u32,
@@ -139,18 +136,14 @@ pub fn deinit(self: *Dylib, allocator: *Allocator) void {
139136
}
140137
self.symbols.deinit(allocator);
141138

142-
for (self.dependent_libs.items) |*id| {
143-
id.deinit(allocator);
144-
}
145-
self.dependent_libs.deinit(allocator);
146139
allocator.free(self.name);
147140

148141
if (self.id) |*id| {
149142
id.deinit(allocator);
150143
}
151144
}
152145

153-
pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target) !void {
146+
pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target, dependent_libs: anytype) !void {
154147
log.debug("parsing shared library '{s}'", .{self.name});
155148

156149
self.library_offset = try fat.getLibraryOffset(self.file.reader(), target);
@@ -172,12 +165,12 @@ pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target) !void {
172165
return error.MismatchedCpuArchitecture;
173166
}
174167

175-
try self.readLoadCommands(allocator, reader);
168+
try self.readLoadCommands(allocator, reader, dependent_libs);
176169
try self.parseId(allocator);
177170
try self.parseSymbols(allocator);
178171
}
179172

180-
fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype) !void {
173+
fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype, dependent_libs: anytype) !void {
181174
const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
182175

183176
try self.load_commands.ensureUnusedCapacity(allocator, self.header.?.ncmds);
@@ -198,8 +191,8 @@ fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype) !void
198191
macho.LC_REEXPORT_DYLIB => {
199192
if (should_lookup_reexports) {
200193
// Parse install_name to dependent dylib.
201-
const id = try Id.fromLoadCommand(allocator, cmd.Dylib);
202-
try self.dependent_libs.append(allocator, id);
194+
var id = try Id.fromLoadCommand(allocator, cmd.Dylib);
195+
try dependent_libs.writeItem(id);
203196
}
204197
},
205198
else => {
@@ -341,7 +334,13 @@ const TargetMatcher = struct {
341334
}
342335
};
343336

344-
pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
337+
pub fn parseFromStub(
338+
self: *Dylib,
339+
allocator: *Allocator,
340+
target: std.Target,
341+
lib_stub: LibStub,
342+
dependent_libs: anytype,
343+
) !void {
345344
if (lib_stub.inner.len == 0) return error.EmptyStubFile;
346345

347346
log.debug("parsing shared library from stub '{s}'", .{self.name});
@@ -416,8 +415,8 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
416415

417416
log.debug(" (found re-export '{s}')", .{lib});
418417

419-
const dep_id = try Id.default(allocator, lib);
420-
try self.dependent_libs.append(allocator, dep_id);
418+
var dep_id = try Id.default(allocator, lib);
419+
try dependent_libs.writeItem(dep_id);
421420
}
422421
}
423422
}
@@ -521,55 +520,10 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
521520

522521
log.debug(" (found re-export '{s}')", .{lib});
523522

524-
const dep_id = try Id.default(allocator, lib);
525-
try self.dependent_libs.append(allocator, dep_id);
523+
var dep_id = try Id.default(allocator, lib);
524+
try dependent_libs.writeItem(dep_id);
526525
}
527526
}
528527
}
529528
}
530529
}
531-
532-
pub fn parseDependentLibs(
533-
self: *Dylib,
534-
macho_file: *MachO,
535-
syslibroot: ?[]const u8,
536-
) !void {
537-
outer: for (self.dependent_libs.items) |id| {
538-
if (macho_file.dylibs_map.contains(id.name)) continue :outer;
539-
540-
const has_ext = blk: {
541-
const basename = fs.path.basename(id.name);
542-
break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
543-
};
544-
const extension = if (has_ext) fs.path.extension(id.name) else "";
545-
const without_ext = if (has_ext) blk: {
546-
const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable;
547-
break :blk id.name[0..index];
548-
} else id.name;
549-
550-
for (&[_][]const u8{ extension, ".tbd" }) |ext| {
551-
const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{
552-
without_ext,
553-
ext,
554-
});
555-
defer macho_file.base.allocator.free(with_ext);
556-
557-
const full_path = if (syslibroot) |root|
558-
try fs.path.join(macho_file.base.allocator, &.{ root, with_ext })
559-
else
560-
with_ext;
561-
defer if (syslibroot) |_| macho_file.base.allocator.free(full_path);
562-
563-
log.debug("trying dependency at fully resolved path {s}", .{full_path});
564-
565-
const did_parse_successfully = try macho_file.parseDylib(full_path, .{
566-
.id = id,
567-
.syslibroot = syslibroot,
568-
.is_dependent = true,
569-
});
570-
if (!did_parse_successfully) continue;
571-
} else {
572-
log.debug("unable to resolve dependency {s}", .{id.name});
573-
}
574-
}
575-
}

src/main.zig

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ fn buildOutputType(
564564
var root_src_file: ?[]const u8 = null;
565565
var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 };
566566
var have_version = false;
567+
var compatibility_version: ?std.builtin.Version = null;
567568
var strip = false;
568569
var single_threaded = false;
569570
var function_sections = false;
@@ -1613,6 +1614,29 @@ fn buildOutputType(
16131614
) catch |err| {
16141615
fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
16151616
};
1617+
} else if (mem.eql(u8, arg, "-framework") or mem.eql(u8, arg, "-weak_framework")) {
1618+
i += 1;
1619+
if (i >= linker_args.items.len) {
1620+
fatal("expected linker arg after '{s}'", .{arg});
1621+
}
1622+
try frameworks.append(linker_args.items[i]);
1623+
} else if (mem.eql(u8, arg, "-compatibility_version")) {
1624+
i += 1;
1625+
if (i >= linker_args.items.len) {
1626+
fatal("expected linker arg after '{s}'", .{arg});
1627+
}
1628+
compatibility_version = std.builtin.Version.parse(linker_args.items[i]) catch |err| {
1629+
fatal("unable to parse -compatibility_version '{s}': {s}", .{ linker_args.items[i], @errorName(err) });
1630+
};
1631+
} else if (mem.eql(u8, arg, "-current_version")) {
1632+
i += 1;
1633+
if (i >= linker_args.items.len) {
1634+
fatal("expected linker arg after '{s}'", .{arg});
1635+
}
1636+
version = std.builtin.Version.parse(linker_args.items[i]) catch |err| {
1637+
fatal("unable to parse -current_version '{s}': {s}", .{ linker_args.items[i], @errorName(err) });
1638+
};
1639+
have_version = true;
16161640
} else {
16171641
warn("unsupported linker arg: {s}", .{arg});
16181642
}

0 commit comments

Comments
 (0)