Commit 2b0d322ea0

Jakub Konka <kubkon@jakubkonka.com>
2021-05-22 13:08:00
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.
1 parent f4101c1
Changed files (10)
src
test
standalone
link_static_lib_as_system_lib
src/link/MachO/Archive.zig
@@ -27,14 +27,14 @@ toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},
 // `struct ar_hdr', and as many bytes of member file data as its `ar_size'
 // member indicates, for each member file.
 /// String that begins an archive file.
-pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
+const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
 /// Size of that string.
-pub const SARMAG: u4 = 8;
+const SARMAG: u4 = 8;
 
 /// String in ar_fmag at the end of each header.
-pub const ARFMAG: *const [2:0]u8 = "`\n";
+const ARFMAG: *const [2:0]u8 = "`\n";
 
-pub const ar_hdr = extern struct {
+const ar_hdr = extern struct {
     /// Member file name, sometimes / terminated.
     ar_name: [16]u8,
 
@@ -60,7 +60,7 @@ pub const ar_hdr = extern struct {
         Name: []const u8,
         Length: u64,
     };
-    pub fn nameOrLength(self: ar_hdr) !NameOrLength {
+    fn nameOrLength(self: ar_hdr) !NameOrLength {
         const value = getValue(&self.ar_name);
         const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
         const len = value.len;
@@ -75,7 +75,7 @@ pub const ar_hdr = extern struct {
         }
     }
 
-    pub fn size(self: ar_hdr) !u64 {
+    fn size(self: ar_hdr) !u64 {
         const value = getValue(&self.ar_size);
         return std.fmt.parseInt(u64, value, 10);
     }
@@ -231,3 +231,9 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
 
     return object;
 }
+
+pub fn isArchive(file: fs.File) !bool {
+    const magic = try file.reader().readBytesNoEof(Archive.SARMAG);
+    try file.seekTo(0);
+    return mem.eql(u8, &magic, Archive.ARMAG);
+}
src/link/MachO/Dylib.zig
@@ -183,3 +183,9 @@ pub fn parseSymbols(self: *Dylib) !void {
         try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
     }
 }
+
+pub fn isDylib(file: fs.File) !bool {
+    const header = try file.reader().readStruct(macho.mach_header_64);
+    try file.seekTo(0);
+    return header.filetype == macho.MH_DYLIB;
+}
src/link/MachO/Object.zig
@@ -485,3 +485,9 @@ pub fn parseDataInCode(self: *Object) !void {
         try self.data_in_code_entries.append(self.allocator, dice);
     }
 }
+
+pub fn isObject(file: fs.File) !bool {
+    const header = try file.reader().readStruct(macho.mach_header_64);
+    try file.seekTo(0);
+    return header.filetype == macho.MH_OBJECT;
+}
src/link/MachO/Zld.zig
@@ -187,7 +187,7 @@ pub fn closeFiles(self: Zld) void {
 }
 
 const LinkArgs = struct {
-    shared_libs: []const []const u8,
+    libs: []const []const u8,
     rpaths: []const []const u8,
 };
 
@@ -229,7 +229,7 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L
     try self.populateMetadata();
     try self.addRpaths(args.rpaths);
     try self.parseInputFiles(files);
-    try self.parseDylibs(args.shared_libs);
+    try self.parseLibs(args.libs);
     try self.resolveSymbols();
     try self.resolveStubsAndGotEntries();
     try self.updateMetadata();
@@ -265,13 +265,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         };
 
         try_object: {
-            const header = try file.reader().readStruct(macho.mach_header_64);
-            if (header.filetype != macho.MH_OBJECT) {
-                try file.seekTo(0);
-                break :try_object;
-            }
-
-            try file.seekTo(0);
+            if (!(try Object.isObject(file))) break :try_object;
             try classified.append(.{
                 .kind = .object,
                 .file = file,
@@ -281,13 +275,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         }
 
         try_archive: {
-            const magic = try file.reader().readBytesNoEof(Archive.SARMAG);
-            if (!mem.eql(u8, &magic, Archive.ARMAG)) {
-                try file.seekTo(0);
-                break :try_archive;
-            }
-
-            try file.seekTo(0);
+            if (!(try Archive.isArchive(file))) break :try_archive;
             try classified.append(.{
                 .kind = .archive,
                 .file = file,
@@ -297,13 +285,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         }
 
         try_dylib: {
-            const header = try file.reader().readStruct(macho.mach_header_64);
-            if (header.filetype != macho.MH_DYLIB) {
-                try file.seekTo(0);
-                break :try_dylib;
-            }
-
-            try file.seekTo(0);
+            if (!(try Dylib.isDylib(file))) break :try_dylib;
             try classified.append(.{
                 .kind = .dylib,
                 .file = file,
@@ -312,7 +294,8 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
             continue;
         }
 
-        log.debug("unexpected input file of unknown type '{s}'", .{file_name});
+        file.close();
+        log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
     }
 
     // Based on our classification, proceed with parsing.
@@ -373,35 +356,52 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
     }
 }
 
-fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void {
-    for (shared_libs) |lib| {
-        const dylib = try self.allocator.create(Dylib);
-        errdefer self.allocator.destroy(dylib);
-
-        dylib.* = Dylib.init(self.allocator);
-        dylib.arch = self.arch.?;
-        dylib.name = try self.allocator.dupe(u8, lib);
-        dylib.file = try fs.cwd().openFile(lib, .{});
-
-        const ordinal = @intCast(u16, self.dylibs.items.len);
-        dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
-
-        // TODO Defer parsing of the dylibs until they are actually needed
-        try dylib.parse();
-        try self.dylibs.append(self.allocator, dylib);
-
-        // Add LC_LOAD_DYLIB command
-        const dylib_id = dylib.id orelse unreachable;
-        var dylib_cmd = try createLoadDylibCommand(
-            self.allocator,
-            dylib_id.name,
-            dylib_id.timestamp,
-            dylib_id.current_version,
-            dylib_id.compatibility_version,
-        );
-        errdefer dylib_cmd.deinit(self.allocator);
-
-        try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
+fn parseLibs(self: *Zld, libs: []const []const u8) !void {
+    for (libs) |lib| {
+        const file = try fs.cwd().openFile(lib, .{});
+
+        if (try Dylib.isDylib(file)) {
+            const dylib = try self.allocator.create(Dylib);
+            errdefer self.allocator.destroy(dylib);
+
+            dylib.* = Dylib.init(self.allocator);
+            dylib.arch = self.arch.?;
+            dylib.name = try self.allocator.dupe(u8, lib);
+            dylib.file = file;
+
+            const ordinal = @intCast(u16, self.dylibs.items.len);
+            dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
+
+            // TODO Defer parsing of the dylibs until they are actually needed
+            try dylib.parse();
+            try self.dylibs.append(self.allocator, dylib);
+
+            // Add LC_LOAD_DYLIB command
+            const dylib_id = dylib.id orelse unreachable;
+            var dylib_cmd = try createLoadDylibCommand(
+                self.allocator,
+                dylib_id.name,
+                dylib_id.timestamp,
+                dylib_id.current_version,
+                dylib_id.compatibility_version,
+            );
+            errdefer dylib_cmd.deinit(self.allocator);
+
+            try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
+        } else if (try Archive.isArchive(file)) {
+            const archive = try self.allocator.create(Archive);
+            errdefer self.allocator.destroy(archive);
+
+            archive.* = Archive.init(self.allocator);
+            archive.arch = self.arch.?;
+            archive.name = try self.allocator.dupe(u8, lib);
+            archive.file = file;
+            try archive.parse();
+            try self.archives.append(self.allocator, archive);
+        } else {
+            file.close();
+            log.warn("unknown filetype for a library: '{s}'", .{lib});
+        }
     }
 }
 
src/link/MachO.zig
@@ -698,8 +698,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 try positionals.append(comp.libcxx_static_lib.?.full_object_path);
             }
 
-            // Shared libraries.
-            var shared_libs = std.ArrayList([]const u8).init(arena);
+            // Shared and static libraries passed via `-l` flag.
+            var libs = std.ArrayList([]const u8).init(arena);
             var search_lib_names = std.ArrayList([]const u8).init(arena);
 
             const system_libs = self.base.options.system_libs.items();
@@ -708,9 +708,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 // 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 .dylib files, in which
                 // case we want to avoid prepending "-l".
-                // TODO I think they should go as an input file instead of via shared_libs.
                 if (Compilation.classifyFileExt(link_lib) == .shared_library) {
-                    try shared_libs.append(link_lib);
+                    try positionals.append(link_lib);
                     continue;
                 }
 
@@ -760,24 +759,29 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 }
             }
 
-            for (search_lib_names.items) |l_name| {
-                // TODO text-based API, or .tbd files.
-                const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.dylib", .{l_name});
+            // TODO text-based API, or .tbd files.
+            const exts = &[_][]const u8{ "dylib", "a" };
 
+            for (search_lib_names.items) |l_name| {
                 var found = false;
-                for (search_lib_dirs.items) |lib_dir| {
-                    const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext });
 
-                    // Check if the dylib file exists.
-                    const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
-                        error.FileNotFound => continue,
-                        else => |e| return e,
-                    };
-                    defer tmp.close();
+                for (exts) |ext| ext: {
+                    const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.{s}", .{ l_name, ext });
+
+                    for (search_lib_dirs.items) |lib_dir| {
+                        const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext });
 
-                    try shared_libs.append(full_path);
-                    found = true;
-                    break;
+                        // Check if the dylib file exists.
+                        const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
+                            error.FileNotFound => continue,
+                            else => |e| return e,
+                        };
+                        defer tmp.close();
+
+                        try libs.append(full_path);
+                        found = true;
+                        break :ext;
+                    }
                 }
 
                 if (!found) {
@@ -835,7 +839,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             }
 
             try zld.link(positionals.items, full_out_path, .{
-                .shared_libs = shared_libs.items,
+                .libs = libs.items,
                 .rpaths = rpaths.items,
             });
 
test/standalone.zig
@@ -14,6 +14,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
     cases.addBuildFile("test/standalone/global_linkage/build.zig");
     cases.addBuildFile("test/standalone/static_c_lib/build.zig");
     cases.addBuildFile("test/standalone/link_interdependent_static_c_libs/build.zig");
+    cases.addBuildFile("test/standalone/link_static_lib_as_system_lib/build.zig");
     cases.addBuildFile("test/standalone/issue_339/build.zig");
     cases.addBuildFile("test/standalone/issue_794/build.zig");
     cases.addBuildFile("test/standalone/issue_5825/build.zig");