Skip to content

Commit b42442f

Browse files
committed
windows: fixes to support using zig cc/c++ with CMake on Windows
Using zig cc with CMake on Windows was failing during compiler detection. -nostdinc was causing the crt not to be linked, and Coff/lld.zig assumed that wWinMainCRTStartup would be present in this case. -nostdlib did not prevent the default behaviour of linking libc++ when zig c++ was used. This caused libc++ to be built when CMake ran ABI detection using zig c++, which fails as libcxxabi cannot compile under MSVC. - Change the behaviour of COFF -nostdinc to set /entry to the function that the default CRT method for the specified subsystem would have called. - Fix -ENTRY being passed twice if it was specified explicitly and -nostdlib was present. - Add support for /pdb, /version, /implib, and /subsystem as linker args (passed by CMake) - Remove -Ddisable-zstd, no longer needed - Add -Ddisable-libcpp for use when bootstrapping on msvc
1 parent a03c8ef commit b42442f

6 files changed

Lines changed: 72 additions & 12 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ set(ZIG_SHARED_LLVM off CACHE BOOL "Prefer linking against shared LLVM libraries
9292
set(ZIG_STATIC_LLVM off CACHE BOOL "Prefer linking against static LLVM libraries")
9393
set(ZIG_STATIC_ZLIB off CACHE BOOL "Prefer linking against static zlib")
9494
set(ZIG_ENABLE_ZSTD on CACHE BOOL "Enable linking zstd")
95+
set(ZIG_ENABLE_LIBCPP on CACHE BOOL "Enable linking libcpp")
9596
set(ZIG_STATIC_ZSTD off CACHE BOOL "Prefer linking against static zstd")
9697
set(ZIG_USE_CCACHE off CACHE BOOL "Use ccache")
9798

build.zig

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ pub fn build(b: *Builder) !void {
9999
const enable_macos_sdk = b.option(bool, "enable-macos-sdk", "Run tests requiring presence of macOS SDK and frameworks") orelse false;
100100
const enable_symlinks_windows = b.option(bool, "enable-symlinks-windows", "Run tests requiring presence of symlinks on Windows") orelse false;
101101
const config_h_path_option = b.option([]const u8, "config_h", "Path to the generated config.h");
102-
const disable_zstd = b.option(bool, "disable-zstd", "Skip linking zstd") orelse false;
102+
const disable_libcpp = b.option(bool, "disable-libcpp", "Skip building/linking libcpp") orelse false;
103103

104104
if (!skip_install_lib_files) {
105105
b.installDirectory(InstallDirectoryOptions{
@@ -278,8 +278,8 @@ pub fn build(b: *Builder) !void {
278278
try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx);
279279
} else {
280280
// Here we are -Denable-llvm but no cmake integration.
281-
try addStaticLlvmOptionsToExe(exe, !disable_zstd);
282-
try addStaticLlvmOptionsToExe(test_cases, !disable_zstd);
281+
try addStaticLlvmOptionsToExe(exe);
282+
try addStaticLlvmOptionsToExe(test_cases);
283283
}
284284
if (target.isWindows()) {
285285
inline for (.{ exe, test_cases }) |artifact| {
@@ -607,7 +607,7 @@ fn addCmakeCfgOptionsToExe(
607607
}
608608
}
609609

610-
fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep, link_zstd: bool) !void {
610+
fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep) !void {
611611
// Adds the Zig C++ sources which both stage1 and stage2 need.
612612
//
613613
// We need this because otherwise zig_clang_cc1_main.cpp ends up pulling
@@ -629,10 +629,7 @@ fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep, link_zstd: bool) !vo
629629
}
630630

631631
exe.linkSystemLibrary("z");
632-
633-
if (link_zstd) {
634-
exe.linkSystemLibrary("zstd");
635-
}
632+
exe.linkSystemLibrary("zstd");
636633

637634
if (exe.target.getOs().tag != .windows or exe.target.getAbi() != .msvc) {
638635
// This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries.

src/Compilation.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,11 @@ pub const InitOptions = struct {
10421042
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
10431043
dead_strip_dylibs: bool = false,
10441044
libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default,
1045+
/// (Windows) PDB source path prefix to instruct the linker how to resolve relative
1046+
/// paths when consolidating CodeView streams into a single PDB file.
1047+
pdb_source_path: ?[]const u8 = null,
1048+
/// (Windows) PDB output path
1049+
pdb_out_path: ?[]const u8 = null,
10451050
};
10461051

10471052
fn addPackageTableToCacheHash(
@@ -1892,6 +1897,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
18921897
.headerpad_max_install_names = options.headerpad_max_install_names,
18931898
.dead_strip_dylibs = options.dead_strip_dylibs,
18941899
.force_undefined_symbols = .{},
1900+
.pdb_source_path = pdb_source_path,
1901+
.pdb_out_path = options.pdb_out_path,
18951902
});
18961903
errdefer bin_file.destroy();
18971904
comp.* = .{

src/link.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ pub const Options = struct {
223223
/// paths when consolidating CodeView streams into a single PDB file.
224224
pdb_source_path: ?[]const u8 = null,
225225

226+
/// (Windows) PDB output path
227+
pdb_out_path: ?[]const u8 = null,
228+
226229
/// (Windows) .def file to specify when linking
227230
module_definition_file: ?[]const u8 = null,
228231

src/link/Coff/lld.zig

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,16 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
166166
try argv.append("-DEBUG");
167167

168168
const out_ext = std.fs.path.extension(full_out_path);
169-
const out_pdb = try allocPrint(arena, "{s}.pdb", .{
169+
const out_pdb = self.base.options.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{
170170
full_out_path[0 .. full_out_path.len - out_ext.len],
171171
});
172+
172173
try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb}));
173174
try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb}));
174175
}
176+
if (self.base.options.version) |version| {
177+
try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor }));
178+
}
175179
if (self.base.options.lto) {
176180
switch (self.base.options.optimize_mode) {
177181
.Debug => {},
@@ -427,15 +431,40 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
427431
}
428432
} else {
429433
try argv.append("-NODEFAULTLIB");
430-
if (!is_lib) {
434+
if (!is_lib and self.base.options.entry == null) {
431435
if (self.base.options.module) |module| {
432436
if (module.stage1_flags.have_winmain_crt_startup) {
433437
try argv.append("-ENTRY:WinMainCRTStartup");
434438
} else {
435439
try argv.append("-ENTRY:wWinMainCRTStartup");
436440
}
437441
} else {
438-
try argv.append("-ENTRY:wWinMainCRTStartup");
442+
// If the crt isn't being linked, it won't provide the CRT startup methods that
443+
// call through to the user-provided entrypoint. Instead, choose the entry point
444+
// that the CRT methods would have called. Note that this differs from the behaviour
445+
// of link.exe (which still tries to use the CRT methods in this case), but this
446+
// fixes CMake compiler checks when using zig cc on Windows, as Windows-Clang.cmake
447+
// does not specify /entry:main
448+
449+
// TODO: I think the correct thing to do in this case would be to inspect the object
450+
// being linked (like link.exe / lld-link does) and detect which symbols are available.
451+
// This would allow detection of the w variants, as well as the crt methods.
452+
if (resolved_subsystem) |subsystem| {
453+
switch (subsystem) {
454+
.Console => {
455+
// The default is to call mainCRTStartup/wmainCRTStartup, which calls main/wmain
456+
try argv.append("-ENTRY:main");
457+
},
458+
.Windows => {
459+
// The default is to call WinMainCRTStartup/wWinMainCRTStartup, which calls WinMain/wWinMain
460+
try argv.append("-ENTRY:WinMain");
461+
},
462+
else => {}
463+
}
464+
}
465+
466+
// when no /entry is specified, lld-link will infer it based on which functions
467+
// are present in the object being linked - see lld/COFF/Driver.cpp#LinkerDriver::findDefaultEntry
439468
}
440469
}
441470
}

src/main.zig

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ fn buildOutputType(
782782
var headerpad_max_install_names: bool = false;
783783
var dead_strip_dylibs: bool = false;
784784
var reference_trace: ?u32 = null;
785+
var pdb_out_path: ?[]const u8 = null;
785786

786787
// e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
787788
// This array is populated by zig cc frontend and then has to be converted to zig-style
@@ -1541,7 +1542,10 @@ fn buildOutputType(
15411542
.no_stack_protector => want_stack_protector = 0,
15421543
.unwind_tables => want_unwind_tables = true,
15431544
.no_unwind_tables => want_unwind_tables = false,
1544-
.nostdlib => ensure_libc_on_non_freestanding = false,
1545+
.nostdlib => {
1546+
ensure_libc_on_non_freestanding = false;
1547+
ensure_libcpp_on_non_freestanding = false;
1548+
},
15451549
.nostdlib_cpp => ensure_libcpp_on_non_freestanding = false,
15461550
.shared => {
15471551
link_mode = .Dynamic;
@@ -2120,6 +2124,24 @@ fn buildOutputType(
21202124
next_arg,
21212125
});
21222126
};
2127+
} else if (mem.startsWith(u8, arg, "/subsystem:")) {
2128+
var split_it = mem.splitBackwards(u8, arg, ":");
2129+
subsystem = try parseSubSystem(split_it.first());
2130+
} else if (mem.startsWith(u8, arg, "/implib:")) {
2131+
var split_it = mem.splitBackwards(u8, arg, ":");
2132+
emit_implib = .{ .yes = split_it.first() };
2133+
emit_implib_arg_provided = true;
2134+
} else if (mem.startsWith(u8, arg, "/pdb:")) {
2135+
var split_it = mem.splitBackwards(u8, arg, ":");
2136+
pdb_out_path = split_it.first();
2137+
} else if (mem.startsWith(u8, arg, "/version:")) {
2138+
var split_it = mem.splitBackwards(u8, arg, ":");
2139+
const version_arg = split_it.first();
2140+
version = std.builtin.Version.parse(version_arg) catch |err| {
2141+
fatal("unable to parse /version '{s}': {s}", .{ arg, @errorName(err) });
2142+
};
2143+
2144+
have_version = true;
21232145
} else {
21242146
warn("unsupported linker arg: {s}", .{arg});
21252147
}
@@ -3069,6 +3091,7 @@ fn buildOutputType(
30693091
.headerpad_max_install_names = headerpad_max_install_names,
30703092
.dead_strip_dylibs = dead_strip_dylibs,
30713093
.reference_trace = reference_trace,
3094+
.pdb_out_path = pdb_out_path,
30723095
}) catch |err| switch (err) {
30733096
error.LibCUnavailable => {
30743097
const target = target_info.target;

0 commit comments

Comments
 (0)