Skip to content

Commit 0168ed7

Browse files
committed
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.
1 parent 4fac7a5 commit 0168ed7

File tree

3 files changed

+106
-9
lines changed

3 files changed

+106
-9
lines changed

lib/std/Build/Step/Compile.zig

+13
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ is_linking_libc: bool,
9090
is_linking_libcpp: bool,
9191
vcpkg_bin_path: ?[]const u8 = null,
9292

93+
// keep in sync with src/Compilation.zig:RcIncludes
94+
/// Behavior of automatic detection of include directories when compiling .rc files.
95+
/// any: Use MSVC if available, fall back to MinGW.
96+
/// msvc: Use MSVC include paths (must be present on the system).
97+
/// gnu: Use MinGW include paths (distributed with Zig).
98+
/// none: Do not use any autodetected include paths.
99+
rc_includes: enum { any, msvc, gnu, none } = .any,
100+
93101
installed_path: ?[]const u8,
94102

95103
/// Base address for an executable image.
@@ -1949,6 +1957,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
19491957
}
19501958
}
19511959

1960+
if (self.rc_includes != .any) {
1961+
try zig_args.append("-rcincludes");
1962+
try zig_args.append(@tagName(self.rc_includes));
1963+
}
1964+
19521965
try addFlag(&zig_args, "valgrind", self.valgrind_support);
19531966
try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath);
19541967

src/Compilation.zig

+77-9
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,17 @@ pub const RcSourceFile = struct {
243243
extra_flags: []const []const u8 = &.{},
244244
};
245245

246+
pub const RcIncludes = enum {
247+
/// Use MSVC if available, fall back to MinGW.
248+
any,
249+
/// Use MSVC include paths (MSVC install + Windows SDK, must be present on the system).
250+
msvc,
251+
/// Use MinGW include paths (distributed with Zig).
252+
gnu,
253+
/// Do not use any autodetected include paths.
254+
none,
255+
};
256+
246257
const Job = union(enum) {
247258
/// Write the constant value for a Decl to the output file.
248259
codegen_decl: Module.Decl.Index,
@@ -568,6 +579,7 @@ pub const InitOptions = struct {
568579
symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{},
569580
c_source_files: []const CSourceFile = &[0]CSourceFile{},
570581
rc_source_files: []const RcSourceFile = &[0]RcSourceFile{},
582+
rc_includes: RcIncludes = .any,
571583
link_objects: []LinkObject = &[0]LinkObject{},
572584
framework_dirs: []const []const u8 = &[0][]const u8{},
573585
frameworks: []const Framework = &.{},
@@ -1001,16 +1013,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
10011013
options.libc_installation,
10021014
);
10031015

1004-
const rc_dirs = try detectLibCIncludeDirs(
1016+
const rc_dirs = try detectWin32ResourceIncludeDirs(
10051017
arena,
1006-
options.zig_lib_directory.path.?,
1007-
options.target,
1008-
options.is_native_abi,
1009-
// Set "link libc" to true here whenever there are rc files to compile, since
1010-
// the .rc preprocessor will need to know the libc include dirs even if we
1011-
// are not linking libc
1012-
options.rc_source_files.len > 0,
1013-
options.libc_installation,
1018+
options,
10141019
);
10151020

10161021
const sysroot = options.sysroot orelse libc_dirs.sysroot;
@@ -2450,6 +2455,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24502455
man.hash.addListOfBytes(key.src.extra_flags);
24512456
}
24522457

2458+
man.hash.addListOfBytes(comp.rc_include_dir_list);
2459+
24532460
cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm);
24542461
cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir);
24552462
cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc);
@@ -5156,6 +5163,67 @@ fn failCObjWithOwnedErrorMsg(
51565163
return error.AnalysisFail;
51575164
}
51585165

5166+
/// The include directories used when preprocessing .rc files are separate from the
5167+
/// target. Which include directories are used is determined by `options.rc_includes`.
5168+
///
5169+
/// Note: It should be okay that the include directories used when compiling .rc
5170+
/// files differ from the include directories used when compiling the main
5171+
/// binary, since the .res format is not dependent on anything ABI-related. The
5172+
/// only relevant differences would be things like `#define` constants being
5173+
/// different in the MinGW headers vs the MSVC headers, but any such
5174+
/// differences would likely be a MinGW bug.
5175+
fn detectWin32ResourceIncludeDirs(arena: Allocator, options: InitOptions) !LibCDirs {
5176+
// Set the includes to .none here when there are no rc files to compile
5177+
var includes = if (options.rc_source_files.len > 0) options.rc_includes else .none;
5178+
if (builtin.target.os.tag != .windows) {
5179+
switch (includes) {
5180+
// MSVC can't be found when the host isn't Windows, so short-circuit.
5181+
.msvc => return error.WindowsSdkNotFound,
5182+
// Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts.
5183+
.any => includes = .gnu,
5184+
.none, .gnu => {},
5185+
}
5186+
}
5187+
while (true) {
5188+
switch (includes) {
5189+
.any, .msvc => return detectLibCIncludeDirs(
5190+
arena,
5191+
options.zig_lib_directory.path.?,
5192+
.{
5193+
.cpu = options.target.cpu,
5194+
.os = options.target.os,
5195+
.abi = .msvc,
5196+
.ofmt = options.target.ofmt,
5197+
},
5198+
options.is_native_abi,
5199+
// The .rc preprocessor will need to know the libc include dirs even if we
5200+
// are not linking libc, so force 'link_libc' to true
5201+
true,
5202+
options.libc_installation,
5203+
) catch |err| {
5204+
if (includes == .any) {
5205+
// fall back to mingw
5206+
includes = .gnu;
5207+
continue;
5208+
}
5209+
return err;
5210+
},
5211+
.gnu => return detectLibCFromBuilding(arena, options.zig_lib_directory.path.?, .{
5212+
.cpu = options.target.cpu,
5213+
.os = options.target.os,
5214+
.abi = .gnu,
5215+
.ofmt = options.target.ofmt,
5216+
}),
5217+
.none => return LibCDirs{
5218+
.libc_include_dir_list = &[0][]u8{},
5219+
.libc_installation = null,
5220+
.libc_framework_dir_list = &.{},
5221+
.sysroot = null,
5222+
},
5223+
}
5224+
}
5225+
}
5226+
51595227
fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) SemaError {
51605228
@setCold(true);
51615229
var bundle: ErrorBundle.Wip = undefined;

src/main.zig

+16
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,11 @@ const usage_build_generic =
473473
\\ --libc [file] Provide a file which specifies libc paths
474474
\\ -cflags [flags] -- Set extra flags for the next positional C source files
475475
\\ -rcflags [flags] -- Set extra flags for the next positional .rc source files
476+
\\ -rcincludes=[type] Set the type of includes to use when compiling .rc source files
477+
\\ any (default) Use msvc if available, fall back to gnu
478+
\\ msvc Use msvc include paths (must be present on the system)
479+
\\ gnu Use mingw include paths (distributed with Zig)
480+
\\ none Do not use any autodetected include paths
476481
\\
477482
\\Link Options:
478483
\\ -l[lib], --library [lib] Link against system library (only if actually used)
@@ -927,6 +932,7 @@ fn buildOutputType(
927932
var symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{};
928933
var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena);
929934
var rc_source_files = std.ArrayList(Compilation.RcSourceFile).init(arena);
935+
var rc_includes: Compilation.RcIncludes = .any;
930936
var res_files = std.ArrayList(Compilation.LinkObject).init(arena);
931937
var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
932938
var framework_dirs = std.ArrayList([]const u8).init(arena);
@@ -1046,6 +1052,10 @@ fn buildOutputType(
10461052
if (mem.eql(u8, next_arg, "--")) break;
10471053
try extra_cflags.append(next_arg);
10481054
}
1055+
} else if (mem.eql(u8, arg, "-rcincludes")) {
1056+
rc_includes = parseRcIncludes(args_iter.nextOrFatal());
1057+
} else if (mem.startsWith(u8, arg, "-rcincludes=")) {
1058+
rc_includes = parseRcIncludes(arg["-rcincludes=".len..]);
10491059
} else if (mem.eql(u8, arg, "-rcflags")) {
10501060
extra_rcflags.shrinkRetainingCapacity(0);
10511061
while (true) {
@@ -3369,6 +3379,7 @@ fn buildOutputType(
33693379
.symbol_wrap_set = symbol_wrap_set,
33703380
.c_source_files = c_source_files.items,
33713381
.rc_source_files = rc_source_files.items,
3382+
.rc_includes = rc_includes,
33723383
.link_objects = link_objects.items,
33733384
.framework_dirs = framework_dirs.items,
33743385
.frameworks = resolved_frameworks.items,
@@ -6532,3 +6543,8 @@ fn accessFrameworkPath(
65326543

65336544
return false;
65346545
}
6546+
6547+
fn parseRcIncludes(arg: []const u8) Compilation.RcIncludes {
6548+
return std.meta.stringToEnum(Compilation.RcIncludes, arg) orelse
6549+
fatal("unsupported rc includes type: '{s}'", .{arg});
6550+
}

0 commit comments

Comments
 (0)