Commit a1e21ceec8

Andrew Kelley <andrew@ziglang.org>
2023-08-02 02:43:09
frontend: fix linking to Windows DLLs as system libs
1 parent a08cc7d
src/Compilation.zig
@@ -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 = undefined,
+        };
         try comp.work_queue.writeItem(.{
             .windows_import_lib = comp.bin_file.options.system_libs.count() - 1,
         });
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 {
@@ -477,6 +478,8 @@ const usage_build_generic =
     \\  -needed-l[lib],                Link against system library (even if unused)
     \\    --needed-library [lib]
     \\  -L[d], --library-directory [d] Add a directory to the library search path
+    \\  -search_paths_first            Search each library search path for dynamic libs then static libs
+    \\  -search_dylibs_first           Search for dynamic libs in each library search path, then 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)
@@ -537,8 +540,6 @@ const usage_build_generic =
     \\  -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
@@ -2567,6 +2568,34 @@ fn buildOutputType(
     }
     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();
+
     // 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.
@@ -2612,6 +2641,25 @@ fn buildOutputType(
                 },
             }
 
+            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 = undefined,
+                        },
+                    });
+                    continue;
+                }
+            }
+
             if (fs.path.isAbsolute(lib_name)) {
                 fatal("cannot use absolute path as a system library: {s}", .{lib_name});
             }
@@ -2758,9 +2806,13 @@ fn buildOutputType(
 
         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), f.checked_paths,
+                    @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths,
                 });
+                if (f.preferred_mode == .Dynamic and f.strategy == .no_fallback) {
+                    std.log.info("to link statically, pass the library as a positional argument", .{});
+                }
             }
             process.exit(1);
         }
@@ -3079,35 +3131,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();
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});