authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-05-22 13:08:00+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-05-22 16:12:45+02:00
log2b0d322ea003907dc2111864cea259e5e0043328
tree8707c105b098f8e1aa504cc3adcaeaaf4cfdf244
parentf4101c1153980b887e9aa8850ac0a9dd88192140

zld: permit system static libs

This commits permits passing in static archives using the system lib flag `-la`. With this commit, `zig ld` will now look firstly for a dynamic library (which always takes precedence), and will fall back on `liba.a` if the dylib is not found. The static archive is searched for in the system lib search dirs like the dylibs.

10 files changed, 138 insertions(+), 78 deletions(-)

src/link/MachO.zig+23-19
...@@ -698,8 +698,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {...@@ -698,8 +698,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
698 try positionals.append(comp.libcxx_static_lib.?.full_object_path);698 try positionals.append(comp.libcxx_static_lib.?.full_object_path);
699 }699 }
700700
701 // Shared libraries.701 // Shared and static libraries passed via `-l` flag.
702 var shared_libs = std.ArrayList([]const u8).init(arena);702 var libs = std.ArrayList([]const u8).init(arena);
703 var search_lib_names = std.ArrayList([]const u8).init(arena);703 var search_lib_names = std.ArrayList([]const u8).init(arena);
704704
705 const system_libs = self.base.options.system_libs.items();705 const system_libs = self.base.options.system_libs.items();
...@@ -708,9 +708,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {...@@ -708,9 +708,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
708 // By this time, we depend on these libs being dynamically linked libraries and not static libraries708 // By this time, we depend on these libs being dynamically linked libraries and not static libraries
709 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which709 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
710 // case we want to avoid prepending "-l".710 // case we want to avoid prepending "-l".
711 // TODO I think they should go as an input file instead of via shared_libs.
712 if (Compilation.classifyFileExt(link_lib) == .shared_library) {711 if (Compilation.classifyFileExt(link_lib) == .shared_library) {
713 try shared_libs.append(link_lib);712 try positionals.append(link_lib);
714 continue;713 continue;
715 }714 }
716715
...@@ -760,24 +759,29 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {...@@ -760,24 +759,29 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
760 }759 }
761 }760 }
762761
763 for (search_lib_names.items) |l_name| {762 // TODO text-based API, or .tbd files.
764 // TODO text-based API, or .tbd files.763 const exts = &[_][]const u8{ "dylib", "a" };
765 const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.dylib", .{l_name});
766764
765 for (search_lib_names.items) |l_name| {
767 var found = false;766 var found = false;
768 for (search_lib_dirs.items) |lib_dir| {
769 const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext });
770767
771 // Check if the dylib file exists.768 for (exts) |ext| ext: {
772 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {769 const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.{s}", .{ l_name, ext });
773 error.FileNotFound => continue,770
774 else => |e| return e,771 for (search_lib_dirs.items) |lib_dir| {
775 };772 const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext });
776 defer tmp.close();
777773
778 try shared_libs.append(full_path);774 // Check if the dylib file exists.
779 found = true;775 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
780 break;776 error.FileNotFound => continue,
777 else => |e| return e,
778 };
779 defer tmp.close();
780
781 try libs.append(full_path);
782 found = true;
783 break :ext;
784 }
781 }785 }
782786
783 if (!found) {787 if (!found) {
...@@ -835,7 +839,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {...@@ -835,7 +839,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
835 }839 }
836840
837 try zld.link(positionals.items, full_out_path, .{841 try zld.link(positionals.items, full_out_path, .{
838 .shared_libs = shared_libs.items,842 .libs = libs.items,
839 .rpaths = rpaths.items,843 .rpaths = rpaths.items,
840 });844 });
841845
src/link/MachO/Archive.zig+12-6
...@@ -27,14 +27,14 @@ toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},...@@ -27,14 +27,14 @@ toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},
27// `struct ar_hdr', and as many bytes of member file data as its `ar_size'27// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
28// member indicates, for each member file.28// member indicates, for each member file.
29/// String that begins an archive file.29/// String that begins an archive file.
30pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";30const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
31/// Size of that string.31/// Size of that string.
32pub const SARMAG: u4 = 8;32const SARMAG: u4 = 8;
3333
34/// String in ar_fmag at the end of each header.34/// String in ar_fmag at the end of each header.
35pub const ARFMAG: *const [2:0]u8 = "`\n";35const ARFMAG: *const [2:0]u8 = "`\n";
3636
37pub const ar_hdr = extern struct {37const ar_hdr = extern struct {
38 /// Member file name, sometimes / terminated.38 /// Member file name, sometimes / terminated.
39 ar_name: [16]u8,39 ar_name: [16]u8,
4040
...@@ -60,7 +60,7 @@ pub const ar_hdr = extern struct {...@@ -60,7 +60,7 @@ pub const ar_hdr = extern struct {
60 Name: []const u8,60 Name: []const u8,
61 Length: u64,61 Length: u64,
62 };62 };
63 pub fn nameOrLength(self: ar_hdr) !NameOrLength {63 fn nameOrLength(self: ar_hdr) !NameOrLength {
64 const value = getValue(&self.ar_name);64 const value = getValue(&self.ar_name);
65 const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;65 const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
66 const len = value.len;66 const len = value.len;
...@@ -75,7 +75,7 @@ pub const ar_hdr = extern struct {...@@ -75,7 +75,7 @@ pub const ar_hdr = extern struct {
75 }75 }
76 }76 }
7777
78 pub fn size(self: ar_hdr) !u64 {78 fn size(self: ar_hdr) !u64 {
79 const value = getValue(&self.ar_size);79 const value = getValue(&self.ar_size);
80 return std.fmt.parseInt(u64, value, 10);80 return std.fmt.parseInt(u64, value, 10);
81 }81 }
...@@ -231,3 +231,9 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {...@@ -231,3 +231,9 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
231231
232 return object;232 return object;
233}233}
234
235pub fn isArchive(file: fs.File) !bool {
236 const magic = try file.reader().readBytesNoEof(Archive.SARMAG);
237 try file.seekTo(0);
238 return mem.eql(u8, &magic, Archive.ARMAG);
239}
src/link/MachO/Dylib.zig+6
...@@ -183,3 +183,9 @@ pub fn parseSymbols(self: *Dylib) !void {...@@ -183,3 +183,9 @@ pub fn parseSymbols(self: *Dylib) !void {
183 try self.symbols.putNoClobber(self.allocator, name, &proxy.base);183 try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
184 }184 }
185}185}
186
187pub fn isDylib(file: fs.File) !bool {
188 const header = try file.reader().readStruct(macho.mach_header_64);
189 try file.seekTo(0);
190 return header.filetype == macho.MH_DYLIB;
191}
src/link/MachO/Object.zig+6
...@@ -485,3 +485,9 @@ pub fn parseDataInCode(self: *Object) !void {...@@ -485,3 +485,9 @@ pub fn parseDataInCode(self: *Object) !void {
485 try self.data_in_code_entries.append(self.allocator, dice);485 try self.data_in_code_entries.append(self.allocator, dice);
486 }486 }
487}487}
488
489pub fn isObject(file: fs.File) !bool {
490 const header = try file.reader().readStruct(macho.mach_header_64);
491 try file.seekTo(0);
492 return header.filetype == macho.MH_OBJECT;
493}
src/link/MachO/Zld.zig+53-53
...@@ -187,7 +187,7 @@ pub fn closeFiles(self: Zld) void {...@@ -187,7 +187,7 @@ pub fn closeFiles(self: Zld) void {
187}187}
188188
189const LinkArgs = struct {189const LinkArgs = struct {
190 shared_libs: []const []const u8,190 libs: []const []const u8,
191 rpaths: []const []const u8,191 rpaths: []const []const u8,
192};192};
193193
...@@ -229,7 +229,7 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L...@@ -229,7 +229,7 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L
229 try self.populateMetadata();229 try self.populateMetadata();
230 try self.addRpaths(args.rpaths);230 try self.addRpaths(args.rpaths);
231 try self.parseInputFiles(files);231 try self.parseInputFiles(files);
232 try self.parseDylibs(args.shared_libs);232 try self.parseLibs(args.libs);
233 try self.resolveSymbols();233 try self.resolveSymbols();
234 try self.resolveStubsAndGotEntries();234 try self.resolveStubsAndGotEntries();
235 try self.updateMetadata();235 try self.updateMetadata();
...@@ -265,13 +265,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {...@@ -265,13 +265,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
265 };265 };
266266
267 try_object: {267 try_object: {
268 const header = try file.reader().readStruct(macho.mach_header_64);268 if (!(try Object.isObject(file))) break :try_object;
269 if (header.filetype != macho.MH_OBJECT) {
270 try file.seekTo(0);
271 break :try_object;
272 }
273
274 try file.seekTo(0);
275 try classified.append(.{269 try classified.append(.{
276 .kind = .object,270 .kind = .object,
277 .file = file,271 .file = file,
...@@ -281,13 +275,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {...@@ -281,13 +275,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
281 }275 }
282276
283 try_archive: {277 try_archive: {
284 const magic = try file.reader().readBytesNoEof(Archive.SARMAG);278 if (!(try Archive.isArchive(file))) break :try_archive;
285 if (!mem.eql(u8, &magic, Archive.ARMAG)) {
286 try file.seekTo(0);
287 break :try_archive;
288 }
289
290 try file.seekTo(0);
291 try classified.append(.{279 try classified.append(.{
292 .kind = .archive,280 .kind = .archive,
293 .file = file,281 .file = file,
...@@ -297,13 +285,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {...@@ -297,13 +285,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
297 }285 }
298286
299 try_dylib: {287 try_dylib: {
300 const header = try file.reader().readStruct(macho.mach_header_64);288 if (!(try Dylib.isDylib(file))) break :try_dylib;
301 if (header.filetype != macho.MH_DYLIB) {
302 try file.seekTo(0);
303 break :try_dylib;
304 }
305
306 try file.seekTo(0);
307 try classified.append(.{289 try classified.append(.{
308 .kind = .dylib,290 .kind = .dylib,
309 .file = file,291 .file = file,
...@@ -312,7 +294,8 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {...@@ -312,7 +294,8 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
312 continue;294 continue;
313 }295 }
314296
315 log.debug("unexpected input file of unknown type '{s}'", .{file_name});297 file.close();
298 log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
316 }299 }
317300
318 // Based on our classification, proceed with parsing.301 // Based on our classification, proceed with parsing.
...@@ -373,35 +356,52 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {...@@ -373,35 +356,52 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
373 }356 }
374}357}
375358
376fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void {359fn parseLibs(self: *Zld, libs: []const []const u8) !void {
377 for (shared_libs) |lib| {360 for (libs) |lib| {
378 const dylib = try self.allocator.create(Dylib);361 const file = try fs.cwd().openFile(lib, .{});
379 errdefer self.allocator.destroy(dylib);362
380363 if (try Dylib.isDylib(file)) {
381 dylib.* = Dylib.init(self.allocator);364 const dylib = try self.allocator.create(Dylib);
382 dylib.arch = self.arch.?;365 errdefer self.allocator.destroy(dylib);
383 dylib.name = try self.allocator.dupe(u8, lib);366
384 dylib.file = try fs.cwd().openFile(lib, .{});367 dylib.* = Dylib.init(self.allocator);
385368 dylib.arch = self.arch.?;
386 const ordinal = @intCast(u16, self.dylibs.items.len);369 dylib.name = try self.allocator.dupe(u8, lib);
387 dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem370 dylib.file = file;
388371
389 // TODO Defer parsing of the dylibs until they are actually needed372 const ordinal = @intCast(u16, self.dylibs.items.len);
390 try dylib.parse();373 dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
391 try self.dylibs.append(self.allocator, dylib);374
392375 // TODO Defer parsing of the dylibs until they are actually needed
393 // Add LC_LOAD_DYLIB command376 try dylib.parse();
394 const dylib_id = dylib.id orelse unreachable;377 try self.dylibs.append(self.allocator, dylib);
395 var dylib_cmd = try createLoadDylibCommand(378
396 self.allocator,379 // Add LC_LOAD_DYLIB command
397 dylib_id.name,380 const dylib_id = dylib.id orelse unreachable;
398 dylib_id.timestamp,381 var dylib_cmd = try createLoadDylibCommand(
399 dylib_id.current_version,382 self.allocator,
400 dylib_id.compatibility_version,383 dylib_id.name,
401 );384 dylib_id.timestamp,
402 errdefer dylib_cmd.deinit(self.allocator);385 dylib_id.current_version,
403386 dylib_id.compatibility_version,
404 try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });387 );
388 errdefer dylib_cmd.deinit(self.allocator);
389
390 try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
391 } else if (try Archive.isArchive(file)) {
392 const archive = try self.allocator.create(Archive);
393 errdefer self.allocator.destroy(archive);
394
395 archive.* = Archive.init(self.allocator);
396 archive.arch = self.arch.?;
397 archive.name = try self.allocator.dupe(u8, lib);
398 archive.file = file;
399 try archive.parse();
400 try self.archives.append(self.allocator, archive);
401 } else {
402 file.close();
403 log.warn("unknown filetype for a library: '{s}'", .{lib});
404 }
405 }405 }
406}406}
407407
test/standalone.zig+1
...@@ -14,6 +14,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -14,6 +14,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
14 cases.addBuildFile("test/standalone/global_linkage/build.zig");14 cases.addBuildFile("test/standalone/global_linkage/build.zig");
15 cases.addBuildFile("test/standalone/static_c_lib/build.zig");15 cases.addBuildFile("test/standalone/static_c_lib/build.zig");
16 cases.addBuildFile("test/standalone/link_interdependent_static_c_libs/build.zig");16 cases.addBuildFile("test/standalone/link_interdependent_static_c_libs/build.zig");
17 cases.addBuildFile("test/standalone/link_static_lib_as_system_lib/build.zig");
17 cases.addBuildFile("test/standalone/issue_339/build.zig");18 cases.addBuildFile("test/standalone/issue_339/build.zig");
18 cases.addBuildFile("test/standalone/issue_794/build.zig");19 cases.addBuildFile("test/standalone/issue_794/build.zig");
19 cases.addBuildFile("test/standalone/issue_5825/build.zig");20 cases.addBuildFile("test/standalone/issue_5825/build.zig");
test/standalone/link_static_lib_as_system_lib/a.c created+4
...@@ -0,0 +1,4 @@
1#include "a.h"
2int32_t add(int32_t a, int32_t b) {
3 return a + b;
4}
test/standalone/link_static_lib_as_system_lib/a.h created+2
...@@ -0,0 +1,2 @@
1#include <stdint.h>
2int32_t add(int32_t a, int32_t b);
test/standalone/link_static_lib_as_system_lib/build.zig created+23
...@@ -0,0 +1,23 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const lib_a = b.addStaticLibrary("a", null);
8 lib_a.addCSourceFile("a.c", &[_][]const u8{});
9 lib_a.setBuildMode(mode);
10 lib_a.addIncludeDir(".");
11 lib_a.install();
12
13 const test_exe = b.addTest("main.zig");
14 test_exe.setBuildMode(mode);
15 test_exe.linkSystemLibrary("a"); // force linking liba.a as -la
16 test_exe.addSystemIncludeDir(".");
17 const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch unreachable;
18 test_exe.addLibPath(search_path);
19
20 const test_step = b.step("test", "Test it");
21 test_step.dependOn(b.getInstallStep());
22 test_step.dependOn(&test_exe.step);
23}
test/standalone/link_static_lib_as_system_lib/main.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const expect = std.testing.expect;
3const c = @cImport(@cInclude("a.h"));
4
5test "import C add" {
6 const result = c.add(2, 1);
7 try expect(result == 3);
8}