| author | |
| committer | |
| log | 2b0d322ea003907dc2111864cea259e5e0043328 |
| tree | 8707c105b098f8e1aa504cc3adcaeaaf4cfdf244 |
| parent | f4101c1153980b887e9aa8850ac0a9dd88192140 |
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 | } |
| 700 | 700 | ||
| 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); |
| 704 | 704 | ||
| 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 libraries | 708 | // 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 which | 709 | // (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 | } |
| 716 | 715 | ||
| ... | @@ -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 | } |
| 762 | 761 | ||
| 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}); | ||
| 766 | 764 | ||
| 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 }); | ||
| 770 | 767 | ||
| 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(); | ||
| 777 | 773 | ||
| 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 | } |
| 782 | 786 | ||
| 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 | } |
| 836 | 840 | ||
| 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 | }); |
| 841 | 845 |
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. |
| 30 | pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; | 30 | const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; |
| 31 | /// Size of that string. | 31 | /// Size of that string. |
| 32 | pub const SARMAG: u4 = 8; | 32 | const SARMAG: u4 = 8; |
| 33 | 33 | ||
| 34 | /// String in ar_fmag at the end of each header. | 34 | /// String in ar_fmag at the end of each header. |
| 35 | pub const ARFMAG: *const [2:0]u8 = "`\n"; | 35 | const ARFMAG: *const [2:0]u8 = "`\n"; |
| 36 | 36 | ||
| 37 | pub const ar_hdr = extern struct { | 37 | const 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, |
| 40 | 40 | ||
| ... | @@ -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 | } |
| 77 | 77 | ||
| 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 { |
| 231 | 231 | ||
| 232 | return object; | 232 | return object; |
| 233 | } | 233 | } |
| 234 | |||
| 235 | pub 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 | |||
| 187 | pub 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 | |||
| 489 | pub 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 | } |
| 188 | 188 | ||
| 189 | const LinkArgs = struct { | 189 | const 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 | }; |
| 193 | 193 | ||
| ... | @@ -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 | }; |
| 266 | 266 | ||
| 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 | } |
| 282 | 276 | ||
| 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 | } |
| 298 | 286 | ||
| 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 | } |
| 314 | 296 | ||
| 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 | } |
| 317 | 300 | ||
| 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 | } |
| 375 | 358 | ||
| 376 | fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void { | 359 | fn 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 | |
| 380 | 363 | 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); |
| 385 | 368 | 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 libSystem | 370 | dylib.file = file; |
| 388 | 371 | ||
| 389 | // TODO Defer parsing of the dylibs until they are actually needed | 372 | 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 | |
| 392 | 375 | // TODO Defer parsing of the dylibs until they are actually needed | |
| 393 | // Add LC_LOAD_DYLIB command | 376 | 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, |
| 403 | 386 | 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 | } |
| 407 | 407 |
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" | ||
| 2 | int32_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> | ||
| 2 | int32_t add(int32_t a, int32_t b); | ||
test/standalone/link_static_lib_as_system_lib/build.zig created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Builder = std.build.Builder; | ||
| 3 | |||
| 4 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const expect = std.testing.expect; | ||
| 3 | const c = @cImport(@cInclude("a.h")); | ||
| 4 | |||
| 5 | test "import C add" { | ||
| 6 | const result = c.add(2, 1); | ||
| 7 | try expect(result == 3); | ||
| 8 | } | ||