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 `<MSVC install directory>/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.
This commit is contained in:
Ryan Liptak
2023-09-11 23:05:48 -07:00
parent 4fac7a5263
commit 0168ed7bf1
3 changed files with 106 additions and 9 deletions

View File

@@ -90,6 +90,14 @@ is_linking_libc: bool,
is_linking_libcpp: bool,
vcpkg_bin_path: ?[]const u8 = null,
// keep in sync with src/Compilation.zig:RcIncludes
/// Behavior of automatic detection of include directories when compiling .rc files.
/// any: Use MSVC if available, fall back to MinGW.
/// msvc: Use MSVC include paths (must be present on the system).
/// gnu: Use MinGW include paths (distributed with Zig).
/// none: Do not use any autodetected include paths.
rc_includes: enum { any, msvc, gnu, none } = .any,
installed_path: ?[]const u8,
/// Base address for an executable image.
@@ -1949,6 +1957,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
}
}
if (self.rc_includes != .any) {
try zig_args.append("-rcincludes");
try zig_args.append(@tagName(self.rc_includes));
}
try addFlag(&zig_args, "valgrind", self.valgrind_support);
try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath);

View File

@@ -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;

View File

@@ -473,6 +473,11 @@ const usage_build_generic =
\\ --libc [file] Provide a file which specifies libc paths
\\ -cflags [flags] -- Set extra flags for the next positional C source files
\\ -rcflags [flags] -- Set extra flags for the next positional .rc source files
\\ -rcincludes=[type] Set the type of includes to use when compiling .rc source files
\\ any (default) Use msvc if available, fall back to gnu
\\ msvc Use msvc include paths (must be present on the system)
\\ gnu Use mingw include paths (distributed with Zig)
\\ none Do not use any autodetected include paths
\\
\\Link Options:
\\ -l[lib], --library [lib] Link against system library (only if actually used)
@@ -927,6 +932,7 @@ fn buildOutputType(
var symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{};
var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena);
var rc_source_files = std.ArrayList(Compilation.RcSourceFile).init(arena);
var rc_includes: Compilation.RcIncludes = .any;
var res_files = std.ArrayList(Compilation.LinkObject).init(arena);
var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
var framework_dirs = std.ArrayList([]const u8).init(arena);
@@ -1046,6 +1052,10 @@ fn buildOutputType(
if (mem.eql(u8, next_arg, "--")) break;
try extra_cflags.append(next_arg);
}
} else if (mem.eql(u8, arg, "-rcincludes")) {
rc_includes = parseRcIncludes(args_iter.nextOrFatal());
} else if (mem.startsWith(u8, arg, "-rcincludes=")) {
rc_includes = parseRcIncludes(arg["-rcincludes=".len..]);
} else if (mem.eql(u8, arg, "-rcflags")) {
extra_rcflags.shrinkRetainingCapacity(0);
while (true) {
@@ -3369,6 +3379,7 @@ fn buildOutputType(
.symbol_wrap_set = symbol_wrap_set,
.c_source_files = c_source_files.items,
.rc_source_files = rc_source_files.items,
.rc_includes = rc_includes,
.link_objects = link_objects.items,
.framework_dirs = framework_dirs.items,
.frameworks = resolved_frameworks.items,
@@ -6532,3 +6543,8 @@ fn accessFrameworkPath(
return false;
}
fn parseRcIncludes(arg: []const u8) Compilation.RcIncludes {
return std.meta.stringToEnum(Compilation.RcIncludes, arg) orelse
fatal("unsupported rc includes type: '{s}'", .{arg});
}