| 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 | 698 | try positionals.append(comp.libcxx_static_lib.?.full_object_path); |
| 699 | 699 | } |
| 700 | 700 | |
| 701 | // Shared libraries. | |
| 702 | var shared_libs = std.ArrayList([]const u8).init(arena); | |
| 701 | // Shared and static libraries passed via `-l` flag. | |
| 702 | var libs = std.ArrayList([]const u8).init(arena); | |
| 703 | 703 | var search_lib_names = std.ArrayList([]const u8).init(arena); |
| 704 | 704 | |
| 705 | 705 | const system_libs = self.base.options.system_libs.items(); |
| ... | ... | @@ -708,9 +708,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { |
| 708 | 708 | // By this time, we depend on these libs being dynamically linked libraries and not static libraries |
| 709 | 709 | // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which |
| 710 | 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 | 711 | if (Compilation.classifyFileExt(link_lib) == .shared_library) { |
| 713 | try shared_libs.append(link_lib); | |
| 712 | try positionals.append(link_lib); | |
| 714 | 713 | continue; |
| 715 | 714 | } |
| 716 | 715 | |
| ... | ... | @@ -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| { | |
| 764 | // TODO text-based API, or .tbd files. | |
| 765 | const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.dylib", .{l_name}); | |
| 762 | // TODO text-based API, or .tbd files. | |
| 763 | const exts = &[_][]const u8{ "dylib", "a" }; | |
| 766 | 764 | |
| 765 | for (search_lib_names.items) |l_name| { | |
| 767 | 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. | |
| 772 | const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) { | |
| 773 | error.FileNotFound => continue, | |
| 774 | else => |e| return e, | |
| 775 | }; | |
| 776 | defer tmp.close(); | |
| 768 | for (exts) |ext| ext: { | |
| 769 | const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.{s}", .{ l_name, ext }); | |
| 770 | ||
| 771 | for (search_lib_dirs.items) |lib_dir| { | |
| 772 | const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext }); | |
| 777 | 773 | |
| 778 | try shared_libs.append(full_path); | |
| 779 | found = true; | |
| 780 | break; | |
| 774 | // Check if the dylib file exists. | |
| 775 | const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) { | |
| 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 | 787 | if (!found) { |
| ... | ... | @@ -835,7 +839,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { |
| 835 | 839 | } |
| 836 | 840 | |
| 837 | 841 | try zld.link(positionals.items, full_out_path, .{ |
| 838 | .shared_libs = shared_libs.items, | |
| 842 | .libs = libs.items, | |
| 839 | 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 | 27 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' |
| 28 | 28 | // member indicates, for each member file. |
| 29 | 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 | 31 | /// Size of that string. |
| 32 | pub const SARMAG: u4 = 8; | |
| 32 | const SARMAG: u4 = 8; | |
| 33 | 33 | |
| 34 | 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 | 38 | /// Member file name, sometimes / terminated. |
| 39 | 39 | ar_name: [16]u8, |
| 40 | 40 | |
| ... | ... | @@ -60,7 +60,7 @@ pub const ar_hdr = extern struct { |
| 60 | 60 | Name: []const u8, |
| 61 | 61 | Length: u64, |
| 62 | 62 | }; |
| 63 | pub fn nameOrLength(self: ar_hdr) !NameOrLength { | |
| 63 | fn nameOrLength(self: ar_hdr) !NameOrLength { | |
| 64 | 64 | const value = getValue(&self.ar_name); |
| 65 | 65 | const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive; |
| 66 | 66 | const len = value.len; |
| ... | ... | @@ -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 | 79 | const value = getValue(&self.ar_size); |
| 80 | 80 | return std.fmt.parseInt(u64, value, 10); |
| 81 | 81 | } |
| ... | ... | @@ -231,3 +231,9 @@ pub fn parseObject(self: Archive, offset: u32) !*Object { |
| 231 | 231 | |
| 232 | 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 | 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 | 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 | 187 | } |
| 188 | 188 | |
| 189 | 189 | const LinkArgs = struct { |
| 190 | shared_libs: []const []const u8, | |
| 190 | libs: []const []const u8, | |
| 191 | 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 | 229 | try self.populateMetadata(); |
| 230 | 230 | try self.addRpaths(args.rpaths); |
| 231 | 231 | try self.parseInputFiles(files); |
| 232 | try self.parseDylibs(args.shared_libs); | |
| 232 | try self.parseLibs(args.libs); | |
| 233 | 233 | try self.resolveSymbols(); |
| 234 | 234 | try self.resolveStubsAndGotEntries(); |
| 235 | 235 | try self.updateMetadata(); |
| ... | ... | @@ -265,13 +265,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { |
| 265 | 265 | }; |
| 266 | 266 | |
| 267 | 267 | try_object: { |
| 268 | const header = try file.reader().readStruct(macho.mach_header_64); | |
| 269 | if (header.filetype != macho.MH_OBJECT) { | |
| 270 | try file.seekTo(0); | |
| 271 | break :try_object; | |
| 272 | } | |
| 273 | ||
| 274 | try file.seekTo(0); | |
| 268 | if (!(try Object.isObject(file))) break :try_object; | |
| 275 | 269 | try classified.append(.{ |
| 276 | 270 | .kind = .object, |
| 277 | 271 | .file = file, |
| ... | ... | @@ -281,13 +275,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { |
| 281 | 275 | } |
| 282 | 276 | |
| 283 | 277 | try_archive: { |
| 284 | const magic = try file.reader().readBytesNoEof(Archive.SARMAG); | |
| 285 | if (!mem.eql(u8, &magic, Archive.ARMAG)) { | |
| 286 | try file.seekTo(0); | |
| 287 | break :try_archive; | |
| 288 | } | |
| 289 | ||
| 290 | try file.seekTo(0); | |
| 278 | if (!(try Archive.isArchive(file))) break :try_archive; | |
| 291 | 279 | try classified.append(.{ |
| 292 | 280 | .kind = .archive, |
| 293 | 281 | .file = file, |
| ... | ... | @@ -297,13 +285,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { |
| 297 | 285 | } |
| 298 | 286 | |
| 299 | 287 | try_dylib: { |
| 300 | const header = try file.reader().readStruct(macho.mach_header_64); | |
| 301 | if (header.filetype != macho.MH_DYLIB) { | |
| 302 | try file.seekTo(0); | |
| 303 | break :try_dylib; | |
| 304 | } | |
| 305 | ||
| 306 | try file.seekTo(0); | |
| 288 | if (!(try Dylib.isDylib(file))) break :try_dylib; | |
| 307 | 289 | try classified.append(.{ |
| 308 | 290 | .kind = .dylib, |
| 309 | 291 | .file = file, |
| ... | ... | @@ -312,7 +294,8 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { |
| 312 | 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 | 301 | // Based on our classification, proceed with parsing. |
| ... | ... | @@ -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 { | |
| 377 | for (shared_libs) |lib| { | |
| 378 | const dylib = try self.allocator.create(Dylib); | |
| 379 | errdefer self.allocator.destroy(dylib); | |
| 380 | ||
| 381 | dylib.* = Dylib.init(self.allocator); | |
| 382 | dylib.arch = self.arch.?; | |
| 383 | dylib.name = try self.allocator.dupe(u8, lib); | |
| 384 | dylib.file = try fs.cwd().openFile(lib, .{}); | |
| 385 | ||
| 386 | const ordinal = @intCast(u16, self.dylibs.items.len); | |
| 387 | dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem | |
| 388 | ||
| 389 | // TODO Defer parsing of the dylibs until they are actually needed | |
| 390 | try dylib.parse(); | |
| 391 | try self.dylibs.append(self.allocator, dylib); | |
| 392 | ||
| 393 | // Add LC_LOAD_DYLIB command | |
| 394 | const dylib_id = dylib.id orelse unreachable; | |
| 395 | var dylib_cmd = try createLoadDylibCommand( | |
| 396 | self.allocator, | |
| 397 | dylib_id.name, | |
| 398 | dylib_id.timestamp, | |
| 399 | dylib_id.current_version, | |
| 400 | dylib_id.compatibility_version, | |
| 401 | ); | |
| 402 | errdefer dylib_cmd.deinit(self.allocator); | |
| 403 | ||
| 404 | try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); | |
| 359 | fn parseLibs(self: *Zld, libs: []const []const u8) !void { | |
| 360 | for (libs) |lib| { | |
| 361 | const file = try fs.cwd().openFile(lib, .{}); | |
| 362 | ||
| 363 | if (try Dylib.isDylib(file)) { | |
| 364 | const dylib = try self.allocator.create(Dylib); | |
| 365 | errdefer self.allocator.destroy(dylib); | |
| 366 | ||
| 367 | dylib.* = Dylib.init(self.allocator); | |
| 368 | dylib.arch = self.arch.?; | |
| 369 | dylib.name = try self.allocator.dupe(u8, lib); | |
| 370 | dylib.file = file; | |
| 371 | ||
| 372 | const ordinal = @intCast(u16, self.dylibs.items.len); | |
| 373 | dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem | |
| 374 | ||
| 375 | // TODO Defer parsing of the dylibs until they are actually needed | |
| 376 | try dylib.parse(); | |
| 377 | try self.dylibs.append(self.allocator, dylib); | |
| 378 | ||
| 379 | // Add LC_LOAD_DYLIB command | |
| 380 | const dylib_id = dylib.id orelse unreachable; | |
| 381 | var dylib_cmd = try createLoadDylibCommand( | |
| 382 | self.allocator, | |
| 383 | dylib_id.name, | |
| 384 | dylib_id.timestamp, | |
| 385 | dylib_id.current_version, | |
| 386 | dylib_id.compatibility_version, | |
| 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 | 14 | cases.addBuildFile("test/standalone/global_linkage/build.zig"); |
| 15 | 15 | cases.addBuildFile("test/standalone/static_c_lib/build.zig"); |
| 16 | 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 | 18 | cases.addBuildFile("test/standalone/issue_339/build.zig"); |
| 18 | 19 | cases.addBuildFile("test/standalone/issue_794/build.zig"); |
| 19 | 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 | } |