diff --git a/build.zig b/build.zig index ab3dcfc1b45d..cb62d64da2f7 100644 --- a/build.zig +++ b/build.zig @@ -204,10 +204,9 @@ pub fn build(b: *std.Build) !void { ); if (!no_bin) { - const install_exe = b.addInstallArtifact(exe, .{}); - if (flat) { - install_exe.dest_dir = .prefix; - } + const install_exe = b.addInstallArtifact(exe, .{ + .dest_dir = if (flat) .{ .override = .prefix } else .default, + }); b.getInstallStep().dependOn(&install_exe.step); } diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index c5dbb6f29a06..6369777e884e 100644 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -72,7 +72,7 @@ stage3-debug/bin/zig build test docs \ # Look for HTML errors. # TODO: move this to a build.zig flag (-Denable-tidy) -tidy --drop-empty-elements no -qe "zig-out/doc/langref.html" +tidy --drop-empty-elements no -qe "../zig-out/doc/langref.html" # Ensure that updating the wasm binary from this commit will result in a viable build. stage3-debug/bin/zig build update-zig1 diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index cdfc5eda452a..369c14dc5f8d 100644 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -72,7 +72,7 @@ stage3-release/bin/zig build test docs \ # Look for HTML errors. # TODO: move this to a build.zig flag (-Denable-tidy) -tidy --drop-empty-elements no -qe "zig-out/doc/langref.html" +tidy --drop-empty-elements no -qe "../zig-out/doc/langref.html" # Ensure that updating the wasm binary from this commit will result in a viable build. stage3-release/bin/zig build update-zig1 diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index 422cb675d188..2efca8f7746c 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -72,7 +72,7 @@ stage3-debug/bin/zig build test docs \ # Look for HTML errors. # TODO: move this to a build.zig flag (-Denable-tidy) -tidy --drop-empty-elements no -qe "zig-out/doc/langref.html" +tidy --drop-empty-elements no -qe "../zig-out/doc/langref.html" # Ensure that updating the wasm binary from this commit will result in a viable build. stage3-debug/bin/zig build update-zig1 diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index da2d795c7659..4dc5c379270d 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -73,7 +73,7 @@ stage3-release/bin/zig build test docs \ # Look for HTML errors. # TODO: move this to a build.zig flag (-Denable-tidy) -tidy --drop-empty-elements no -qe "zig-out/doc/langref.html" +tidy --drop-empty-elements no -qe "../zig-out/doc/langref.html" # Ensure that stage3 and stage4 are byte-for-byte identical. stage3-release/bin/zig build \ diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 89cc2ecfd9c1..335a7e2df774 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -149,13 +149,6 @@ entitlements: ?[]const u8 = null, /// (Darwin) Size of the pagezero segment. pagezero_size: ?u64 = null, -/// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. -/// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` -/// option. -/// By default, if no option is specified, the linker assumes `paths_first` as the default -/// search strategy. -search_strategy: ?enum { paths_first, dylibs_first } = null, - /// (Darwin) Set size of the padding between the end of load commands /// and start of `__TEXT,__text` section. headerpad_size: ?u32 = null, @@ -242,7 +235,11 @@ pub const SystemLib = struct { name: []const u8, needed: bool, weak: bool, - use_pkg_config: enum { + use_pkg_config: UsePkgConfig, + preferred_link_mode: std.builtin.LinkMode, + search_strategy: SystemLib.SearchStrategy, + + pub const UsePkgConfig = enum { /// Don't use pkg-config, just pass -lfoo where foo is name. no, /// Try to get information on how to link the library from pkg-config. @@ -251,7 +248,9 @@ pub const SystemLib = struct { /// Try to get information on how to link the library from pkg-config. /// If that fails, error out. force, - }, + }; + + pub const SearchStrategy = enum { paths_first, mode_first, no_fallback }; }; const FrameworkLinkInfo = struct { @@ -718,74 +717,29 @@ pub fn defineCMacroRaw(self: *Compile, name_and_value: []const u8) void { self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); } -/// This one has no integration with anything, it just puts -lname on the command line. -/// Prefer to use `linkSystemLibrary` instead. +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryName(self: *Compile, name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = false, - .weak = false, - .use_pkg_config = .no, - }, - }) catch @panic("OOM"); + return linkSystemLibrary2(self, name, .{ .use_pkg_config = .no }); } -/// This one has no integration with anything, it just puts -needed-lname on the command line. -/// Prefer to use `linkSystemLibraryNeeded` instead. +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryNeededName(self: *Compile, name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = true, - .weak = false, - .use_pkg_config = .no, - }, - }) catch @panic("OOM"); + return linkSystemLibrary2(self, name, .{ .needed = true, .use_pkg_config = .no }); } -/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the -/// command line. Prefer to use `linkSystemLibraryWeak` instead. +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryWeakName(self: *Compile, name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = false, - .weak = true, - .use_pkg_config = .no, - }, - }) catch @panic("OOM"); + return linkSystemLibrary2(self, name, .{ .weak = true, .use_pkg_config = .no }); } -/// This links against a system library, exclusively using pkg-config to find the library. -/// Prefer to use `linkSystemLibrary` instead. +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryPkgConfigOnly(self: *Compile, lib_name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(lib_name), - .needed = false, - .weak = false, - .use_pkg_config = .force, - }, - }) catch @panic("OOM"); + return linkSystemLibrary2(self, lib_name, .{ .use_pkg_config = .force }); } -/// This links against a system library, exclusively using pkg-config to find the library. -/// Prefer to use `linkSystemLibraryNeeded` instead. +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryNeededPkgConfigOnly(self: *Compile, lib_name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(lib_name), - .needed = true, - .weak = false, - .use_pkg_config = .force, - }, - }) catch @panic("OOM"); + return linkSystemLibrary2(self, lib_name, .{ .needed = true, .use_pkg_config = .force }); } /// Run pkg-config for the given library name and parse the output, returning the arguments @@ -885,21 +839,32 @@ fn runPkgConfig(self: *Compile, lib_name: []const u8) ![]const []const u8 { } pub fn linkSystemLibrary(self: *Compile, name: []const u8) void { - self.linkSystemLibraryInner(name, .{}); + self.linkSystemLibrary2(name, .{}); } +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryNeeded(self: *Compile, name: []const u8) void { - self.linkSystemLibraryInner(name, .{ .needed = true }); + return linkSystemLibrary2(self, name, .{ .needed = true }); } +/// deprecated: use linkSystemLibrary2 pub fn linkSystemLibraryWeak(self: *Compile, name: []const u8) void { - self.linkSystemLibraryInner(name, .{ .weak = true }); + return linkSystemLibrary2(self, name, .{ .weak = true }); } -fn linkSystemLibraryInner(self: *Compile, name: []const u8, opts: struct { +pub const LinkSystemLibraryOptions = struct { needed: bool = false, weak: bool = false, -}) void { + use_pkg_config: SystemLib.UsePkgConfig = .yes, + preferred_link_mode: std.builtin.LinkMode = .Dynamic, + search_strategy: SystemLib.SearchStrategy = .paths_first, +}; + +pub fn linkSystemLibrary2( + self: *Compile, + name: []const u8, + options: LinkSystemLibraryOptions, +) void { const b = self.step.owner; if (isLibCLibrary(name)) { self.linkLibC(); @@ -913,9 +878,11 @@ fn linkSystemLibraryInner(self: *Compile, name: []const u8, opts: struct { self.link_objects.append(.{ .system_lib = .{ .name = b.dupe(name), - .needed = opts.needed, - .weak = opts.weak, - .use_pkg_config = .yes, + .needed = options.needed, + .weak = options.weak, + .use_pkg_config = options.use_pkg_config, + .preferred_link_mode = options.preferred_link_mode, + .search_strategy = options.search_strategy, }, }) catch @panic("OOM"); } @@ -1385,6 +1352,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try transitive_deps.add(self.link_objects.items); var prev_has_cflags = false; + var prev_search_strategy: SystemLib.SearchStrategy = .paths_first; + var prev_preferred_link_mode: std.builtin.LinkMode = .Dynamic; for (transitive_deps.link_objects.items) |link_object| { switch (link_object) { @@ -1420,6 +1389,28 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }, .system_lib => |system_lib| { + if ((system_lib.search_strategy != prev_search_strategy or + system_lib.preferred_link_mode != prev_preferred_link_mode) and + self.linkage != .static) + { + switch (system_lib.search_strategy) { + .no_fallback => switch (system_lib.preferred_link_mode) { + .Dynamic => try zig_args.append("-search_dylibs_only"), + .Static => try zig_args.append("-search_static_only"), + }, + .paths_first => switch (system_lib.preferred_link_mode) { + .Dynamic => try zig_args.append("-search_paths_first"), + .Static => try zig_args.append("-search_paths_first_static"), + }, + .mode_first => switch (system_lib.preferred_link_mode) { + .Dynamic => try zig_args.append("-search_dylibs_first"), + .Static => try zig_args.append("-search_static_first"), + }, + } + prev_search_strategy = system_lib.search_strategy; + prev_preferred_link_mode = system_lib.preferred_link_mode; + } + const prefix: []const u8 = prefix: { if (system_lib.needed) break :prefix "-needed-l"; if (system_lib.weak) break :prefix "-weak-l"; @@ -1662,10 +1653,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size}); try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); } - if (self.search_strategy) |strat| switch (strat) { - .paths_first => try zig_args.append("-search_paths_first"), - .dylibs_first => try zig_args.append("-search_dylibs_first"), - }; if (self.headerpad_size) |headerpad_size| { const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size}); try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index f9798695ad5f..4c8f1286b872 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -1,6 +1,5 @@ const std = @import("../../std.zig"); const builtin = @import("builtin"); -const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; const process = std.process; const mem = std.mem; @@ -8,28 +7,18 @@ const mem = std.mem; const NativePaths = @This(); const NativeTargetInfo = std.zig.system.NativeTargetInfo; -include_dirs: ArrayList([:0]u8), -lib_dirs: ArrayList([:0]u8), -framework_dirs: ArrayList([:0]u8), -rpaths: ArrayList([:0]u8), -warnings: ArrayList([:0]u8), +arena: Allocator, +include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +lib_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +framework_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +rpaths: std.ArrayListUnmanaged([]const u8) = .{}, +warnings: std.ArrayListUnmanaged([]const u8) = .{}, -pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths { +pub fn detect(arena: Allocator, native_info: NativeTargetInfo) !NativePaths { const native_target = native_info.target; - - var self: NativePaths = .{ - .include_dirs = ArrayList([:0]u8).init(allocator), - .lib_dirs = ArrayList([:0]u8).init(allocator), - .framework_dirs = ArrayList([:0]u8).init(allocator), - .rpaths = ArrayList([:0]u8).init(allocator), - .warnings = ArrayList([:0]u8).init(allocator), - }; - errdefer self.deinit(); - + var self: NativePaths = .{ .arena = arena }; var is_nix = false; - if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { - defer allocator.free(nix_cflags_compile); - + if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_cflags_compile, ' '); while (true) { @@ -58,9 +47,7 @@ pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths error.EnvironmentVariableNotFound => {}, error.OutOfMemory => |e| return e, } - if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| { - defer allocator.free(nix_ldflags); - + if (process.getEnvVarOwned(arena, "NIX_LDFLAGS")) |nix_ldflags| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_ldflags, ' '); while (true) { @@ -89,17 +76,16 @@ pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths return self; } + // TODO: consider also adding homebrew paths + // TODO: consider also adding macports paths if (comptime builtin.target.isDarwin()) { - try self.addIncludeDir("/usr/include"); - try self.addLibDir("/usr/lib"); - try self.addFrameworkDir("/System/Library/Frameworks"); - - if (builtin.target.os.version_range.semver.min.major < 11) { - try self.addIncludeDir("/usr/local/include"); - try self.addLibDir("/usr/local/lib"); - try self.addFrameworkDir("/Library/Frameworks"); + if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: { + const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk; + try self.addLibDir(try std.fs.path.join(arena, &.{ sdk.path, "usr/lib" })); + try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk.path, "System/Library/Frameworks" })); + try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk.path, "usr/include" })); + return self; } - return self; } @@ -115,8 +101,7 @@ pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths } if (builtin.os.tag != .windows) { - const triple = try native_target.linuxTriple(allocator); - defer allocator.free(triple); + const triple = try native_target.linuxTriple(arena); const qual = native_target.ptrBitWidth(); @@ -172,69 +157,42 @@ pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths return self; } -pub fn deinit(self: *NativePaths) void { - deinitArray(&self.include_dirs); - deinitArray(&self.lib_dirs); - deinitArray(&self.framework_dirs); - deinitArray(&self.rpaths); - deinitArray(&self.warnings); - self.* = undefined; -} - -fn deinitArray(array: *ArrayList([:0]u8)) void { - for (array.items) |item| { - array.allocator.free(item); - } - array.deinit(); -} - pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void { - return self.appendArray(&self.include_dirs, s); + return self.include_dirs.append(self.arena, s); } pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { - const item = try std.fmt.allocPrintZ(self.include_dirs.allocator, fmt, args); - errdefer self.include_dirs.allocator.free(item); - try self.include_dirs.append(item); + const item = try std.fmt.allocPrint(self.arena, fmt, args); + try self.include_dirs.append(self.arena, item); } pub fn addLibDir(self: *NativePaths, s: []const u8) !void { - return self.appendArray(&self.lib_dirs, s); + try self.lib_dirs.append(self.arena, s); } pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { - const item = try std.fmt.allocPrintZ(self.lib_dirs.allocator, fmt, args); - errdefer self.lib_dirs.allocator.free(item); - try self.lib_dirs.append(item); + const item = try std.fmt.allocPrint(self.arena, fmt, args); + try self.lib_dirs.append(self.arena, item); } pub fn addWarning(self: *NativePaths, s: []const u8) !void { - return self.appendArray(&self.warnings, s); + return self.warnings.append(self.arena, s); } pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void { - return self.appendArray(&self.framework_dirs, s); + return self.framework_dirs.append(self.arena, s); } pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { - const item = try std.fmt.allocPrintZ(self.framework_dirs.allocator, fmt, args); - errdefer self.framework_dirs.allocator.free(item); - try self.framework_dirs.append(item); + const item = try std.fmt.allocPrint(self.arena, fmt, args); + try self.framework_dirs.append(self.arena, item); } pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { - const item = try std.fmt.allocPrintZ(self.warnings.allocator, fmt, args); - errdefer self.warnings.allocator.free(item); - try self.warnings.append(item); + const item = try std.fmt.allocPrint(self.arena, fmt, args); + try self.warnings.append(self.arena, item); } pub fn addRPath(self: *NativePaths, s: []const u8) !void { - return self.appendArray(&self.rpaths, s); -} - -fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void { - _ = self; - const item = try array.allocator.dupeZ(u8, s); - errdefer array.allocator.free(item); - try array.append(item); + try self.rpaths.append(self.arena, s); } diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index e29dc68d64dd..992c0d814d79 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -8,28 +8,34 @@ pub const macos = @import("darwin/macos.zig"); /// Check if SDK is installed on Darwin without triggering CLT installation popup window. /// Note: simply invoking `xcrun` will inevitably trigger the CLT installation popup. -/// Therefore, we resort to the same tool used by Homebrew, namely, invoking `xcode-select --print-path` -/// and checking if the status is nonzero or the returned string in nonempty. -/// https://github.com/Homebrew/brew/blob/e119bdc571dcb000305411bc1e26678b132afb98/Library/Homebrew/brew.sh#L630 -pub fn isDarwinSDKInstalled(allocator: Allocator) bool { - const argv = &[_][]const u8{ "/usr/bin/xcode-select", "--print-path" }; - const result = std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv }) catch return false; +/// Therefore, we resort to invoking `xcode-select --print-path` and checking +/// if the status is nonzero. +/// stderr from xcode-select is ignored. +/// If error.OutOfMemory occurs in Allocator, this function returns null. +pub fn isSdkInstalled(allocator: Allocator) bool { + const result = std.process.Child.exec(.{ + .allocator = allocator, + .argv = &.{ "/usr/bin/xcode-select", "--print-path" }, + }) catch return false; + defer { allocator.free(result.stderr); allocator.free(result.stdout); } - if (result.stderr.len != 0 or result.term.Exited != 0) { - // We don't actually care if there were errors as this is best-effort check anyhow. - return false; - } - return result.stdout.len > 0; + + return switch (result.term) { + .Exited => |code| if (code == 0) result.stdout.len > 0 else false, + else => false, + }; } /// Detect SDK on Darwin. /// Calls `xcrun --sdk --show-sdk-path` which fetches the path to the SDK sysroot (if any). /// Subsequently calls `xcrun --sdk --show-sdk-version` which fetches version of the SDK. /// The caller needs to deinit the resulting struct. -pub fn getDarwinSDK(allocator: Allocator, target: Target) ?DarwinSDK { +/// stderr from xcrun is ignored. +/// If error.OutOfMemory occurs in Allocator, this function returns null. +pub fn getSdk(allocator: Allocator, target: Target) ?Sdk { const is_simulator_abi = target.abi == .simulator; const sdk = switch (target.os.tag) { .macos => "macosx", @@ -40,30 +46,28 @@ pub fn getDarwinSDK(allocator: Allocator, target: Target) ?DarwinSDK { }; const path = path: { const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-path" }; - const result = std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv }) catch return null; + const result = std.process.Child.exec(.{ .allocator = allocator, .argv = argv }) catch return null; defer { allocator.free(result.stderr); allocator.free(result.stdout); } - if (result.stderr.len != 0 or result.term.Exited != 0) { - // We don't actually care if there were errors as this is best-effort check anyhow - // and in the worst case the user can specify the sysroot manually. - return null; + switch (result.term) { + .Exited => |code| if (code != 0) return null, + else => return null, } const path = allocator.dupe(u8, mem.trimRight(u8, result.stdout, "\r\n")) catch return null; break :path path; }; const version = version: { const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-version" }; - const result = std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv }) catch return null; + const result = std.process.Child.exec(.{ .allocator = allocator, .argv = argv }) catch return null; defer { allocator.free(result.stderr); allocator.free(result.stdout); } - if (result.stderr.len != 0 or result.term.Exited != 0) { - // We don't actually care if there were errors as this is best-effort check anyhow - // and in the worst case the user can specify the sysroot manually. - return null; + switch (result.term) { + .Exited => |code| if (code != 0) return null, + else => return null, } const raw_version = mem.trimRight(u8, result.stdout, "\r\n"); const version = parseSdkVersion(raw_version) orelse Version{ @@ -73,7 +77,7 @@ pub fn getDarwinSDK(allocator: Allocator, target: Target) ?DarwinSDK { }; break :version version; }; - return DarwinSDK{ + return Sdk{ .path = path, .version = version, }; @@ -96,11 +100,11 @@ fn parseSdkVersion(raw: []const u8) ?Version { return Version.parse(buffer[0..len]) catch null; } -pub const DarwinSDK = struct { +pub const Sdk = struct { path: []const u8, version: Version, - pub fn deinit(self: DarwinSDK, allocator: Allocator) void { + pub fn deinit(self: Sdk, allocator: Allocator) void { allocator.free(self.path); } }; diff --git a/src/Compilation.zig b/src/Compilation.zig index 2e269a7e80df..a08c3e09f46c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -124,6 +124,7 @@ zig_lib_directory: Directory, local_cache_directory: Directory, global_cache_directory: Directory, libc_include_dir_list: []const []const u8, +libc_framework_dir_list: []const []const u8, thread_pool: *ThreadPool, /// Populated when we build the libc++ static library. A Job to build this is placed in the queue @@ -448,6 +449,7 @@ pub const ClangPreprocessorMode = enum { stdout, }; +pub const Framework = link.Framework; pub const SystemLib = link.SystemLib; pub const CacheMode = link.CacheMode; @@ -505,7 +507,7 @@ 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(SystemLib) = .{}, + frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{}, system_lib_names: []const []const u8 = &.{}, system_lib_infos: []const SystemLib = &.{}, /// These correspond to the WASI libc emulated subcomponents including: @@ -636,16 +638,12 @@ pub const InitOptions = struct { wasi_exec_model: ?std.builtin.WasiExecModel = null, /// (Zig compiler development) Enable dumping linker's state as JSON. enable_link_snapshots: bool = false, - /// (Darwin) Path and version of the native SDK if detected. - native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null, /// (Darwin) Install name of the dylib install_name: ?[]const u8 = null, /// (Darwin) Path to entitlements file entitlements: ?[]const u8 = null, /// (Darwin) size of the __PAGEZERO segment pagezero_size: ?u64 = null, - /// (Darwin) search strategy for system libraries - search_strategy: ?link.File.MachO.SearchStrategy = null, /// (Darwin) set minimum space for future expansion of the load commands headerpad_size: ?u32 = null, /// (Darwin) set enough space as if all paths were MATPATHLEN @@ -855,16 +853,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { break :blk false; }; - const sysroot = blk: { - if (options.sysroot) |sysroot| { - break :blk sysroot; - } else if (options.native_darwin_sdk) |sdk| { - break :blk sdk.path; - } else { - break :blk null; - } - }; - const lto = blk: { if (options.want_lto) |explicit| { if (!use_lld and !options.target.isDarwin()) @@ -948,9 +936,10 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { options.is_native_abi, link_libc, options.libc_installation, - options.native_darwin_sdk != null, ); + const sysroot = options.sysroot orelse libc_dirs.sysroot; + const must_pie = target_util.requiresPIE(options.target); const pie: bool = if (options.want_pie) |explicit| pie: { if (!explicit and must_pie) { @@ -1563,11 +1552,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .wasi_exec_model = wasi_exec_model, .hash_style = options.hash_style, .enable_link_snapshots = options.enable_link_snapshots, - .native_darwin_sdk = options.native_darwin_sdk, .install_name = options.install_name, .entitlements = options.entitlements, .pagezero_size = options.pagezero_size, - .search_strategy = options.search_strategy, .headerpad_size = options.headerpad_size, .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, @@ -1601,6 +1588,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .cache_parent = cache, .self_exe_path = options.self_exe_path, .libc_include_dir_list = libc_dirs.libc_include_dir_list, + .libc_framework_dir_list = libc_dirs.libc_framework_dir_list, .sanitize_c = sanitize_c, .thread_pool = options.thread_pool, .clang_passthrough_mode = options.clang_passthrough_mode, @@ -1727,15 +1715,18 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // When linking mingw-w64 there are some import libs we always need. for (mingw.always_link_libs) |name| { - try comp.bin_file.options.system_libs.put(comp.gpa, name, .{}); + try comp.bin_file.options.system_libs.put(comp.gpa, name, .{ + .needed = false, + .weak = false, + .path = null, + }); } } // Generate Windows import libs. if (target.os.tag == .windows) { const count = comp.bin_file.options.system_libs.count(); try comp.work_queue.ensureUnusedCapacity(count); - var i: usize = 0; - while (i < count) : (i += 1) { + for (0..count) |i| { comp.work_queue.writeItemAssumeCapacity(.{ .windows_import_lib = i }); } } @@ -2367,17 +2358,17 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes if (comp.bin_file.options.link_libc) { man.hash.add(comp.bin_file.options.libc_installation != null); if (comp.bin_file.options.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); + man.hash.addOptionalBytes(libc_installation.crt_dir); if (target.abi == .msvc) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + man.hash.addOptionalBytes(libc_installation.msvc_lib_dir); + man.hash.addOptionalBytes(libc_installation.kernel32_lib_dir); } } man.hash.addOptionalBytes(comp.bin_file.options.dynamic_linker); } man.hash.addOptionalBytes(comp.bin_file.options.soname); man.hash.addOptional(comp.bin_file.options.version); - link.hashAddSystemLibs(&man.hash, comp.bin_file.options.system_libs); + try link.hashAddSystemLibs(man, comp.bin_file.options.system_libs); man.hash.addListOfBytes(comp.bin_file.options.force_undefined_symbols.keys()); man.hash.addOptional(comp.bin_file.options.allow_shlib_undefined); man.hash.add(comp.bin_file.options.bind_global_refs_locally); @@ -2395,10 +2386,9 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes // Mach-O specific stuff man.hash.addListOfBytes(comp.bin_file.options.framework_dirs); - link.hashAddSystemLibs(&man.hash, comp.bin_file.options.frameworks); + link.hashAddFrameworks(&man.hash, comp.bin_file.options.frameworks); try man.addOptionalFile(comp.bin_file.options.entitlements); man.hash.addOptional(comp.bin_file.options.pagezero_size); - man.hash.addOptional(comp.bin_file.options.search_strategy); man.hash.addOptional(comp.bin_file.options.headerpad_size); man.hash.add(comp.bin_file.options.headerpad_max_install_names); man.hash.add(comp.bin_file.options.dead_strip_dylibs); @@ -4341,6 +4331,14 @@ pub fn addCCArgs( try argv.append("-ObjC++"); } + for (comp.libc_framework_dir_list) |framework_dir| { + try argv.appendSlice(&.{ "-iframework", framework_dir }); + } + + for (comp.bin_file.options.framework_dirs) |framework_dir| { + try argv.appendSlice(&.{ "-F", framework_dir }); + } + // According to Rich Felker libc headers are supposed to go before C language headers. // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics // and other compiler specific items. @@ -4823,6 +4821,8 @@ test "classifyFileExt" { const LibCDirs = struct { libc_include_dir_list: []const []const u8, libc_installation: ?*const LibCInstallation, + libc_framework_dir_list: []const []const u8, + sysroot: ?[]const u8, }; fn getZigShippedLibCIncludeDirsDarwin(arena: Allocator, zig_lib_dir: []const u8, target: Target) !LibCDirs { @@ -4853,6 +4853,8 @@ fn getZigShippedLibCIncludeDirsDarwin(arena: Allocator, zig_lib_dir: []const u8, return LibCDirs{ .libc_include_dir_list = list, .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, }; } @@ -4863,12 +4865,13 @@ fn detectLibCIncludeDirs( is_native_abi: bool, link_libc: bool, libc_installation: ?*const LibCInstallation, - has_macos_sdk: bool, ) !LibCDirs { if (!link_libc) { return LibCDirs{ .libc_include_dir_list = &[0][]u8{}, .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, }; } @@ -4879,28 +4882,19 @@ fn detectLibCIncludeDirs( // If linking system libraries and targeting the native abi, default to // using the system libc installation. if (is_native_abi and !target.isMinGW()) { - if (target.isDarwin()) { - return if (has_macos_sdk) - // For Darwin/macOS, we are all set with getDarwinSDK found earlier. - LibCDirs{ - .libc_include_dir_list = &[0][]u8{}, - .libc_installation = null, - } - else - getZigShippedLibCIncludeDirsDarwin(arena, zig_lib_dir, target); - } const libc = try arena.create(LibCInstallation); - libc.* = LibCInstallation.findNative(.{ .allocator = arena }) catch |err| switch (err) { + libc.* = LibCInstallation.findNative(.{ .allocator = arena, .target = target }) catch |err| switch (err) { error.CCompilerExitCode, error.CCompilerCrashed, error.CCompilerCannotFindHeaders, error.UnableToSpawnCCompiler, + error.DarwinSdkNotFound, => |e| { // We tried to integrate with the native system C compiler, // however, it is not installed. So we must rely on our bundled // libc files. if (target_util.canBuildLibC(target)) { - return detectLibCFromBuilding(arena, zig_lib_dir, target, has_macos_sdk); + return detectLibCFromBuilding(arena, zig_lib_dir, target); } return e; }, @@ -4912,7 +4906,7 @@ fn detectLibCIncludeDirs( // If not linking system libraries, build and provide our own libc by // default if possible. if (target_util.canBuildLibC(target)) { - return detectLibCFromBuilding(arena, zig_lib_dir, target, has_macos_sdk); + return detectLibCFromBuilding(arena, zig_lib_dir, target); } // If zig can't build the libc for the target and we are targeting the @@ -4926,18 +4920,21 @@ fn detectLibCIncludeDirs( if (use_system_abi) { const libc = try arena.create(LibCInstallation); - libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true }); + libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true, .target = target }); return detectLibCFromLibCInstallation(arena, target, libc); } return LibCDirs{ .libc_include_dir_list = &[0][]u8{}, .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, }; } fn detectLibCFromLibCInstallation(arena: Allocator, target: Target, lci: *const LibCInstallation) !LibCDirs { var list = try std.ArrayList([]const u8).initCapacity(arena, 5); + var framework_list = std.ArrayList([]const u8).init(arena); list.appendAssumeCapacity(lci.include_dir.?); @@ -4965,9 +4962,20 @@ fn detectLibCFromLibCInstallation(arena: Allocator, target: Target, lci: *const list.appendAssumeCapacity(config_dir); } + var sysroot: ?[]const u8 = null; + + if (target.isDarwin()) d: { + const down1 = std.fs.path.dirname(lci.sys_include_dir.?) orelse break :d; + const down2 = std.fs.path.dirname(down1) orelse break :d; + try framework_list.append(try std.fs.path.join(arena, &.{ down2, "System", "Library", "Frameworks" })); + sysroot = down2; + } + return LibCDirs{ .libc_include_dir_list = list.items, .libc_installation = lci, + .libc_framework_dir_list = framework_list.items, + .sysroot = sysroot, }; } @@ -4975,69 +4983,61 @@ fn detectLibCFromBuilding( arena: Allocator, zig_lib_dir: []const u8, target: std.Target, - has_macos_sdk: bool, ) !LibCDirs { - switch (target.os.tag) { - .macos => return if (has_macos_sdk) - // For Darwin/macOS, we are all set with getDarwinSDK found earlier. - LibCDirs{ - .libc_include_dir_list = &[0][]u8{}, - .libc_installation = null, - } - else - getZigShippedLibCIncludeDirsDarwin(arena, zig_lib_dir, target), - else => { - const generic_name = target_util.libCGenericName(target); - // Some architectures are handled by the same set of headers. - const arch_name = if (target.abi.isMusl()) - musl.archNameHeaders(target.cpu.arch) - else if (target.cpu.arch.isThumb()) - // ARM headers are valid for Thumb too. - switch (target.cpu.arch) { - .thumb => "arm", - .thumbeb => "armeb", - else => unreachable, - } - else - @tagName(target.cpu.arch); - const os_name = @tagName(target.os.tag); - // Musl's headers are ABI-agnostic and so they all have the "musl" ABI name. - const abi_name = if (target.abi.isMusl()) "musl" else @tagName(target.abi); - const s = std.fs.path.sep_str; - const arch_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", - .{ zig_lib_dir, arch_name, os_name, abi_name }, - ); - const generic_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{s}", - .{ zig_lib_dir, generic_name }, - ); - const generic_arch_name = target_util.osArchName(target); - const arch_os_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-any", - .{ zig_lib_dir, generic_arch_name, os_name }, - ); - const generic_os_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any", - .{ zig_lib_dir, os_name }, - ); + if (target.isDarwin()) + return getZigShippedLibCIncludeDirsDarwin(arena, zig_lib_dir, target); + + const generic_name = target_util.libCGenericName(target); + // Some architectures are handled by the same set of headers. + const arch_name = if (target.abi.isMusl()) + musl.archNameHeaders(target.cpu.arch) + else if (target.cpu.arch.isThumb()) + // ARM headers are valid for Thumb too. + switch (target.cpu.arch) { + .thumb => "arm", + .thumbeb => "armeb", + else => unreachable, + } + else + @tagName(target.cpu.arch); + const os_name = @tagName(target.os.tag); + // Musl's headers are ABI-agnostic and so they all have the "musl" ABI name. + const abi_name = if (target.abi.isMusl()) "musl" else @tagName(target.abi); + const s = std.fs.path.sep_str; + const arch_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", + .{ zig_lib_dir, arch_name, os_name, abi_name }, + ); + const generic_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{s}", + .{ zig_lib_dir, generic_name }, + ); + const generic_arch_name = target_util.osArchName(target); + const arch_os_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-any", + .{ zig_lib_dir, generic_arch_name, os_name }, + ); + const generic_os_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any", + .{ zig_lib_dir, os_name }, + ); - const list = try arena.alloc([]const u8, 4); - list[0] = arch_include_dir; - list[1] = generic_include_dir; - list[2] = arch_os_include_dir; - list[3] = generic_os_include_dir; + const list = try arena.alloc([]const u8, 4); + list[0] = arch_include_dir; + list[1] = generic_include_dir; + list[2] = arch_os_include_dir; + list[3] = generic_os_include_dir; - return LibCDirs{ - .libc_include_dir_list = list, - .libc_installation = null, - }; - }, - } + return LibCDirs{ + .libc_include_dir_list = list, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + }; } pub fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) ![]const u8 { @@ -5618,6 +5618,11 @@ pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void { // to queue up a work item to produce the DLL import library for this. const gop = try comp.bin_file.options.system_libs.getOrPut(comp.gpa, lib_name); if (!gop.found_existing and comp.getTarget().os.tag == .windows) { + gop.value_ptr.* = .{ + .needed = true, + .weak = false, + .path = null, + }; try comp.work_queue.writeItem(.{ .windows_import_lib = comp.bin_file.options.system_libs.count() - 1, }); diff --git a/src/libc_installation.zig b/src/libc_installation.zig index bdfe83b71ab7..2d42a03a32d7 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -33,6 +33,7 @@ pub const LibCInstallation = struct { LibCKernel32LibNotFound, UnsupportedArchitecture, WindowsSdkNotFound, + DarwinSdkNotFound, ZigIsTheCCompiler, }; @@ -171,6 +172,7 @@ pub const LibCInstallation = struct { pub const FindNativeOptions = struct { allocator: Allocator, + target: std.Target, /// If enabled, will print human-friendly errors to stderr. verbose: bool = false, @@ -181,7 +183,19 @@ pub const LibCInstallation = struct { var self: LibCInstallation = .{}; if (is_darwin) { - @panic("Darwin is handled separately via std.zig.system.darwin module"); + if (!std.zig.system.darwin.isSdkInstalled(args.allocator)) + return error.DarwinSdkNotFound; + const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse + return error.DarwinSdkNotFound; + defer args.allocator.free(sdk.path); + + self.include_dir = try fs.path.join(args.allocator, &.{ + sdk.path, "usr/include", + }); + self.sys_include_dir = try fs.path.join(args.allocator, &.{ + sdk.path, "usr/include", + }); + return self; } else if (is_windows) { var sdk: ZigWindowsSDK = ZigWindowsSDK.find(args.allocator) catch |err| switch (err) { error.NotFound => return error.WindowsSdkNotFound, diff --git a/src/link.zig b/src/link.zig index 42322cbda882..2fea01ff89ab 100644 --- a/src/link.zig +++ b/src/link.zig @@ -21,7 +21,20 @@ const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); /// When adding a new field, remember to update `hashAddSystemLibs`. +/// These are *always* dynamically linked. Static libraries will be +/// provided as positional arguments. pub const SystemLib = struct { + needed: bool, + weak: bool, + /// This can be null in two cases right now: + /// 1. Windows DLLs that zig ships such as advapi32. + /// 2. extern "foo" fn declarations where we find out about libraries too late + /// TODO: make this non-optional and resolve those two cases somehow. + path: ?[]const u8, +}; + +/// When adding a new field, remember to update `hashAddFrameworks`. +pub const Framework = struct { needed: bool = false, weak: bool = false, }; @@ -31,11 +44,23 @@ pub const SortSection = enum { name, alignment }; pub const CacheMode = enum { incremental, whole }; pub fn hashAddSystemLibs( - hh: *Cache.HashHelper, + man: *Cache.Manifest, hm: std.StringArrayHashMapUnmanaged(SystemLib), +) !void { + const keys = hm.keys(); + man.hash.addListOfBytes(keys); + for (hm.values()) |value| { + man.hash.add(value.needed); + man.hash.add(value.weak); + if (value.path) |p| _ = try man.addFile(p, null); + } +} + +pub fn hashAddFrameworks( + hh: *Cache.HashHelper, + hm: std.StringArrayHashMapUnmanaged(Framework), ) void { const keys = hm.keys(); - hh.add(keys.len); hh.addListOfBytes(keys); for (hm.values()) |value| { hh.add(value.needed); @@ -183,9 +208,12 @@ pub const Options = struct { objects: []Compilation.LinkObject, framework_dirs: []const []const u8, - frameworks: std.StringArrayHashMapUnmanaged(SystemLib), + frameworks: std.StringArrayHashMapUnmanaged(Framework), + /// These are *always* dynamically linked. Static libraries will be + /// provided as positional arguments. system_libs: std.StringArrayHashMapUnmanaged(SystemLib), wasi_emulated_libs: []const wasi_libc.CRTFile, + // TODO: remove this. libraries are resolved by the frontend. lib_dirs: []const []const u8, rpath_list: []const []const u8, @@ -203,6 +231,7 @@ pub const Options = struct { version: ?std.SemanticVersion, compatibility_version: ?std.SemanticVersion, + darwin_sdk_version: ?std.SemanticVersion = null, libc_installation: ?*const LibCInstallation, dwarf_format: ?std.dwarf.Format, @@ -213,9 +242,6 @@ pub const Options = struct { /// (Zig compiler development) Enable dumping of linker's state as JSON. enable_link_snapshots: bool = false, - /// (Darwin) Path and version of the native SDK if detected. - native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null, - /// (Darwin) Install name for the dylib install_name: ?[]const u8 = null, @@ -225,9 +251,6 @@ pub const Options = struct { /// (Darwin) size of the __PAGEZERO segment pagezero_size: ?u64 = null, - /// (Darwin) search strategy for system libraries - search_strategy: ?File.MachO.SearchStrategy = null, - /// (Darwin) set minimum space for future expansion of the load commands headerpad_size: ?u32 = null, diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig index 800b12808133..bd82254e6aed 100644 --- a/src/link/Coff/lld.zig +++ b/src/link/Coff/lld.zig @@ -88,7 +88,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod } } } - link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); + try link.hashAddSystemLibs(&man, self.base.options.system_libs); man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); man.hash.addOptional(self.base.options.subsystem); man.hash.add(self.base.options.is_test); @@ -405,6 +405,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "uuid.lib")); for (mingw.always_link_libs) |name| { if (!self.base.options.system_libs.contains(name)) { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index c4371fbcbff6..dd88d47fab23 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1428,7 +1428,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v } man.hash.addOptionalBytes(self.base.options.soname); man.hash.addOptional(self.base.options.version); - link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); + try link.hashAddSystemLibs(&man, self.base.options.system_libs); man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); man.hash.add(allow_shlib_undefined); man.hash.add(self.base.options.bind_global_refs_locally); @@ -1824,8 +1824,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v argv.appendAssumeCapacity("--as-needed"); var as_needed = true; - for (system_libs, 0..) |link_lib, i| { - const lib_as_needed = !system_libs_values[i].needed; + for (system_libs_values) |lib_info| { + const lib_as_needed = !lib_info.needed; switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { 0b00, 0b11 => {}, 0b01 => { @@ -1842,9 +1842,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v // libraries and not static libraries (the check for that needs to be earlier), // but they could be full paths to .so files, in which case we // want to avoid prepending "-l". - const ext = Compilation.classifyFileExt(link_lib); - const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); - argv.appendAssumeCapacity(arg); + argv.appendAssumeCapacity(lib_info.path.?); } if (!as_needed) { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 44e6acc3976d..1f2dd4f8b007 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -58,11 +58,6 @@ const Rebase = @import("MachO/dyld_info/Rebase.zig"); pub const base_tag: File.Tag = File.Tag.macho; -pub const SearchStrategy = enum { - paths_first, - dylibs_first, -}; - /// Mode of operation of the linker. pub const Mode = enum { /// Incremental mode will preallocate segments/sections and is compatible with @@ -834,39 +829,50 @@ pub fn resolveLibSystem( out_libs: anytype, ) !void { // If we were given the sysroot, try to look there first for libSystem.B.{dylib, tbd}. - var libsystem_available = false; - if (syslibroot != null) blk: { - // Try stub file first. If we hit it, then we're done as the stub file - // re-exports every single symbol definition. - for (search_dirs) |dir| { - if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| { - try out_libs.put(full_path, .{ .needed = true }); - libsystem_available = true; - break :blk; - } + if (syslibroot) |root| { + const full_dir_path = try std.fs.path.join(arena, &.{ root, "usr", "lib" }); + if (try resolveLibSystemInDirs(arena, &.{full_dir_path}, out_libs)) return; + } + + // Next, try input search dirs if we are linking on a custom host such as Nix. + if (try resolveLibSystemInDirs(arena, search_dirs, out_libs)) return; + + // As a fallback, try linking against Zig shipped stub. + const libsystem_name = try std.fmt.allocPrint(arena, "libSystem.{d}.tbd", .{ + target.os.version_range.semver.min.major, + }); + const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ + "libc", "darwin", libsystem_name, + }); + try out_libs.put(full_path, .{ + .needed = true, + .weak = false, + .path = full_path, + }); +} + +fn resolveLibSystemInDirs(arena: Allocator, dirs: []const []const u8, out_libs: anytype) !bool { + // Try stub file first. If we hit it, then we're done as the stub file + // re-exports every single symbol definition. + for (dirs) |dir| { + if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| { + try out_libs.put(full_path, .{ .needed = true, .weak = false, .path = full_path }); + return true; } - // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib - // doesn't export libc.dylib which we'll need to resolve subsequently also. - for (search_dirs) |dir| { - if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| { - if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| { - try out_libs.put(libsystem_path, .{ .needed = true }); - try out_libs.put(libc_path, .{ .needed = true }); - libsystem_available = true; - break :blk; - } + } + // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib + // doesn't export libc.dylib which we'll need to resolve subsequently also. + for (dirs) |dir| { + if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| { + if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| { + try out_libs.put(libsystem_path, .{ .needed = true, .weak = false, .path = libsystem_path }); + try out_libs.put(libc_path, .{ .needed = true, .weak = false, .path = libc_path }); + return true; } } } - if (!libsystem_available) { - const libsystem_name = try std.fmt.allocPrint(arena, "libSystem.{d}.tbd", .{ - target.os.version_range.semver.min.major, - }); - const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ - "libc", "darwin", libsystem_name, - }); - try out_libs.put(full_path, .{ .needed = true }); - } + + return false; } pub fn resolveSearchDir( diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig index 10f446f19159..8f803d98cb2d 100644 --- a/src/link/MachO/load_commands.zig +++ b/src/link/MachO/load_commands.zig @@ -278,11 +278,10 @@ pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !vo const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8)); break :blk platform_version; }; - const sdk_version = if (options.native_darwin_sdk) |sdk| blk: { - const ver = sdk.version; - const sdk_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8)); - break :blk sdk_version; - } else platform_version; + const sdk_version: u32 = if (options.darwin_sdk_version) |ver| + @intCast(ver.major << 16 | ver.minor << 8) + else + platform_version; const is_simulator_abi = options.target.abi == .simulator; try lc_writer.writeStruct(macho.build_version_command{ .cmdsize = cmdsize, diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index 39b64fe1787d..56eee2b5463e 100644 --- a/src/link/MachO/zld.zig +++ b/src/link/MachO/zld.zig @@ -3410,7 +3410,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr // installation sources because they are always a product of the compiler version + target information. man.hash.add(stack_size); man.hash.addOptional(options.pagezero_size); - man.hash.addOptional(options.search_strategy); man.hash.addOptional(options.headerpad_size); man.hash.add(options.headerpad_max_install_names); man.hash.add(gc_sections); @@ -3418,13 +3417,13 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr man.hash.add(options.strip); man.hash.addListOfBytes(options.lib_dirs); man.hash.addListOfBytes(options.framework_dirs); - link.hashAddSystemLibs(&man.hash, options.frameworks); + link.hashAddFrameworks(&man.hash, options.frameworks); man.hash.addListOfBytes(options.rpath_list); if (is_dyn_lib) { man.hash.addOptionalBytes(options.install_name); man.hash.addOptional(options.version); } - link.hashAddSystemLibs(&man.hash, options.system_libs); + try link.hashAddSystemLibs(&man, options.system_libs); man.hash.addOptionalBytes(options.sysroot); man.hash.addListOfBytes(options.force_undefined_symbols.keys()); try man.addOptionalFile(options.entitlements); @@ -3550,84 +3549,15 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr try positionals.append(comp.libcxx_static_lib.?.full_object_path); } - // Shared and static libraries passed via `-l` flag. - var candidate_libs = std.StringArrayHashMap(link.SystemLib).init(arena); - - const system_lib_names = options.system_libs.keys(); - for (system_lib_names) |system_lib_name| { - // 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". - if (Compilation.classifyFileExt(system_lib_name) == .shared_library) { - try positionals.append(system_lib_name); - continue; - } - - const system_lib_info = options.system_libs.get(system_lib_name).?; - try candidate_libs.put(system_lib_name, .{ - .needed = system_lib_info.needed, - .weak = system_lib_info.weak, - }); - } - - var lib_dirs = std.ArrayList([]const u8).init(arena); - for (options.lib_dirs) |dir| { - if (try MachO.resolveSearchDir(arena, dir, options.sysroot)) |search_dir| { - try lib_dirs.append(search_dir); - } else { - log.warn("directory not found for '-L{s}'", .{dir}); - } - } - var libs = std.StringArrayHashMap(link.SystemLib).init(arena); - // Assume ld64 default -search_paths_first if no strategy specified. - const search_strategy = options.search_strategy orelse .paths_first; - outer: for (candidate_libs.keys()) |lib_name| { - switch (search_strategy) { - .paths_first => { - // Look in each directory for a dylib (stub first), and then for archive - for (lib_dirs.items) |dir| { - for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| { - if (try MachO.resolveLib(arena, dir, lib_name, ext)) |full_path| { - try libs.put(full_path, candidate_libs.get(lib_name).?); - continue :outer; - } - } - } else { - log.warn("library not found for '-l{s}'", .{lib_name}); - lib_not_found = true; - } - }, - .dylibs_first => { - // First, look for a dylib in each search dir - for (lib_dirs.items) |dir| { - for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| { - if (try MachO.resolveLib(arena, dir, lib_name, ext)) |full_path| { - try libs.put(full_path, candidate_libs.get(lib_name).?); - continue :outer; - } - } - } else for (lib_dirs.items) |dir| { - if (try MachO.resolveLib(arena, dir, lib_name, ".a")) |full_path| { - try libs.put(full_path, candidate_libs.get(lib_name).?); - } else { - log.warn("library not found for '-l{s}'", .{lib_name}); - lib_not_found = true; - } - } - }, - } - } - - if (lib_not_found) { - log.warn("Library search paths:", .{}); - for (lib_dirs.items) |dir| { - log.warn(" {s}", .{dir}); - } + { + const vals = options.system_libs.values(); + try libs.ensureUnusedCapacity(vals.len); + for (vals) |v| libs.putAssumeCapacity(v.path.?, v); } - try MachO.resolveLibSystem(arena, comp, options.sysroot, target, lib_dirs.items, &libs); + try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs); // frameworks var framework_dirs = std.ArrayList([]const u8).init(arena); @@ -3647,6 +3577,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr try libs.put(full_path, .{ .needed = info.needed, .weak = info.weak, + .path = full_path, }); continue :outer; } @@ -3698,11 +3629,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size})); } - if (options.search_strategy) |strat| switch (strat) { - .paths_first => try argv.append("-search_paths_first"), - .dylibs_first => try argv.append("-search_dylibs_first"), - }; - if (options.headerpad_size) |headerpad_size| { try argv.append("-headerpad_size"); try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{headerpad_size})); diff --git a/src/main.zig b/src/main.zig index c29c35ede49d..456886c915ce 100644 --- a/src/main.zig +++ b/src/main.zig @@ -28,6 +28,7 @@ const target_util = @import("target.zig"); const crash_report = @import("crash_report.zig"); const Module = @import("Module.zig"); const AstGen = @import("AstGen.zig"); +const mingw = @import("mingw.zig"); const Server = std.zig.Server; pub const std_options = struct { @@ -476,7 +477,19 @@ const usage_build_generic = \\ -l[lib], --library [lib] Link against system library (only if actually used) \\ -needed-l[lib], Link against system library (even if unused) \\ --needed-library [lib] + \\ -weak-l[lib] link against system library marking it and all + \\ -weak_library [lib] referenced symbols as weak \\ -L[d], --library-directory [d] Add a directory to the library search path + \\ -search_paths_first For each library search path, check for dynamic + \\ lib then static lib before proceeding to next path. + \\ -search_paths_first_static For each library search path, check for static + \\ lib then dynamic lib before proceeding to next path. + \\ -search_dylibs_first Search for dynamic libs in all library search + \\ paths, then static libs. + \\ -search_static_first Search for static libs in all library search + \\ paths, then dynamic libs. + \\ -search_dylibs_only Only search for dynamic libs. + \\ -search_static_only Only search for static libs. \\ -T[script], --script [script] Use a custom linker script \\ --version-script [path] Provide a version .map file \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) @@ -527,18 +540,14 @@ const usage_build_generic = \\ --subsystem [subsystem] (Windows) /SUBSYSTEM: to the linker \\ --stack [size] Override default stack size \\ --image-base [addr] Set base address for executable image - \\ -weak-l[lib] (Darwin) link against system library and mark it and all referenced symbols as weak - \\ -weak_library [lib] \\ -framework [name] (Darwin) link against framework \\ -needed_framework [name] (Darwin) link against framework (even if unused) - \\ -needed_library [lib] (Darwin) link against system library (even if unused) + \\ -needed_library [lib] link against system library (even if unused) \\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak \\ -F[dir] (Darwin) add search path for frameworks \\ -install_name=[value] (Darwin) add dylib's install name \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation - \\ -search_paths_first (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a` - \\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a` \\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation \\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN \\ -dead_strip (Darwin) remove functions and data that are unreachable by the entry point or exported symbols @@ -716,6 +725,39 @@ const ArgsIterator = struct { } }; +/// In contrast to `link.SystemLib`, this stores arguments that may need to be +/// resolved into static libraries so that we can pass only dynamic libraries +/// as system libs to `Compilation`. +const SystemLib = struct { + needed: bool, + weak: bool, + + preferred_mode: std.builtin.LinkMode, + search_strategy: SearchStrategy, + + const SearchStrategy = enum { paths_first, mode_first, no_fallback }; + + fn fallbackMode(this: SystemLib) std.builtin.LinkMode { + assert(this.search_strategy != .no_fallback); + return switch (this.preferred_mode) { + .Dynamic => .Static, + .Static => .Dynamic, + }; + } +}; + +const CliModule = struct { + mod: *Package, + /// still in CLI arg format + deps_str: []const u8, +}; + +fn cleanupModules(modules: *std.StringArrayHashMap(CliModule)) void { + var it = modules.iterator(); + while (it.next()) |kv| kv.value_ptr.mod.destroy(modules.allocator); + modules.deinit(); +} + fn buildOutputType( gpa: Allocator, arena: Allocator, @@ -849,12 +891,12 @@ fn buildOutputType( var minor_subsystem_version: ?u32 = null; var wasi_exec_model: ?std.builtin.WasiExecModel = null; var enable_link_snapshots: bool = false; - var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null; var install_name: ?[]const u8 = null; var hash_style: link.HashStyle = .both; var entitlements: ?[]const u8 = null; var pagezero_size: ?u64 = null; - var search_strategy: ?link.File.MachO.SearchStrategy = null; + var lib_search_strategy: SystemLib.SearchStrategy = .paths_first; + var lib_preferred_mode: std.builtin.LinkMode = .Dynamic; var headerpad_size: ?u32 = null; var headerpad_max_install_names: bool = false; var dead_strip_dylibs: bool = false; @@ -862,66 +904,30 @@ fn buildOutputType( var error_tracing: ?bool = null; var pdb_out_path: ?[]const u8 = null; var dwarf_format: ?std.dwarf.Format = null; - // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. // This array is populated by zig cc frontend and then has to be converted to zig-style // CPU features. - var llvm_m_args = std.ArrayList([]const u8).init(gpa); - defer llvm_m_args.deinit(); - - var system_libs = std.StringArrayHashMap(Compilation.SystemLib).init(gpa); - defer system_libs.deinit(); - - var static_libs = std.ArrayList([]const u8).init(gpa); - defer static_libs.deinit(); - - var wasi_emulated_libs = std.ArrayList(wasi_libc.CRTFile).init(gpa); - defer wasi_emulated_libs.deinit(); - - var clang_argv = std.ArrayList([]const u8).init(gpa); - defer clang_argv.deinit(); - - var extra_cflags = std.ArrayList([]const u8).init(gpa); - defer extra_cflags.deinit(); - - var lib_dirs = std.ArrayList([]const u8).init(gpa); - defer lib_dirs.deinit(); - - var rpath_list = std.ArrayList([]const u8).init(gpa); - defer rpath_list.deinit(); - + var llvm_m_args = std.ArrayList([]const u8).init(arena); + var system_libs = std.StringArrayHashMap(SystemLib).init(arena); + var wasi_emulated_libs = std.ArrayList(wasi_libc.CRTFile).init(arena); + var clang_argv = std.ArrayList([]const u8).init(arena); + var extra_cflags = std.ArrayList([]const u8).init(arena); + // These are before resolving sysroot. + var lib_dir_args = std.ArrayList([]const u8).init(arena); + var rpath_list = std.ArrayList([]const u8).init(arena); var symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{}; - - var c_source_files = std.ArrayList(Compilation.CSourceFile).init(gpa); - defer c_source_files.deinit(); - - var link_objects = std.ArrayList(Compilation.LinkObject).init(gpa); - defer link_objects.deinit(); - - var framework_dirs = std.ArrayList([]const u8).init(gpa); - defer framework_dirs.deinit(); - - var frameworks: std.StringArrayHashMapUnmanaged(Compilation.SystemLib) = .{}; - + 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) = .{}; // null means replace with the test executable binary - var test_exec_args = std.ArrayList(?[]const u8).init(gpa); - defer test_exec_args.deinit(); - - var linker_export_symbol_names = std.ArrayList([]const u8).init(gpa); - defer linker_export_symbol_names.deinit(); - + var test_exec_args = std.ArrayList(?[]const u8).init(arena); + var linker_export_symbol_names = std.ArrayList([]const u8).init(arena); // Contains every module specified via --mod. The dependencies are added // after argument parsing is completed. We use a StringArrayHashMap to make // error output consistent. - var modules = std.StringArrayHashMap(struct { - mod: *Package, - deps_str: []const u8, // still in CLI arg format - }).init(gpa); - defer { - var it = modules.iterator(); - while (it.next()) |kv| kv.value_ptr.mod.destroy(gpa); - modules.deinit(); - } + var modules = std.StringArrayHashMap(CliModule).init(gpa); + defer cleanupModules(&modules); // The dependency string for the root package var root_deps_str: ?[]const u8 = null; @@ -1061,7 +1067,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-rpath")) { try rpath_list.append(args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) { - try lib_dirs.append(args_iter.nextOrFatal()); + try lib_dir_args.append(args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-F")) { try framework_dirs.append(args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-framework")) { @@ -1085,9 +1091,23 @@ fn buildOutputType( fatal("unable to parse pagezero size'{s}': {s}", .{ next_arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-search_paths_first")) { - search_strategy = .paths_first; + lib_search_strategy = .paths_first; + lib_preferred_mode = .Dynamic; + } else if (mem.eql(u8, arg, "-search_paths_first_static")) { + lib_search_strategy = .paths_first; + lib_preferred_mode = .Static; } else if (mem.eql(u8, arg, "-search_dylibs_first")) { - search_strategy = .dylibs_first; + lib_search_strategy = .mode_first; + lib_preferred_mode = .Dynamic; + } else if (mem.eql(u8, arg, "-search_static_first")) { + lib_search_strategy = .mode_first; + lib_preferred_mode = .Static; + } else if (mem.eql(u8, arg, "-search_dylibs_only")) { + lib_search_strategy = .no_fallback; + lib_preferred_mode = .Dynamic; + } else if (mem.eql(u8, arg, "-search_static_only")) { + lib_search_strategy = .no_fallback; + lib_preferred_mode = .Static; } else if (mem.eql(u8, arg, "-headerpad")) { const next_arg = args_iter.nextOrFatal(); headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| { @@ -1104,17 +1124,33 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-version-script") or mem.eql(u8, arg, "--version-script")) { version_script = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "--library") or mem.eql(u8, arg, "-l")) { - // We don't know whether this library is part of libc or libc++ until - // we resolve the target, so we simply append to the list for now. - try system_libs.put(args_iter.nextOrFatal(), .{}); + // We don't know whether this library is part of libc + // or libc++ until we resolve the target, so we append + // to the list for now. + try system_libs.put(args_iter.nextOrFatal(), .{ + .needed = false, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l") or mem.eql(u8, arg, "-needed_library")) { const next_arg = args_iter.nextOrFatal(); - try system_libs.put(next_arg, .{ .needed = true }); + try system_libs.put(next_arg, .{ + .needed = true, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) { - try system_libs.put(args_iter.nextOrFatal(), .{ .weak = true }); + try system_libs.put(args_iter.nextOrFatal(), .{ + .needed = false, + .weak = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.eql(u8, arg, "-D")) { try clang_argv.append(arg); try clang_argv.append(args_iter.nextOrFatal()); @@ -1346,8 +1382,12 @@ fn buildOutputType( emit_implib_arg_provided = true; } else if (mem.eql(u8, arg, "-dynamic")) { link_mode = .Dynamic; + lib_preferred_mode = .Dynamic; + lib_search_strategy = .mode_first; } else if (mem.eql(u8, arg, "-static")) { link_mode = .Static; + lib_preferred_mode = .Static; + lib_search_strategy = .no_fallback; } else if (mem.eql(u8, arg, "-fdll-export-fns")) { dll_export_fns = true; } else if (mem.eql(u8, arg, "-fno-dll-export-fns")) { @@ -1486,17 +1526,33 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-T")) { linker_script = arg[2..]; } else if (mem.startsWith(u8, arg, "-L")) { - try lib_dirs.append(arg[2..]); + try lib_dir_args.append(arg[2..]); } else if (mem.startsWith(u8, arg, "-F")) { try framework_dirs.append(arg[2..]); } else if (mem.startsWith(u8, arg, "-l")) { - // We don't know whether this library is part of libc or libc++ until - // we resolve the target, so we simply append to the list for now. - try system_libs.put(arg["-l".len..], .{}); + // We don't know whether this library is part of libc + // or libc++ until we resolve the target, so we append + // to the list for now. + try system_libs.put(arg["-l".len..], .{ + .needed = false, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.startsWith(u8, arg, "-needed-l")) { - try system_libs.put(arg["-needed-l".len..], .{ .needed = true }); + try system_libs.put(arg["-needed-l".len..], .{ + .needed = true, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.startsWith(u8, arg, "-weak-l")) { - try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); + try system_libs.put(arg["-weak-l".len..], .{ + .needed = false, + .weak = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.startsWith(u8, arg, "-D")) { try clang_argv.append(arg); } else if (mem.startsWith(u8, arg, "-I")) { @@ -1571,7 +1627,6 @@ fn buildOutputType( var emit_llvm = false; var needed = false; var must_link = false; - var force_static_libs = false; var file_ext: ?Compilation.FileExt = null; while (it.has_next) { it.next() catch |err| { @@ -1641,10 +1696,13 @@ fn buildOutputType( .must_link = must_link, .loption = true, }); - } else if (force_static_libs) { - try static_libs.append(it.only_arg); } else { - try system_libs.put(it.only_arg, .{ .needed = needed }); + try system_libs.put(it.only_arg, .{ + .needed = needed, + .weak = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } }, .ignore => {}, @@ -1740,17 +1798,21 @@ fn buildOutputType( mem.eql(u8, linker_arg, "-dy") or mem.eql(u8, linker_arg, "-call_shared")) { - force_static_libs = false; + lib_search_strategy = .no_fallback; + lib_preferred_mode = .Dynamic; } else if (mem.eql(u8, linker_arg, "-Bstatic") or mem.eql(u8, linker_arg, "-dn") or mem.eql(u8, linker_arg, "-non_shared") or mem.eql(u8, linker_arg, "-static")) { - force_static_libs = true; + lib_search_strategy = .no_fallback; + lib_preferred_mode = .Static; } else if (mem.eql(u8, linker_arg, "-search_paths_first")) { - search_strategy = .paths_first; + lib_search_strategy = .paths_first; + lib_preferred_mode = .Dynamic; } else if (mem.eql(u8, linker_arg, "-search_dylibs_first")) { - search_strategy = .dylibs_first; + lib_search_strategy = .mode_first; + lib_preferred_mode = .Dynamic; } else { try linker_args.append(linker_arg); } @@ -1828,7 +1890,7 @@ fn buildOutputType( try linker_args.append("-z"); try linker_args.append(it.only_arg); }, - .lib_dir => try lib_dirs.append(it.only_arg), + .lib_dir => try lib_dir_args.append(it.only_arg), .mcpu => target_mcpu = it.only_arg, .m => try llvm_m_args.append(it.only_arg), .dep_file => { @@ -1860,7 +1922,12 @@ fn buildOutputType( .force_undefined_symbol => { try force_undefined_symbols.put(gpa, it.only_arg, {}); }, - .weak_library => try system_libs.put(it.only_arg, .{ .weak = true }), + .weak_library => try system_libs.put(it.only_arg, .{ + .needed = false, + .weak = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }), .weak_framework => try frameworks.put(gpa, it.only_arg, .{ .weak = true }), .headerpad_max_install_names => headerpad_max_install_names = true, .compress_debug_sections => { @@ -2156,11 +2223,26 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-needed_framework")) { try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .needed = true }); } else if (mem.eql(u8, arg, "-needed_library")) { - try system_libs.put(linker_args_it.nextOrFatal(), .{ .needed = true }); + try system_libs.put(linker_args_it.nextOrFatal(), .{ + .weak = false, + .needed = true, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.startsWith(u8, arg, "-weak-l")) { - try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); + try system_libs.put(arg["-weak-l".len..], .{ + .weak = true, + .needed = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.eql(u8, arg, "-weak_library")) { - try system_libs.put(linker_args_it.nextOrFatal(), .{ .weak = true }); + try system_libs.put(linker_args_it.nextOrFatal(), .{ + .weak = true, + .needed = false, + .preferred_mode = lib_preferred_mode, + .search_strategy = lib_search_strategy, + }); } else if (mem.eql(u8, arg, "-compatibility_version")) { const compat_version = linker_args_it.nextOrFatal(); compatibility_version = std.SemanticVersion.parse(compat_version) catch |err| { @@ -2458,105 +2540,6 @@ fn buildOutputType( } } - // Now that we have target info, we can find out if any of the system libraries - // are part of libc or libc++. We remove them from the list and communicate their - // existence via flags instead. - { - // Similarly, if any libs in this list are statically provided, we remove - // them from this list and populate the link_objects array instead. - const sep = fs.path.sep_str; - var test_path = std.ArrayList(u8).init(gpa); - defer test_path.deinit(); - - var i: usize = 0; - syslib: while (i < system_libs.count()) { - const lib_name = system_libs.keys()[i]; - - if (target_util.is_libc_lib_name(target_info.target, lib_name)) { - link_libc = true; - system_libs.orderedRemoveAt(i); - continue; - } - if (target_util.is_libcpp_lib_name(target_info.target, lib_name)) { - link_libcpp = true; - system_libs.orderedRemoveAt(i); - continue; - } - switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) { - .none => {}, - .only_libunwind, .both => { - link_libunwind = true; - system_libs.orderedRemoveAt(i); - continue; - }, - .only_compiler_rt => { - std.log.warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name}); - system_libs.orderedRemoveAt(i); - continue; - }, - } - - if (fs.path.isAbsolute(lib_name)) { - fatal("cannot use absolute path as a system library: {s}", .{lib_name}); - } - - if (target_info.target.os.tag == .wasi) { - if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| { - try wasi_emulated_libs.append(crt_file); - system_libs.orderedRemoveAt(i); - continue; - } - } - - for (lib_dirs.items) |lib_dir_path| { - if (cross_target.isDarwin()) break; // Targeting Darwin we let the linker resolve the libraries in the correct order - test_path.clearRetainingCapacity(); - try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{ - lib_dir_path, - target_info.target.libPrefix(), - lib_name, - target_info.target.staticLibSuffix(), - }); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| fatal("unable to search for static library '{s}': {s}", .{ - test_path.items, @errorName(e), - }), - }; - try link_objects.append(.{ .path = try arena.dupe(u8, test_path.items) }); - system_libs.orderedRemoveAt(i); - continue :syslib; - } - - // Unfortunately, in the case of MinGW we also need to look for `libfoo.a`. - if (target_info.target.isMinGW()) { - for (lib_dirs.items) |lib_dir_path| { - test_path.clearRetainingCapacity(); - try test_path.writer().print("{s}" ++ sep ++ "lib{s}.a", .{ - lib_dir_path, lib_name, - }); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| fatal("unable to search for static library '{s}': {s}", .{ - test_path.items, @errorName(e), - }), - }; - try link_objects.append(.{ .path = try arena.dupe(u8, test_path.items) }); - system_libs.orderedRemoveAt(i); - continue :syslib; - } - } - - std.log.scoped(.cli).debug("depending on system for -l{s}", .{lib_name}); - - i += 1; - } - } - // libc++ depends on libc - if (link_libcpp) { - link_libc = true; - } - if (use_lld) |opt| { if (opt and cross_target.isDarwin()) { fatal("LLD requested with Mach-O object format. Only the self-hosted linker is supported for this target.", .{}); @@ -2575,8 +2558,124 @@ fn buildOutputType( want_native_include_dirs = true; } - if (sysroot == null and cross_target.isNativeOs() and - (system_libs.count() != 0 or want_native_include_dirs)) + // 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| { + if (fs.path.isAbsolute(dir)) { + const stripped_dir = dir[fs.path.diskDesignator(dir).len..]; + const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir }); + try lib_dirs.append(full_path); + } + try lib_dirs.append(dir); + } + } else { + lib_dirs = lib_dir_args; + } + lib_dir_args = undefined; // From here we use lib_dirs instead. + + const self_exe_path: ?[]const u8 = if (!process.can_spawn) + null + else + introspect.findZigExePath(arena) catch |err| { + fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); + }; + + var zig_lib_directory: Compilation.Directory = d: { + if (override_lib_dir) |unresolved_lib_dir| { + const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir); + break :d .{ + .path = lib_dir, + .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { + fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) }); + }, + }; + } else if (builtin.os.tag == .wasi) { + break :d getWasiPreopen("/lib"); + } else if (self_exe_path) |p| { + break :d introspect.findZigLibDirFromSelfExe(arena, p) catch |err| { + fatal("unable to find zig installation directory: {s}", .{@errorName(err)}); + }; + } else { + unreachable; + } + }; + defer zig_lib_directory.handle.close(); + + // First, remove libc, libc++, and compiler_rt libraries from the system libraries list. + // We need to know whether the set of system libraries contains anything besides these + // to decide whether to trigger native path detection logic. + var external_system_libs: std.MultiArrayList(struct { + name: []const u8, + info: SystemLib, + }) = .{}; + + var resolved_system_libs: std.MultiArrayList(struct { + name: []const u8, + lib: Compilation.SystemLib, + }) = .{}; + + for (system_libs.keys(), system_libs.values()) |lib_name, info| { + if (target_util.is_libc_lib_name(target_info.target, lib_name)) { + link_libc = true; + continue; + } + if (target_util.is_libcpp_lib_name(target_info.target, lib_name)) { + link_libcpp = true; + continue; + } + switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) { + .none => {}, + .only_libunwind, .both => { + link_libunwind = true; + continue; + }, + .only_compiler_rt => { + std.log.warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name}); + continue; + }, + } + + if (target_info.target.os.tag == .windows) { + const exists = mingw.libExists(arena, target_info.target, zig_lib_directory, lib_name) catch |err| { + fatal("failed to check zig installation for DLL import libs: {s}", .{ + @errorName(err), + }); + }; + if (exists) { + try resolved_system_libs.append(arena, .{ + .name = lib_name, + .lib = .{ + .needed = true, + .weak = false, + .path = null, + }, + }); + continue; + } + } + + if (fs.path.isAbsolute(lib_name)) { + fatal("cannot use absolute path as a system library: {s}", .{lib_name}); + } + + if (target_info.target.os.tag == .wasi) { + if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| { + try wasi_emulated_libs.append(crt_file); + continue; + } + } + + try external_system_libs.append(arena, .{ + .name = lib_name, + .info = info, + }); + } + // After this point, external_system_libs is used instead of system_libs. + + // Trigger native system library path detection if necessary. + if (sysroot == null and cross_target.isNativeOs() and cross_target.isNativeAbi() and + (external_system_libs.len != 0 or want_native_include_dirs)) { const paths = std.zig.system.NativePaths.detect(arena, target_info) catch |err| { fatal("unable to detect native system paths: {s}", .{@errorName(err)}); @@ -2585,83 +2684,181 @@ fn buildOutputType( warn("{s}", .{warning}); } - const has_sysroot = if (comptime builtin.target.isDarwin()) outer: { - if (std.zig.system.darwin.isDarwinSDKInstalled(arena)) { - const sdk = std.zig.system.darwin.getDarwinSDK(arena, target_info.target) orelse - break :outer false; - native_darwin_sdk = sdk; - try clang_argv.ensureUnusedCapacity(2); - clang_argv.appendAssumeCapacity("-isysroot"); - clang_argv.appendAssumeCapacity(sdk.path); - break :outer true; - } else break :outer false; - } else false; - try clang_argv.ensureUnusedCapacity(paths.include_dirs.items.len * 2); - const isystem_flag = if (has_sysroot) "-iwithsysroot" else "-isystem"; for (paths.include_dirs.items) |include_dir| { - clang_argv.appendAssumeCapacity(isystem_flag); + clang_argv.appendAssumeCapacity("-isystem"); clang_argv.appendAssumeCapacity(include_dir); } - try clang_argv.ensureUnusedCapacity(paths.framework_dirs.items.len * 2); - try framework_dirs.ensureUnusedCapacity(paths.framework_dirs.items.len); - const iframework_flag = if (has_sysroot) "-iframeworkwithsysroot" else "-iframework"; - for (paths.framework_dirs.items) |framework_dir| { - clang_argv.appendAssumeCapacity(iframework_flag); - clang_argv.appendAssumeCapacity(framework_dir); - framework_dirs.appendAssumeCapacity(framework_dir); - } - - for (paths.lib_dirs.items) |lib_dir| { - try lib_dirs.append(lib_dir); - } - for (paths.rpaths.items) |rpath| { - try rpath_list.append(rpath); - } + try framework_dirs.appendSlice(paths.framework_dirs.items); + try lib_dirs.appendSlice(paths.lib_dirs.items); + try rpath_list.appendSlice(paths.rpaths.items); } + // If any libs in this list are statically provided, we omit them from the + // resolved list and populate the link_objects array instead. { - // Resolve static libraries into full paths. - const sep = fs.path.sep_str; - var test_path = std.ArrayList(u8).init(gpa); defer test_path.deinit(); - for (static_libs.items) |static_lib| { - for (lib_dirs.items) |lib_dir_path| { - test_path.clearRetainingCapacity(); - try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{ - lib_dir_path, - target_info.target.libPrefix(), - static_lib, - target_info.target.staticLibSuffix(), - }); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| fatal("unable to search for static library '{s}': {s}", .{ - test_path.items, @errorName(e), - }), - }; - try link_objects.append(.{ .path = try arena.dupe(u8, test_path.items) }); - break; - } else { - var search_paths = std.ArrayList(u8).init(arena); - for (lib_dirs.items) |lib_dir_path| { - try search_paths.writer().print("\n {s}" ++ sep ++ "{s}{s}{s}", .{ - lib_dir_path, - target_info.target.libPrefix(), - static_lib, - target_info.target.staticLibSuffix(), + var checked_paths = std.ArrayList(u8).init(gpa); + defer checked_paths.deinit(); + + var failed_libs = std.ArrayList(struct { + name: []const u8, + strategy: SystemLib.SearchStrategy, + checked_paths: []const u8, + preferred_mode: std.builtin.LinkMode, + }).init(arena); + + syslib: for (external_system_libs.items(.name), external_system_libs.items(.info)) |lib_name, info| { + // Checked in the first pass above while looking for libc libraries. + assert(!fs.path.isAbsolute(lib_name)); + + checked_paths.clearRetainingCapacity(); + + switch (info.search_strategy) { + .mode_first, .no_fallback => { + // check for preferred mode + for (lib_dirs.items) |lib_dir_path| { + if (try accessLibPath( + &test_path, + &checked_paths, + lib_dir_path, + lib_name, + target_info.target, + info.preferred_mode, + )) { + const path = try arena.dupe(u8, test_path.items); + switch (info.preferred_mode) { + .Static => try link_objects.append(.{ .path = path }), + .Dynamic => try resolved_system_libs.append(arena, .{ + .name = lib_name, + .lib = .{ + .needed = info.needed, + .weak = info.weak, + .path = path, + }, + }), + } + continue :syslib; + } + } + // check for fallback mode + if (info.search_strategy == .no_fallback) { + try failed_libs.append(.{ + .name = lib_name, + .strategy = info.search_strategy, + .checked_paths = try arena.dupe(u8, checked_paths.items), + .preferred_mode = info.preferred_mode, + }); + continue :syslib; + } + for (lib_dirs.items) |lib_dir_path| { + if (try accessLibPath( + &test_path, + &checked_paths, + lib_dir_path, + lib_name, + target_info.target, + info.fallbackMode(), + )) { + const path = try arena.dupe(u8, test_path.items); + switch (info.fallbackMode()) { + .Static => try link_objects.append(.{ .path = path }), + .Dynamic => try resolved_system_libs.append(arena, .{ + .name = lib_name, + .lib = .{ + .needed = info.needed, + .weak = info.weak, + .path = path, + }, + }), + } + continue :syslib; + } + } + try failed_libs.append(.{ + .name = lib_name, + .strategy = info.search_strategy, + .checked_paths = try arena.dupe(u8, checked_paths.items), + .preferred_mode = info.preferred_mode, }); - } - try search_paths.appendSlice("\n suggestion: use full paths to static libraries on the command line rather than using -l and -L arguments"); - fatal("static library '{s}' not found. search paths: {s}", .{ - static_lib, search_paths.items, + continue :syslib; + }, + .paths_first => { + for (lib_dirs.items) |lib_dir_path| { + // check for preferred mode + if (try accessLibPath( + &test_path, + &checked_paths, + lib_dir_path, + lib_name, + target_info.target, + info.preferred_mode, + )) { + const path = try arena.dupe(u8, test_path.items); + switch (info.preferred_mode) { + .Static => try link_objects.append(.{ .path = path }), + .Dynamic => try resolved_system_libs.append(arena, .{ + .name = lib_name, + .lib = .{ + .needed = info.needed, + .weak = info.weak, + .path = path, + }, + }), + } + continue :syslib; + } + + // check for fallback mode + if (try accessLibPath( + &test_path, + &checked_paths, + lib_dir_path, + lib_name, + target_info.target, + info.fallbackMode(), + )) { + const path = try arena.dupe(u8, test_path.items); + switch (info.fallbackMode()) { + .Static => try link_objects.append(.{ .path = path }), + .Dynamic => try resolved_system_libs.append(arena, .{ + .name = lib_name, + .lib = .{ + .needed = info.needed, + .weak = info.weak, + .path = path, + }, + }), + } + continue :syslib; + } + } + try failed_libs.append(.{ + .name = lib_name, + .strategy = info.search_strategy, + .checked_paths = try arena.dupe(u8, checked_paths.items), + .preferred_mode = info.preferred_mode, + }); + continue :syslib; + }, + } + @compileError("unreachable"); + } + + if (failed_libs.items.len > 0) { + for (failed_libs.items) |f| { + const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; + std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{ + @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths, }); } + process.exit(1); } } + // After this point, resolved_system_libs is used instead of external_system_libs. const object_format = target_info.target.ofmt; @@ -2912,35 +3109,6 @@ fn buildOutputType( } } - const self_exe_path: ?[]const u8 = if (!process.can_spawn) - null - else - introspect.findZigExePath(arena) catch |err| { - fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); - }; - - var zig_lib_directory: Compilation.Directory = d: { - if (override_lib_dir) |unresolved_lib_dir| { - const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir); - break :d .{ - .path = lib_dir, - .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { - fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) }); - }, - }; - } else if (builtin.os.tag == .wasi) { - break :d getWasiPreopen("/lib"); - } else if (self_exe_path) |p| { - break :d introspect.findZigLibDirFromSelfExe(arena, p) catch |err| { - fatal("unable to find zig installation directory: {s}", .{@errorName(err)}); - }; - } else { - unreachable; - } - }; - - defer zig_lib_directory.handle.close(); - var thread_pool: ThreadPool = undefined; try thread_pool.init(.{ .allocator = gpa }); defer thread_pool.deinit(); @@ -3086,8 +3254,8 @@ fn buildOutputType( .link_objects = link_objects.items, .framework_dirs = framework_dirs.items, .frameworks = frameworks, - .system_lib_names = system_libs.keys(), - .system_lib_infos = system_libs.values(), + .system_lib_names = resolved_system_libs.items(.name), + .system_lib_infos = resolved_system_libs.items(.lib), .wasi_emulated_libs = wasi_emulated_libs.items, .link_libc = link_libc, .link_libcpp = link_libcpp, @@ -3192,11 +3360,9 @@ fn buildOutputType( .wasi_exec_model = wasi_exec_model, .debug_compile_errors = debug_compile_errors, .enable_link_snapshots = enable_link_snapshots, - .native_darwin_sdk = native_darwin_sdk, .install_name = install_name, .entitlements = entitlements, .pagezero_size = pagezero_size, - .search_strategy = search_strategy, .headerpad_size = headerpad_size, .headerpad_max_install_names = headerpad_max_install_names, .dead_strip_dylibs = dead_strip_dylibs, @@ -4069,10 +4235,12 @@ pub fn cmdLibC(gpa: Allocator, args: []const []const u8) !void { if (!cross_target.isNative()) { fatal("unable to detect libc for non-native target", .{}); } + const target_info = try detectNativeTargetInfo(cross_target); var libc = LibCInstallation.findNative(.{ .allocator = gpa, .verbose = true, + .target = target_info.target, }) catch |err| { fatal("unable to detect native libc: {s}", .{@errorName(err)}); }; @@ -6068,3 +6236,83 @@ fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions { .include_reference_trace = ttyconf != .no_color, }; } + +fn accessLibPath( + test_path: *std.ArrayList(u8), + checked_paths: *std.ArrayList(u8), + lib_dir_path: []const u8, + lib_name: []const u8, + target: std.Target, + link_mode: std.builtin.LinkMode, +) !bool { + const sep = fs.path.sep_str; + + if (target.isDarwin() and link_mode == .Dynamic) tbd: { + // Prefer .tbd over .dylib. + test_path.clearRetainingCapacity(); + try test_path.writer().print("{s}" ++ sep ++ "lib{s}.tbd", .{ lib_dir_path, lib_name }); + try checked_paths.writer().print("\n {s}", .{test_path.items}); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => break :tbd, + else => |e| fatal("unable to search for tbd library '{s}': {s}", .{ + test_path.items, @errorName(e), + }), + }; + return true; + } + + main_check: { + test_path.clearRetainingCapacity(); + try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{ + lib_dir_path, + target.libPrefix(), + lib_name, + switch (link_mode) { + .Static => target.staticLibSuffix(), + .Dynamic => target.dynamicLibSuffix(), + }, + }); + try checked_paths.writer().print("\n {s}", .{test_path.items}); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => break :main_check, + else => |e| fatal("unable to search for {s} library '{s}': {s}", .{ + @tagName(link_mode), test_path.items, @errorName(e), + }), + }; + return true; + } + + // In the case of Darwin, the main check will be .dylib, so here we + // additionally check for .so files. + if (target.isDarwin() and link_mode == .Dynamic) so: { + test_path.clearRetainingCapacity(); + try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, lib_name }); + try checked_paths.writer().print("\n {s}", .{test_path.items}); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => break :so, + else => |e| fatal("unable to search for so library '{s}': {s}", .{ + test_path.items, @errorName(e), + }), + }; + return true; + } + + // In the case of MinGW, the main check will be .lib but we also need to + // look for `libfoo.a`. + if (target.isMinGW() and link_mode == .Static) mingw: { + test_path.clearRetainingCapacity(); + try test_path.writer().print("{s}" ++ sep ++ "lib{s}.a", .{ + lib_dir_path, lib_name, + }); + try checked_paths.writer().print("\n {s}", .{test_path.items}); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => break :mingw, + else => |e| fatal("unable to search for static library '{s}': {s}", .{ + test_path.items, @errorName(e), + }), + }; + return true; + } + + return false; +} diff --git a/src/mingw.zig b/src/mingw.zig index 39059f804a32..f4d5ae23e236 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -283,7 +283,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const def_file_path = findDef(comp, arena, lib_name) catch |err| switch (err) { + const def_file_path = findDef(arena, comp.getTarget(), comp.zig_lib_directory, lib_name) catch |err| switch (err) { error.FileNotFound => { log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name }); // In this case we will end up putting foo.lib onto the linker line and letting the linker @@ -431,10 +431,28 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { }); } -/// This function body is verbose but all it does is test 3 different paths and see if a .def file exists. -fn findDef(comp: *Compilation, allocator: Allocator, lib_name: []const u8) ![]u8 { - const target = comp.getTarget(); +pub fn libExists( + allocator: Allocator, + target: std.Target, + zig_lib_directory: Cache.Directory, + lib_name: []const u8, +) !bool { + const s = findDef(allocator, target, zig_lib_directory, lib_name) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + defer allocator.free(s); + return true; +} +/// This function body is verbose but all it does is test 3 different paths and +/// see if a .def file exists. +fn findDef( + allocator: Allocator, + target: std.Target, + zig_lib_directory: Cache.Directory, + lib_name: []const u8, +) ![]u8 { const lib_path = switch (target.cpu.arch) { .x86 => "lib32", .x86_64 => "lib64", @@ -451,7 +469,7 @@ fn findDef(comp: *Compilation, allocator: Allocator, lib_name: []const u8) ![]u8 { // Try the archtecture-specific path first. const fmt_path = "libc" ++ s ++ "mingw" ++ s ++ "{s}" ++ s ++ "{s}.def"; - if (comp.zig_lib_directory.path) |p| { + if (zig_lib_directory.path) |p| { try override_path.writer().print("{s}" ++ s ++ fmt_path, .{ p, lib_path, lib_name }); } else { try override_path.writer().print(fmt_path, .{ lib_path, lib_name }); @@ -468,7 +486,7 @@ fn findDef(comp: *Compilation, allocator: Allocator, lib_name: []const u8) ![]u8 // Try the generic version. override_path.shrinkRetainingCapacity(0); const fmt_path = "libc" ++ s ++ "mingw" ++ s ++ "lib-common" ++ s ++ "{s}.def"; - if (comp.zig_lib_directory.path) |p| { + if (zig_lib_directory.path) |p| { try override_path.writer().print("{s}" ++ s ++ fmt_path, .{ p, lib_name }); } else { try override_path.writer().print(fmt_path, .{lib_name}); @@ -485,7 +503,7 @@ fn findDef(comp: *Compilation, allocator: Allocator, lib_name: []const u8) ![]u8 // Try the generic version and preprocess it. override_path.shrinkRetainingCapacity(0); const fmt_path = "libc" ++ s ++ "mingw" ++ s ++ "lib-common" ++ s ++ "{s}.def.in"; - if (comp.zig_lib_directory.path) |p| { + if (zig_lib_directory.path) |p| { try override_path.writer().print("{s}" ++ s ++ fmt_path, .{ p, lib_name }); } else { try override_path.writer().print(fmt_path, .{lib_name}); diff --git a/src/target.zig b/src/target.zig index 3076a0b0dad4..886df5dcbeb7 100644 --- a/src/target.zig +++ b/src/target.zig @@ -366,6 +366,15 @@ pub fn is_libc_lib_name(target: std.Target, name: []const u8) bool { if (eqlIgnoreCase(ignore_case, name, "m")) return true; + if (eqlIgnoreCase(ignore_case, name, "uuid")) + return true; + if (eqlIgnoreCase(ignore_case, name, "mingw32")) + return true; + if (eqlIgnoreCase(ignore_case, name, "msvcrt-os")) + return true; + if (eqlIgnoreCase(ignore_case, name, "mingwex")) + return true; + return false; } diff --git a/test/link/macho/bugs/13056/build.zig b/test/link/macho/bugs/13056/build.zig index eade752bc46a..50eb99058326 100644 --- a/test/link/macho/bugs/13056/build.zig +++ b/test/link/macho/bugs/13056/build.zig @@ -16,7 +16,7 @@ pub fn build(b: *std.Build) void { fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const target_info = std.zig.system.NativeTargetInfo.detect(target) catch unreachable; - const sdk = std.zig.system.darwin.getDarwinSDK(b.allocator, target_info.target) orelse + const sdk = std.zig.system.darwin.getSdk(b.allocator, target_info.target) orelse @panic("macOS SDK is required to run the test"); const exe = b.addExecutable(.{ diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index 574782617b18..b128fe71cf9c 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -17,8 +17,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize { // -search_dylibs_first - const exe = createScenario(b, optimize, target, "search_dylibs_first"); - exe.search_strategy = .dylibs_first; + const exe = createScenario(b, optimize, target, "search_dylibs_first", .mode_first); const check = exe.checkObject(); check.checkStart(); @@ -34,8 +33,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize { // -search_paths_first - const exe = createScenario(b, optimize, target, "search_paths_first"); - exe.search_strategy = .paths_first; + const exe = createScenario(b, optimize, target, "search_paths_first", .paths_first); const run = b.addRunArtifact(exe); run.skip_foreign_checks = true; @@ -49,6 +47,7 @@ fn createScenario( optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, name: []const u8, + search_strategy: std.Build.Step.Compile.SystemLib.SearchStrategy, ) *std.Build.Step.Compile { const static = b.addStaticLibrary(.{ .name = name, @@ -73,7 +72,10 @@ fn createScenario( .target = target, }); exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} }); - exe.linkSystemLibraryName(name); + exe.linkSystemLibrary2(name, .{ + .use_pkg_config = .no, + .search_strategy = search_strategy, + }); exe.linkLibC(); exe.addLibraryPath(static.getEmittedBinDirectory()); exe.addLibraryPath(dylib.getEmittedBinDirectory());