authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-11-24 18:08:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-11-24 18:12:56-07:00
log27c5c7fb23fceb0a333444408a1dea4188a14c32
tree5c995301673743c3398940c0ebed49647e0e3119
parent7e23b3245a9bf6e002009e6c18c10a9995671afa

stage2: proper `-femit-implib` frontend support

* Improve the logic for determining whether emitting an import lib is eligible, and improve the error message when the user provides contradictory arguments. * Integrate with the EmitLoc / Emit system that already exists, and use the `-femit-implib[=path]`/`-fno-emit-implib` convention that already exists. * Proper integration with the caching system. * CLI: fix bug in error reporting for resolving EmitLoc values for other parameters.

4 files changed, 117 insertions(+), 50 deletions(-)

src/Compilation.zig+26-4
......@@ -654,6 +654,8 @@ pub const InitOptions = struct {
654654 emit_analysis: ?EmitLoc = null,
655655 /// `null` means to not emit docs.
656656 emit_docs: ?EmitLoc = null,
657 /// `null` means to not emit an import lib.
658 emit_implib: ?EmitLoc = null,
657659 link_mode: ?std.builtin.LinkMode = null,
658660 dll_export_fns: ?bool = false,
659661 /// Normally when using LLD to link, Zig uses a file named "lld.id" in the
......@@ -766,8 +768,6 @@ pub const InitOptions = struct {
766768 test_filter: ?[]const u8 = null,
767769 test_name_prefix: ?[]const u8 = null,
768770 subsystem: ?std.Target.SubSystem = null,
769 /// Windows/PE only. Where to output the import library, can contain directories.
770 out_implib: ?[]const u8 = null,
771771 /// WASI-only. Type of WASI execution model ("command" or "reactor").
772772 wasi_exec_model: ?std.builtin.WasiExecModel = null,
773773 /// (Zig compiler development) Enable dumping linker's state as JSON.
......@@ -947,7 +947,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
947947 options.output_mode == .Lib or
948948 options.image_base_override != null or
949949 options.linker_script != null or options.version_script != null or
950 options.out_implib != null)
950 options.emit_implib != null)
951951 {
952952 break :blk true;
953953 }
......@@ -1183,6 +1183,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
11831183 cache.hash.add(options.output_mode);
11841184 cache.hash.add(options.machine_code_model);
11851185 cache.hash.addOptionalEmitLoc(options.emit_bin);
1186 cache.hash.addOptionalEmitLoc(options.emit_implib);
11861187 cache.hash.addBytes(options.root_name);
11871188 if (options.target.os.tag == .wasi) cache.hash.add(wasi_exec_model);
11881189 // TODO audit this and make sure everything is in it
......@@ -1339,18 +1340,21 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
13391340
13401341 const bin_file_emit: ?link.Emit = blk: {
13411342 const emit_bin = options.emit_bin orelse break :blk null;
1343
13421344 if (emit_bin.directory) |directory| {
13431345 break :blk link.Emit{
13441346 .directory = directory,
13451347 .sub_path = emit_bin.basename,
13461348 };
13471349 }
1350
13481351 if (module) |zm| {
13491352 break :blk link.Emit{
13501353 .directory = zm.zig_cache_artifact_directory,
13511354 .sub_path = emit_bin.basename,
13521355 };
13531356 }
1357
13541358 // We could use the cache hash as is no problem, however, we increase
13551359 // the likelihood of cache hits by adding the first C source file
13561360 // path name (not contents) to the hash. This way if the user is compiling
......@@ -1377,6 +1381,24 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
13771381 };
13781382 };
13791383
1384 const implib_emit: ?link.Emit = blk: {
1385 const emit_implib = options.emit_implib orelse break :blk null;
1386
1387 if (emit_implib.directory) |directory| {
1388 break :blk link.Emit{
1389 .directory = directory,
1390 .sub_path = emit_implib.basename,
1391 };
1392 }
1393
1394 // Use the same directory as the bin. The CLI already emits an
1395 // error if -fno-emit-bin is combined with -femit-implib.
1396 break :blk link.Emit{
1397 .directory = bin_file_emit.?.directory,
1398 .sub_path = emit_implib.basename,
1399 };
1400 };
1401
13801402 var system_libs: std.StringArrayHashMapUnmanaged(SystemLib) = .{};
13811403 errdefer system_libs.deinit(gpa);
13821404 try system_libs.ensureTotalCapacity(gpa, options.system_lib_names.len);
......@@ -1386,6 +1408,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
13861408
13871409 const bin_file = try link.File.openPath(gpa, .{
13881410 .emit = bin_file_emit,
1411 .implib_emit = implib_emit,
13891412 .root_name = root_name,
13901413 .module = module,
13911414 .target = options.target,
......@@ -1461,7 +1484,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
14611484 .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os,
14621485 .disable_lld_caching = options.disable_lld_caching,
14631486 .subsystem = options.subsystem,
1464 .out_implib = options.out_implib,
14651487 .is_test = options.is_test,
14661488 .wasi_exec_model = wasi_exec_model,
14671489 .use_stage1 = use_stage1,
src/link.zig+2-1
......@@ -47,6 +47,8 @@ pub const Options = struct {
4747 /// This is `null` when -fno-emit-bin is used. When `openPath` or `flush` is called,
4848 /// it will have already been null-checked.
4949 emit: ?Emit,
50 /// This is `null` not building a Windows DLL, or when -fno-emit-implib is used.
51 implib_emit: ?Emit,
5052 target: std.Target,
5153 output_mode: std.builtin.OutputMode,
5254 link_mode: std.builtin.LinkMode,
......@@ -127,7 +129,6 @@ pub const Options = struct {
127129 gc_sections: ?bool = null,
128130 allow_shlib_undefined: ?bool,
129131 subsystem: ?std.Target.SubSystem,
130 out_implib: ?[]const u8,
131132 linker_script: ?[]const u8,
132133 version_script: ?[]const u8,
133134 soname: ?[]const u8,
src/link/Coff.zig+5-6
......@@ -947,7 +947,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
947947 man.hash.add(self.base.options.dynamicbase);
948948 man.hash.addOptional(self.base.options.major_subsystem_version);
949949 man.hash.addOptional(self.base.options.minor_subsystem_version);
950 man.hash.addOptionalBytes(self.base.options.out_implib);
951950
952951 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
953952 _ = try man.hit();
......@@ -978,7 +977,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
978977 }
979978
980979 const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
981
982980 if (self.base.options.output_mode == .Obj) {
983981 // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy
984982 // here. TODO: think carefully about how we can avoid this redundant operation when doing
......@@ -1070,6 +1068,11 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
10701068
10711069 try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
10721070
1071 if (self.base.options.implib_emit) |emit| {
1072 const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path});
1073 try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path}));
1074 }
1075
10731076 if (self.base.options.link_libc) {
10741077 if (self.base.options.libc_installation) |libc_installation| {
10751078 try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?}));
......@@ -1095,10 +1098,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
10951098 try argv.append(p);
10961099 }
10971100
1098 if (self.base.options.out_implib != null) {
1099 try argv.append(try allocPrint(arena, "-IMPLIB:{s}.lib", .{full_out_path}));
1100 }
1101
11021101 const resolved_subsystem: ?std.Target.SubSystem = blk: {
11031102 if (self.base.options.subsystem) |explicit| break :blk explicit;
11041103 switch (target.os.tag) {
src/main.zig+84-39
......@@ -317,6 +317,8 @@ const usage_build_generic =
317317 \\ -fno-emit-docs (default) Do not produce docs/ dir with html documentation
318318 \\ -femit-analysis[=path] Write analysis JSON file with type information
319319 \\ -fno-emit-analysis (default) Do not write analysis JSON file with type information
320 \\ -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL
321 \\ -fno-emit-implib Do not produce an import .lib when building a Windows DLL
320322 \\ --show-builtin Output the source of @import("builtin") then exit
321323 \\ --cache-dir [path] Override the local cache directory
322324 \\ --global-cache-dir [path] Override the global cache directory
......@@ -585,6 +587,8 @@ fn buildOutputType(
585587 var emit_llvm_bc: Emit = .no;
586588 var emit_docs: Emit = .no;
587589 var emit_analysis: Emit = .no;
590 var emit_implib: Emit = .yes_default_path;
591 var emit_implib_arg_provided = false;
588592 var target_arch_os_abi: []const u8 = "native";
589593 var target_mcpu: ?[]const u8 = null;
590594 var target_dynamic_linker: ?[]const u8 = null;
......@@ -654,7 +658,6 @@ fn buildOutputType(
654658 var main_pkg_path: ?[]const u8 = null;
655659 var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no;
656660 var subsystem: ?std.Target.SubSystem = null;
657 var out_implib: ?[]const u8 = null;
658661 var major_subsystem_version: ?u32 = null;
659662 var minor_subsystem_version: ?u32 = null;
660663 var wasi_exec_model: ?std.builtin.WasiExecModel = null;
......@@ -1091,6 +1094,15 @@ fn buildOutputType(
10911094 emit_analysis = .{ .yes = arg["-femit-analysis=".len..] };
10921095 } else if (mem.eql(u8, arg, "-fno-emit-analysis")) {
10931096 emit_analysis = .no;
1097 } else if (mem.eql(u8, arg, "-femit-implib")) {
1098 emit_implib = .yes_default_path;
1099 emit_implib_arg_provided = true;
1100 } else if (mem.startsWith(u8, arg, "-femit-implib=")) {
1101 emit_implib = .{ .yes = arg["-femit-implib=".len..] };
1102 emit_implib_arg_provided = true;
1103 } else if (mem.eql(u8, arg, "-fno-emit-implib")) {
1104 emit_implib = .no;
1105 emit_implib_arg_provided = true;
10941106 } else if (mem.eql(u8, arg, "-dynamic")) {
10951107 link_mode = .Dynamic;
10961108 } else if (mem.eql(u8, arg, "-static")) {
......@@ -1650,7 +1662,8 @@ fn buildOutputType(
16501662 if (i >= linker_args.items.len) {
16511663 fatal("expected linker arg after '{s}'", .{arg});
16521664 }
1653 out_implib = linker_args.items[i];
1665 emit_implib = .{ .yes = linker_args.items[i] };
1666 emit_implib_arg_provided = true;
16541667 } else {
16551668 warn("unsupported linker arg: {s}", .{arg});
16561669 }
......@@ -1998,11 +2011,15 @@ fn buildOutputType(
19982011 const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name});
19992012 var emit_h_resolved = emit_h.resolve(default_h_basename) catch |err| {
20002013 switch (emit_h) {
2001 .yes => {
2002 fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) });
2014 .yes => |p| {
2015 fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{
2016 p, @errorName(err),
2017 });
20032018 },
20042019 .yes_default_path => {
2005 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_h_basename, @errorName(err) });
2020 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
2021 default_h_basename, @errorName(err),
2022 });
20062023 },
20072024 .no => unreachable,
20082025 }
......@@ -2012,11 +2029,15 @@ fn buildOutputType(
20122029 const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name});
20132030 var emit_asm_resolved = emit_asm.resolve(default_asm_basename) catch |err| {
20142031 switch (emit_asm) {
2015 .yes => {
2016 fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) });
2032 .yes => |p| {
2033 fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{
2034 p, @errorName(err),
2035 });
20172036 },
20182037 .yes_default_path => {
2019 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_asm_basename, @errorName(err) });
2038 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
2039 default_asm_basename, @errorName(err),
2040 });
20202041 },
20212042 .no => unreachable,
20222043 }
......@@ -2026,11 +2047,15 @@ fn buildOutputType(
20262047 const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name});
20272048 var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename) catch |err| {
20282049 switch (emit_llvm_ir) {
2029 .yes => {
2030 fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) });
2050 .yes => |p| {
2051 fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{
2052 p, @errorName(err),
2053 });
20312054 },
20322055 .yes_default_path => {
2033 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) });
2056 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
2057 default_llvm_ir_basename, @errorName(err),
2058 });
20342059 },
20352060 .no => unreachable,
20362061 }
......@@ -2040,11 +2065,15 @@ fn buildOutputType(
20402065 const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name});
20412066 var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename) catch |err| {
20422067 switch (emit_llvm_bc) {
2043 .yes => {
2044 fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ emit_llvm_bc.yes, @errorName(err) });
2068 .yes => |p| {
2069 fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{
2070 p, @errorName(err),
2071 });
20452072 },
20462073 .yes_default_path => {
2047 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_bc_basename, @errorName(err) });
2074 fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
2075 default_llvm_bc_basename, @errorName(err),
2076 });
20482077 },
20492078 .no => unreachable,
20502079 }
......@@ -2054,11 +2083,15 @@ fn buildOutputType(
20542083 const default_analysis_basename = try std.fmt.allocPrint(arena, "{s}-analysis.json", .{root_name});
20552084 var emit_analysis_resolved = emit_analysis.resolve(default_analysis_basename) catch |err| {
20562085 switch (emit_analysis) {
2057 .yes => {
2058 fatal("unable to open directory from argument 'femit-analysis', '{s}': {s}", .{ emit_analysis.yes, @errorName(err) });
2086 .yes => |p| {
2087 fatal("unable to open directory from argument '-femit-analysis', '{s}': {s}", .{
2088 p, @errorName(err),
2089 });
20592090 },
20602091 .yes_default_path => {
2061 fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_analysis_basename, @errorName(err) });
2092 fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{
2093 default_analysis_basename, @errorName(err),
2094 });
20622095 },
20632096 .no => unreachable,
20642097 }
......@@ -2067,8 +2100,10 @@ fn buildOutputType(
20672100
20682101 var emit_docs_resolved = emit_docs.resolve("docs") catch |err| {
20692102 switch (emit_docs) {
2070 .yes => {
2071 fatal("unable to open directory from argument 'femit-docs', '{s}': {s}", .{ emit_h.yes, @errorName(err) });
2103 .yes => |p| {
2104 fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{
2105 p, @errorName(err),
2106 });
20722107 },
20732108 .yes_default_path => {
20742109 fatal("unable to open directory 'docs': {s}", .{@errorName(err)});
......@@ -2078,6 +2113,35 @@ fn buildOutputType(
20782113 };
20792114 defer emit_docs_resolved.deinit();
20802115
2116 const is_dyn_lib = switch (output_mode) {
2117 .Obj, .Exe => false,
2118 .Lib => (link_mode orelse .Static) == .Dynamic,
2119 };
2120 const implib_eligible = is_dyn_lib and
2121 emit_bin_loc != null and target_info.target.os.tag == .windows;
2122 if (!implib_eligible) {
2123 if (!emit_implib_arg_provided) {
2124 emit_implib = .no;
2125 } else if (emit_implib != .no) {
2126 fatal("the argument -femit-implib is allowed only when building a Windows DLL", .{});
2127 }
2128 }
2129 const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name});
2130 var emit_implib_resolved = emit_implib.resolve(default_implib_basename) catch |err| {
2131 switch (emit_implib) {
2132 .yes => |p| {
2133 fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{
2134 p, @errorName(err),
2135 });
2136 },
2137 .yes_default_path => {
2138 fatal("unable to open directory 'docs': {s}", .{@errorName(err)});
2139 },
2140 .no => unreachable,
2141 }
2142 };
2143 defer emit_implib_resolved.deinit();
2144
20812145 const main_pkg: ?*Package = if (root_src_file) |src_path| blk: {
20822146 if (main_pkg_path) |p| {
20832147 const rel_src_path = try fs.path.relative(gpa, p, src_path);
......@@ -2168,16 +2232,6 @@ fn buildOutputType(
21682232 else => false,
21692233 };
21702234
2171 // Always output import libraries (.lib) when building for msvc to replicate
2172 // `link` behavior. lld does not always output import libraries so on the
2173 // gnu abi users must set out_implib.
2174 if (output_mode == .Lib and emit_bin == .yes and target_info.target.abi == .msvc and out_implib == null) {
2175 const emit_bin_ext = fs.path.extension(emit_bin.yes);
2176 out_implib = try std.fmt.allocPrint(gpa, "{s}.lib", .{
2177 emit_bin.yes[0 .. emit_bin.yes.len - emit_bin_ext.len],
2178 });
2179 }
2180
21812235 gimmeMoreOfThoseSweetSweetFileDescriptors();
21822236
21832237 const comp = Compilation.create(gpa, .{
......@@ -2199,6 +2253,7 @@ fn buildOutputType(
21992253 .emit_llvm_bc = emit_llvm_bc_resolved.data,
22002254 .emit_docs = emit_docs_resolved.data,
22012255 .emit_analysis = emit_analysis_resolved.data,
2256 .emit_implib = emit_implib_resolved.data,
22022257 .link_mode = link_mode,
22032258 .dll_export_fns = dll_export_fns,
22042259 .object_format = object_format,
......@@ -2286,7 +2341,6 @@ fn buildOutputType(
22862341 .test_name_prefix = test_name_prefix,
22872342 .disable_lld_caching = !have_enable_cache,
22882343 .subsystem = subsystem,
2289 .out_implib = out_implib,
22902344 .wasi_exec_model = wasi_exec_model,
22912345 .debug_compile_errors = debug_compile_errors,
22922346 .enable_link_snapshots = enable_link_snapshots,
......@@ -2685,15 +2739,6 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
26852739
26862740 _ = try cache_dir.updateFile(src_pdb_path, cwd, dst_pdb_path, .{});
26872741 }
2688
2689 if (comp.bin_file.options.out_implib) |out_implib| {
2690 const src_implib_path = try std.fmt.allocPrint(gpa, "{s}.lib", .{bin_sub_path});
2691 defer gpa.free(src_implib_path);
2692 if (std.fs.path.dirname(out_implib)) |implib_dir| {
2693 try cwd.makePath(implib_dir);
2694 }
2695 _ = try cache_dir.updateFile(src_implib_path, cwd, out_implib, .{});
2696 }
26972742 },
26982743 }
26992744}