From 0168ed7bf1c7fc5010fa82eaf33ed1b3af817709 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 11 Sep 2023 23:05:48 -0700 Subject: rc compilation: Use MSVC includes if present, fallback to mingw The include directories used when preprocessing .rc files are now separate from the target, and by default will use the system MSVC include paths if the MSVC + Windows SDK are present, otherwise it will fall back to the MinGW includes distributed with Zig. This default behavior can be overridden by the `-rcincludes` option (possible values: any (the default), msvc, gnu, or none). This behavior is useful because Windows resource files may `#include` files that only exist with in the MSVC include dirs (e.g. in `/atlmfc/include` which can contain other .rc files, images, icons, cursors, etc). So, by defaulting to the `any` behavior (MSVC if present, MinGW fallback), users will by default get behavior that is most-likely-to-work. It also should be okay that the include directories used when compiling .rc files differ from the include directories used when compiling the main binary, since the .res format is not dependent on anything ABI-related. The only relevant differences would be things like `#define` constants being different values in the MinGW headers vs the MSVC headers, but any such differences would likely be a MinGW bug. --- src/Compilation.zig | 86 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 9 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 17d298b66d..6a078e8581 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -243,6 +243,17 @@ pub const RcSourceFile = struct { extra_flags: []const []const u8 = &.{}, }; +pub const RcIncludes = enum { + /// Use MSVC if available, fall back to MinGW. + any, + /// Use MSVC include paths (MSVC install + Windows SDK, must be present on the system). + msvc, + /// Use MinGW include paths (distributed with Zig). + gnu, + /// Do not use any autodetected include paths. + none, +}; + const Job = union(enum) { /// Write the constant value for a Decl to the output file. codegen_decl: Module.Decl.Index, @@ -568,6 +579,7 @@ pub const InitOptions = struct { symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{}, c_source_files: []const CSourceFile = &[0]CSourceFile{}, rc_source_files: []const RcSourceFile = &[0]RcSourceFile{}, + rc_includes: RcIncludes = .any, link_objects: []LinkObject = &[0]LinkObject{}, framework_dirs: []const []const u8 = &[0][]const u8{}, frameworks: []const Framework = &.{}, @@ -1001,16 +1013,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { options.libc_installation, ); - const rc_dirs = try detectLibCIncludeDirs( + const rc_dirs = try detectWin32ResourceIncludeDirs( arena, - options.zig_lib_directory.path.?, - options.target, - options.is_native_abi, - // Set "link libc" to true here whenever there are rc files to compile, since - // the .rc preprocessor will need to know the libc include dirs even if we - // are not linking libc - options.rc_source_files.len > 0, - options.libc_installation, + options, ); const sysroot = options.sysroot orelse libc_dirs.sysroot; @@ -2450,6 +2455,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.addListOfBytes(key.src.extra_flags); } + man.hash.addListOfBytes(comp.rc_include_dir_list); + cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc); @@ -5156,6 +5163,67 @@ fn failCObjWithOwnedErrorMsg( return error.AnalysisFail; } +/// The include directories used when preprocessing .rc files are separate from the +/// target. Which include directories are used is determined by `options.rc_includes`. +/// +/// Note: It should be okay that the include directories used when compiling .rc +/// files differ from the include directories used when compiling the main +/// binary, since the .res format is not dependent on anything ABI-related. The +/// only relevant differences would be things like `#define` constants being +/// different in the MinGW headers vs the MSVC headers, but any such +/// differences would likely be a MinGW bug. +fn detectWin32ResourceIncludeDirs(arena: Allocator, options: InitOptions) !LibCDirs { + // Set the includes to .none here when there are no rc files to compile + var includes = if (options.rc_source_files.len > 0) options.rc_includes else .none; + if (builtin.target.os.tag != .windows) { + switch (includes) { + // MSVC can't be found when the host isn't Windows, so short-circuit. + .msvc => return error.WindowsSdkNotFound, + // Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts. + .any => includes = .gnu, + .none, .gnu => {}, + } + } + while (true) { + switch (includes) { + .any, .msvc => return detectLibCIncludeDirs( + arena, + options.zig_lib_directory.path.?, + .{ + .cpu = options.target.cpu, + .os = options.target.os, + .abi = .msvc, + .ofmt = options.target.ofmt, + }, + options.is_native_abi, + // The .rc preprocessor will need to know the libc include dirs even if we + // are not linking libc, so force 'link_libc' to true + true, + options.libc_installation, + ) catch |err| { + if (includes == .any) { + // fall back to mingw + includes = .gnu; + continue; + } + return err; + }, + .gnu => return detectLibCFromBuilding(arena, options.zig_lib_directory.path.?, .{ + .cpu = options.target.cpu, + .os = options.target.os, + .abi = .gnu, + .ofmt = options.target.ofmt, + }), + .none => return LibCDirs{ + .libc_include_dir_list = &[0][]u8{}, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + }, + } + } +} + fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) SemaError { @setCold(true); var bundle: ErrorBundle.Wip = undefined; -- cgit v1.2.3