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.
This commit is contained in:
Jakub Konka 2021-05-22 13:08:00 +02:00
parent f4101c1153
commit 2b0d322ea0
10 changed files with 133 additions and 73 deletions

View File

@ -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 {
}
}
// TODO text-based API, or .tbd files.
const exts = &[_][]const u8{ "dylib", "a" };
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});
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 });
try shared_libs.append(full_path);
found = true;
break;
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();
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,
});

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
fn parseLibs(self: *Zld, libs: []const []const u8) !void {
for (libs) |lib| {
const file = try fs.cwd().openFile(lib, .{});
dylib.* = Dylib.init(self.allocator);
dylib.arch = self.arch.?;
dylib.name = try self.allocator.dupe(u8, lib);
dylib.file = try fs.cwd().openFile(lib, .{});
if (try Dylib.isDylib(file)) {
const dylib = try self.allocator.create(Dylib);
errdefer self.allocator.destroy(dylib);
const ordinal = @intCast(u16, self.dylibs.items.len);
dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
dylib.* = Dylib.init(self.allocator);
dylib.arch = self.arch.?;
dylib.name = try self.allocator.dupe(u8, lib);
dylib.file = file;
// TODO Defer parsing of the dylibs until they are actually needed
try dylib.parse();
try self.dylibs.append(self.allocator, dylib);
const ordinal = @intCast(u16, self.dylibs.items.len);
dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
// 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);
// TODO Defer parsing of the dylibs until they are actually needed
try dylib.parse();
try self.dylibs.append(self.allocator, dylib);
try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
// 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});
}
}
}

View File

@ -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");

View File

@ -0,0 +1,4 @@
#include "a.h"
int32_t add(int32_t a, int32_t b) {
return a + b;
}

View File

@ -0,0 +1,2 @@
#include <stdint.h>
int32_t add(int32_t a, int32_t b);

View File

@ -0,0 +1,23 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const lib_a = b.addStaticLibrary("a", null);
lib_a.addCSourceFile("a.c", &[_][]const u8{});
lib_a.setBuildMode(mode);
lib_a.addIncludeDir(".");
lib_a.install();
const test_exe = b.addTest("main.zig");
test_exe.setBuildMode(mode);
test_exe.linkSystemLibrary("a"); // force linking liba.a as -la
test_exe.addSystemIncludeDir(".");
const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch unreachable;
test_exe.addLibPath(search_path);
const test_step = b.step("test", "Test it");
test_step.dependOn(b.getInstallStep());
test_step.dependOn(&test_exe.step);
}

View File

@ -0,0 +1,8 @@
const std = @import("std");
const expect = std.testing.expect;
const c = @cImport(@cInclude("a.h"));
test "import C add" {
const result = c.add(2, 1);
try expect(result == 3);
}