frontend: move framework path resolution from the linker to frontend

This commit is contained in:
Jakub Konka 2023-08-20 10:43:20 +02:00
parent e687c87d69
commit 4793dafa04
5 changed files with 122 additions and 63 deletions

View File

@ -507,7 +507,8 @@ pub const InitOptions = struct {
c_source_files: []const CSourceFile = &[0]CSourceFile{},
link_objects: []LinkObject = &[0]LinkObject{},
framework_dirs: []const []const u8 = &[0][]const u8{},
frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{},
framework_names: []const []const u8 = &.{},
framework_infos: []const Framework = &.{},
system_lib_names: []const []const u8 = &.{},
system_lib_infos: []const SystemLib = &.{},
/// These correspond to the WASI libc emulated subcomponents including:
@ -830,7 +831,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
// Our linker can't handle objects or most advanced options yet.
if (options.link_objects.len != 0 or
options.c_source_files.len != 0 or
options.frameworks.count() != 0 or
options.framework_names.len != 0 or
options.system_lib_names.len != 0 or
options.link_libc or options.link_libcpp or
link_eh_frame_hdr or
@ -1446,6 +1447,13 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
system_libs.putAssumeCapacity(lib_name, options.system_lib_infos[i]);
}
var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
errdefer frameworks.deinit(gpa);
try frameworks.ensureTotalCapacity(gpa, options.framework_names.len);
for (options.framework_names, options.framework_infos) |framework_name, info| {
frameworks.putAssumeCapacity(framework_name, info);
}
const bin_file = try link.File.openPath(gpa, .{
.emit = bin_file_emit,
.implib_emit = implib_emit,
@ -1465,7 +1473,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
.link_libcpp = link_libcpp,
.link_libunwind = link_libunwind,
.objects = options.link_objects,
.frameworks = options.frameworks,
.frameworks = frameworks,
.framework_dirs = options.framework_dirs,
.system_libs = system_libs,
.wasi_emulated_libs = options.wasi_emulated_libs,

View File

@ -37,6 +37,7 @@ pub const SystemLib = struct {
pub const Framework = struct {
needed: bool = false,
weak: bool = false,
path: []const u8,
};
pub const SortSection = enum { name, alignment };

View File

@ -870,7 +870,7 @@ fn resolveLibSystemInDirs(arena: Allocator, dirs: []const []const u8, out_libs:
return false;
}
pub fn resolveLib(
fn resolveLib(
arena: Allocator,
search_dir: []const u8,
name: []const u8,
@ -889,26 +889,6 @@ pub fn resolveLib(
return full_path;
}
pub fn resolveFramework(
arena: Allocator,
search_dir: []const u8,
name: []const u8,
ext: []const u8,
) !?[]const u8 {
const search_name = try std.fmt.allocPrint(arena, "{s}{s}", .{ name, ext });
const prefix_path = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, prefix_path, search_name });
// Check if the file exists.
const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
error.FileNotFound => return null,
else => |e| return e,
};
defer tmp.close();
return full_path;
}
const ParseDylibError = error{
OutOfMemory,
EmptyStubFile,

View File

@ -3512,9 +3512,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
try zld.atoms.append(gpa, Atom.empty); // AtomIndex at 0 is reserved as null atom
try zld.strtab.buffer.append(gpa, 0);
var lib_not_found = false;
var framework_not_found = false;
// Positional arguments to the linker such as object files and static archives.
var positionals = std.ArrayList([]const u8).init(arena);
try positionals.ensureUnusedCapacity(options.objects.len);
@ -3557,35 +3554,18 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
for (vals) |v| libs.putAssumeCapacity(v.path.?, v);
}
{
const vals = options.frameworks.values();
try libs.ensureUnusedCapacity(vals.len);
for (vals) |v| libs.putAssumeCapacity(v.path, .{
.needed = v.needed,
.weak = v.weak,
.path = v.path,
});
}
try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs);
// frameworks
outer: for (options.frameworks.keys()) |f_name| {
for (options.framework_dirs) |dir| {
for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
if (try MachO.resolveFramework(arena, dir, f_name, ext)) |full_path| {
const info = options.frameworks.get(f_name).?;
try libs.put(full_path, .{
.needed = info.needed,
.weak = info.weak,
.path = full_path,
});
continue :outer;
}
}
} else {
log.warn("framework not found for '-framework {s}'", .{f_name});
framework_not_found = true;
}
}
if (framework_not_found) {
log.warn("Framework search paths:", .{});
for (options.framework_dirs) |dir| {
log.warn(" {s}", .{dir});
}
}
if (options.verbose_link) {
var argv = std.ArrayList([]const u8).init(arena);
@ -3731,12 +3711,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
if (resolver.unresolved.count() > 0) {
return error.UndefinedSymbolReference;
}
if (lib_not_found) {
return error.LibraryNotFound;
}
if (framework_not_found) {
return error.FrameworkNotFound;
}
if (options.output_mode == .Exe) {
const entry_name = options.entry orelse load_commands.default_entry_point;

View File

@ -746,6 +746,13 @@ const SystemLib = struct {
}
};
/// Similar to `link.Framework` except it doesn't store yet unresolved
/// path to the framework.
const Framework = struct {
needed: bool = false,
weak: bool = false,
};
const CliModule = struct {
mod: *Package,
/// still in CLI arg format
@ -919,7 +926,7 @@ fn buildOutputType(
var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena);
var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
var framework_dirs = std.ArrayList([]const u8).init(arena);
var frameworks: std.StringArrayHashMapUnmanaged(Compilation.Framework) = .{};
var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
// null means replace with the test executable binary
var test_exec_args = std.ArrayList(?[]const u8).init(arena);
var linker_export_symbol_names = std.ArrayList([]const u8).init(arena);
@ -2566,7 +2573,7 @@ fn buildOutputType(
want_native_include_dirs = true;
}
// Resolve the library and framework path arguments with respect to sysroot.
// Resolve the library path arguments with respect to sysroot.
var lib_dirs = std.ArrayList([]const u8).init(arena);
if (sysroot) |root| {
for (lib_dir_args.items) |dir| {
@ -2868,6 +2875,65 @@ fn buildOutputType(
}
// After this point, resolved_system_libs is used instead of external_system_libs.
// We now repeat part of the process for frameworks.
var resolved_frameworks: std.MultiArrayList(struct {
name: []const u8,
framework: Compilation.Framework,
}) = .{};
if (frameworks.keys().len > 0) {
var test_path = std.ArrayList(u8).init(gpa);
defer test_path.deinit();
var checked_paths = std.ArrayList(u8).init(gpa);
defer checked_paths.deinit();
var failed_frameworks = std.ArrayList(struct {
name: []const u8,
checked_paths: []const u8,
}).init(arena);
framework: for (frameworks.keys(), frameworks.values()) |framework_name, info| {
checked_paths.clearRetainingCapacity();
for (framework_dirs.items) |framework_dir_path| {
if (try accessFrameworkPath(
&test_path,
&checked_paths,
framework_dir_path,
framework_name,
)) {
const path = try arena.dupe(u8, test_path.items);
try resolved_frameworks.append(arena, .{
.name = framework_name,
.framework = .{
.needed = info.needed,
.weak = info.weak,
.path = path,
},
});
continue :framework;
}
}
try failed_frameworks.append(.{
.name = framework_name,
.checked_paths = try arena.dupe(u8, checked_paths.items),
});
}
if (failed_frameworks.items.len > 0) {
for (failed_frameworks.items) |f| {
const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;
std.log.err("unable to find framework '{s}'. searched paths: {s}", .{
f.name, searched_paths,
});
}
process.exit(1);
}
}
// After this point, resolved_frameworks is used instead of frameworks.
const object_format = target_info.target.ofmt;
if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) {
@ -3261,7 +3327,8 @@ fn buildOutputType(
.c_source_files = c_source_files.items,
.link_objects = link_objects.items,
.framework_dirs = framework_dirs.items,
.frameworks = frameworks,
.framework_names = resolved_frameworks.items(.name),
.framework_infos = resolved_frameworks.items(.framework),
.system_lib_names = resolved_system_libs.items(.name),
.system_lib_infos = resolved_system_libs.items(.lib),
.wasi_emulated_libs = wasi_emulated_libs.items,
@ -6336,3 +6403,32 @@ fn accessLibPath(
return false;
}
fn accessFrameworkPath(
test_path: *std.ArrayList(u8),
checked_paths: *std.ArrayList(u8),
framework_dir_path: []const u8,
framework_name: []const u8,
) !bool {
const sep = fs.path.sep_str;
for (&[_][]const u8{ "tbd", "dylib" }) |ext| {
test_path.clearRetainingCapacity();
try test_path.writer().print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}.{s}", .{
framework_dir_path,
framework_name,
framework_name,
ext,
});
try checked_paths.writer().print("\n {s}", .{test_path.items});
fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
error.FileNotFound => continue,
else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{
ext, test_path.items, @errorName(e),
}),
};
return true;
}
return false;
}