authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-10-09 04:06:28-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-10-15 13:33:16-07:00
log5f15acc463d39baedd8de367330286b91c8bafc8
tree9b9afbdb5b05090665c1b9b596f874c4fa8dd74c
parentb0f031f5730be496872591dce3242004cf9a6f22

Add preliminary support for Windows .manifest files

An embedded manifest file is really just XML data embedded as a RT_MANIFEST resource (ID = 24). Typically, the Windows-only 'Manifest Tool' (`mt.exe`) is used to embed manifest files, and `mt.exe` also seems to perform some transformation of the manifest data before embedding, but in testing it doesn't seem like the transformations are necessary to get the intended result. So, to handle embedding manifest files, Zig now takes the following approach: - Generate a .rc file with the contents `1 24 "path-to-manifest.manifest"` - Compile that generated .rc file into a .res file - Link the .res file into the final binary This effectively achieves the same thing as `mt.exe` minus the validation/transformations of the XML data that it performs. How this is used: On the command line: ``` zig build-exe main.zig main.manifest ``` (on the command line, specifying a .manifest file when the target object format is not COFF is an error) or in build.zig: ``` const exe = b.addExecutable(.{ .name = "manifest-test", .root_source_file = .{ .path = "main.zig" }, .target = target, .optimize = optimize, .win32_manifest = .{ .path = "main.manifest" }, }); ``` (in build.zig, the manifest file is ignored if the target object format is not COFF) Note: Currently, only one manifest file can be specified per compilation. This is because the ID of the manifest resource is currently always 1. Specifying multiple manifests could be supported if a way for the user to specify an ID for each manifest is added (manifest IDs must be a u16). Closes #17406 options

4 files changed, 228 insertions(+), 32 deletions(-)

lib/std/Build.zig+14
...@@ -635,6 +635,12 @@ pub const ExecutableOptions = struct {...@@ -635,6 +635,12 @@ pub const ExecutableOptions = struct {
635 use_lld: ?bool = null,635 use_lld: ?bool = null,
636 zig_lib_dir: ?LazyPath = null,636 zig_lib_dir: ?LazyPath = null,
637 main_mod_path: ?LazyPath = null,637 main_mod_path: ?LazyPath = null,
638 /// Embed a `.manifest` file in the compilation if the object format supports it.
639 /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
640 /// Manifest files must have the extension `.manifest`.
641 /// Can be set regardless of target. The `.manifest` file will be ignored
642 /// if the target object format does not support embedded manifests.
643 win32_manifest: ?LazyPath = null,
638644
639 /// Deprecated; use `main_mod_path`.645 /// Deprecated; use `main_mod_path`.
640 main_pkg_path: ?LazyPath = null,646 main_pkg_path: ?LazyPath = null,
...@@ -656,6 +662,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {...@@ -656,6 +662,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
656 .use_lld = options.use_lld,662 .use_lld = options.use_lld,
657 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,663 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
658 .main_mod_path = options.main_mod_path orelse options.main_pkg_path,664 .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
665 .win32_manifest = options.win32_manifest,
659 });666 });
660}667}
661668
...@@ -706,6 +713,12 @@ pub const SharedLibraryOptions = struct {...@@ -706,6 +713,12 @@ pub const SharedLibraryOptions = struct {
706 use_lld: ?bool = null,713 use_lld: ?bool = null,
707 zig_lib_dir: ?LazyPath = null,714 zig_lib_dir: ?LazyPath = null,
708 main_mod_path: ?LazyPath = null,715 main_mod_path: ?LazyPath = null,
716 /// Embed a `.manifest` file in the compilation if the object format supports it.
717 /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
718 /// Manifest files must have the extension `.manifest`.
719 /// Can be set regardless of target. The `.manifest` file will be ignored
720 /// if the target object format does not support embedded manifests.
721 win32_manifest: ?LazyPath = null,
709722
710 /// Deprecated; use `main_mod_path`.723 /// Deprecated; use `main_mod_path`.
711 main_pkg_path: ?LazyPath = null,724 main_pkg_path: ?LazyPath = null,
...@@ -727,6 +740,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile...@@ -727,6 +740,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile
727 .use_lld = options.use_lld,740 .use_lld = options.use_lld,
728 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,741 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
729 .main_mod_path = options.main_mod_path orelse options.main_pkg_path,742 .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
743 .win32_manifest = options.win32_manifest,
730 });744 });
731}745}
732746
lib/std/Build/Step/Compile.zig+26
...@@ -98,6 +98,10 @@ vcpkg_bin_path: ?[]const u8 = null,...@@ -98,6 +98,10 @@ vcpkg_bin_path: ?[]const u8 = null,
98/// none: Do not use any autodetected include paths.98/// none: Do not use any autodetected include paths.
99rc_includes: enum { any, msvc, gnu, none } = .any,99rc_includes: enum { any, msvc, gnu, none } = .any,
100100
101/// (Windows) .manifest file to embed in the compilation
102/// Set via options; intended to be read-only after that.
103win32_manifest: ?LazyPath = null,
104
101installed_path: ?[]const u8,105installed_path: ?[]const u8,
102106
103/// Base address for an executable image.107/// Base address for an executable image.
...@@ -319,6 +323,12 @@ pub const Options = struct {...@@ -319,6 +323,12 @@ pub const Options = struct {
319 use_lld: ?bool = null,323 use_lld: ?bool = null,
320 zig_lib_dir: ?LazyPath = null,324 zig_lib_dir: ?LazyPath = null,
321 main_mod_path: ?LazyPath = null,325 main_mod_path: ?LazyPath = null,
326 /// Embed a `.manifest` file in the compilation if the object format supports it.
327 /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
328 /// Manifest files must have the extension `.manifest`.
329 /// Can be set regardless of target. The `.manifest` file will be ignored
330 /// if the target object format does not support embedded manifests.
331 win32_manifest: ?LazyPath = null,
322332
323 /// deprecated; use `main_mod_path`.333 /// deprecated; use `main_mod_path`.
324 main_pkg_path: ?LazyPath = null,334 main_pkg_path: ?LazyPath = null,
...@@ -525,6 +535,15 @@ pub fn create(owner: *std.Build, options: Options) *Compile {...@@ -525,6 +535,15 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
525 lp.addStepDependencies(&self.step);535 lp.addStepDependencies(&self.step);
526 }536 }
527537
538 // Only the PE/COFF format has a Resource Table which is where the manifest
539 // gets embedded, so for any other target the manifest file is just ignored.
540 if (self.target.getObjectFormat() == .coff) {
541 if (options.win32_manifest) |lp| {
542 self.win32_manifest = lp.dupe(self.step.owner);
543 lp.addStepDependencies(&self.step);
544 }
545 }
546
528 if (self.kind == .lib) {547 if (self.kind == .lib) {
529 if (self.linkage != null and self.linkage.? == .static) {548 if (self.linkage != null and self.linkage.? == .static) {
530 self.out_lib_filename = self.out_filename;549 self.out_lib_filename = self.out_filename;
...@@ -957,6 +976,9 @@ pub fn addCSourceFile(self: *Compile, source: CSourceFile) void {...@@ -957,6 +976,9 @@ pub fn addCSourceFile(self: *Compile, source: CSourceFile) void {
957 source.file.addStepDependencies(&self.step);976 source.file.addStepDependencies(&self.step);
958}977}
959978
979/// Resource files must have the extension `.rc`.
980/// Can be called regardless of target. The .rc file will be ignored
981/// if the target object format does not support embedded resources.
960pub fn addWin32ResourceFile(self: *Compile, source: RcSourceFile) void {982pub fn addWin32ResourceFile(self: *Compile, source: RcSourceFile) void {
961 // Only the PE/COFF format has a Resource Table, so for any other target983 // Only the PE/COFF format has a Resource Table, so for any other target
962 // the resource file is just ignored.984 // the resource file is just ignored.
...@@ -1593,6 +1615,10 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -1593,6 +1615,10 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1593 }1615 }
1594 }1616 }
15951617
1618 if (self.win32_manifest) |manifest_file| {
1619 try zig_args.append(manifest_file.getPath(b));
1620 }
1621
1596 if (transitive_deps.is_linking_libcpp) {1622 if (transitive_deps.is_linking_libcpp) {
1597 try zig_args.append("-lc++");1623 try zig_args.append("-lc++");
1598 }1624 }
src/Compilation.zig+173-32
...@@ -358,7 +358,10 @@ pub const CObject = struct {...@@ -358,7 +358,10 @@ pub const CObject = struct {
358358
359pub const Win32Resource = struct {359pub const Win32Resource = struct {
360 /// Relative to cwd. Owned by arena.360 /// Relative to cwd. Owned by arena.
361 src: RcSourceFile,361 src: union(enum) {
362 rc: RcSourceFile,
363 manifest: []const u8,
364 },
362 status: union(enum) {365 status: union(enum) {
363 new,366 new,
364 success: struct {367 success: struct {
...@@ -582,6 +585,7 @@ pub const InitOptions = struct {...@@ -582,6 +585,7 @@ pub const InitOptions = struct {
582 symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{},585 symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{},
583 c_source_files: []const CSourceFile = &[0]CSourceFile{},586 c_source_files: []const CSourceFile = &[0]CSourceFile{},
584 rc_source_files: []const RcSourceFile = &[0]RcSourceFile{},587 rc_source_files: []const RcSourceFile = &[0]RcSourceFile{},
588 manifest_file: ?[]const u8 = null,
585 rc_includes: RcIncludes = .any,589 rc_includes: RcIncludes = .any,
586 link_objects: []LinkObject = &[0]LinkObject{},590 link_objects: []LinkObject = &[0]LinkObject{},
587 framework_dirs: []const []const u8 = &[0][]const u8{},591 framework_dirs: []const []const u8 = &[0][]const u8{},
...@@ -1749,16 +1753,26 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1749,16 +1753,26 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1749 comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});1753 comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});
1750 }1754 }
17511755
1752 // Add a `Win32Resource` for each `rc_source_files`.1756 // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`.
1753 if (!build_options.only_core_functionality) {1757 if (!build_options.only_core_functionality) {
1754 try comp.win32_resource_table.ensureTotalCapacity(gpa, options.rc_source_files.len);1758 try comp.win32_resource_table.ensureTotalCapacity(gpa, options.rc_source_files.len + @intFromBool(options.manifest_file != null));
1755 for (options.rc_source_files) |rc_source_file| {1759 for (options.rc_source_files) |rc_source_file| {
1756 const win32_resource = try gpa.create(Win32Resource);1760 const win32_resource = try gpa.create(Win32Resource);
1757 errdefer gpa.destroy(win32_resource);1761 errdefer gpa.destroy(win32_resource);
17581762
1759 win32_resource.* = .{1763 win32_resource.* = .{
1760 .status = .{ .new = {} },1764 .status = .{ .new = {} },
1761 .src = rc_source_file,1765 .src = .{ .rc = rc_source_file },
1766 };
1767 comp.win32_resource_table.putAssumeCapacityNoClobber(win32_resource, {});
1768 }
1769 if (options.manifest_file) |manifest_path| {
1770 const win32_resource = try gpa.create(Win32Resource);
1771 errdefer gpa.destroy(win32_resource);
1772
1773 win32_resource.* = .{
1774 .status = .{ .new = {} },
1775 .src = .{ .manifest = manifest_path },
1762 };1776 };
1763 comp.win32_resource_table.putAssumeCapacityNoClobber(win32_resource, {});1777 comp.win32_resource_table.putAssumeCapacityNoClobber(win32_resource, {});
1764 }1778 }
...@@ -2477,8 +2491,15 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2477,8 +2491,15 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24772491
2478 if (!build_options.only_core_functionality) {2492 if (!build_options.only_core_functionality) {
2479 for (comp.win32_resource_table.keys()) |key| {2493 for (comp.win32_resource_table.keys()) |key| {
2480 _ = try man.addFile(key.src.src_path, null);2494 switch (key.src) {
2481 man.hash.addListOfBytes(key.src.extra_flags);2495 .rc => |rc_src| {
2496 _ = try man.addFile(rc_src.src_path, null);
2497 man.hash.addListOfBytes(rc_src.extra_flags);
2498 },
2499 .manifest => |manifest_path| {
2500 _ = try man.addFile(manifest_path, null);
2501 },
2502 }
2482 }2503 }
2483 }2504 }
24842505
...@@ -4172,7 +4193,10 @@ fn reportRetryableWin32ResourceError(...@@ -4172,7 +4193,10 @@ fn reportRetryableWin32ResourceError(
4172 try bundle.addRootErrorMessage(.{4193 try bundle.addRootErrorMessage(.{
4173 .msg = try bundle.printString("{s}", .{@errorName(err)}),4194 .msg = try bundle.printString("{s}", .{@errorName(err)}),
4174 .src_loc = try bundle.addSourceLocation(.{4195 .src_loc = try bundle.addSourceLocation(.{
4175 .src_path = try bundle.addString(win32_resource.src.src_path),4196 .src_path = try bundle.addString(switch (win32_resource.src) {
4197 .rc => |rc_src| rc_src.src_path,
4198 .manifest => |manifest_src| manifest_src,
4199 }),
4176 .line = 0,4200 .line = 0,
4177 .column = 0,4201 .column = 0,
4178 .span_start = 0,4202 .span_start = 0,
...@@ -4542,7 +4566,17 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4542,7 +4566,17 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
4542 const tracy_trace = trace(@src());4566 const tracy_trace = trace(@src());
4543 defer tracy_trace.end();4567 defer tracy_trace.end();
45444568
4545 log.debug("updating win32 resource: {s}", .{win32_resource.src.src_path});4569 const src_path = switch (win32_resource.src) {
4570 .rc => |rc_src| rc_src.src_path,
4571 .manifest => |src_path| src_path,
4572 };
4573 const src_basename = std.fs.path.basename(src_path);
4574
4575 log.debug("updating win32 resource: {s}", .{src_path});
4576
4577 var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
4578 defer arena_allocator.deinit();
4579 const arena = arena_allocator.allocator();
45464580
4547 if (win32_resource.clearStatus(comp.gpa)) {4581 if (win32_resource.clearStatus(comp.gpa)) {
4548 // There was previous failure.4582 // There was previous failure.
...@@ -4553,24 +4587,113 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4553,24 +4587,113 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
4553 _ = comp.failed_win32_resources.swapRemove(win32_resource);4587 _ = comp.failed_win32_resources.swapRemove(win32_resource);
4554 }4588 }
45554589
4590 win32_resource_prog_node.activate();
4591 var child_progress_node = win32_resource_prog_node.start(src_basename, 0);
4592 child_progress_node.activate();
4593 defer child_progress_node.end();
4594
4556 var man = comp.obtainWin32ResourceCacheManifest();4595 var man = comp.obtainWin32ResourceCacheManifest();
4557 defer man.deinit();4596 defer man.deinit();
45584597
4559 _ = try man.addFile(win32_resource.src.src_path, null);4598 // For .manifest files, we ultimately just want to generate a .res with
4560 man.hash.addListOfBytes(win32_resource.src.extra_flags);4599 // the XML data as a RT_MANIFEST resource. This means we can skip preprocessing,
4600 // include paths, CLI options, etc.
4601 if (win32_resource.src == .manifest) {
4602 _ = try man.addFile(src_path, null);
45614603
4562 var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);4604 const res_basename = try std.fmt.allocPrint(arena, "{s}.res", .{src_basename});
4563 defer arena_allocator.deinit();
4564 const arena = arena_allocator.allocator();
45654605
4566 const rc_basename = std.fs.path.basename(win32_resource.src.src_path);4606 const digest = if (try man.hit()) man.final() else blk: {
4607 // The digest only depends on the .manifest file, so we can
4608 // get the digest now and write the .res directly to the cache
4609 const digest = man.final();
45674610
4568 win32_resource_prog_node.activate();4611 const o_sub_path = try std.fs.path.join(arena, &.{ "o", &digest });
4569 var child_progress_node = win32_resource_prog_node.start(rc_basename, 0);4612 var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{});
4570 child_progress_node.activate();4613 defer o_dir.close();
4571 defer child_progress_node.end();
45724614
4573 const rc_basename_noext = rc_basename[0 .. rc_basename.len - std.fs.path.extension(rc_basename).len];4615 var output_file = o_dir.createFile(res_basename, .{}) catch |err| {
4616 const output_file_path = try comp.local_cache_directory.join(arena, &.{ o_sub_path, res_basename });
4617 return comp.failWin32Resource(win32_resource, "failed to create output file '{s}': {s}", .{ output_file_path, @errorName(err) });
4618 };
4619 var output_file_closed = false;
4620 defer if (!output_file_closed) output_file.close();
4621
4622 var diagnostics = resinator.errors.Diagnostics.init(arena);
4623 defer diagnostics.deinit();
4624
4625 var output_buffered_stream = std.io.bufferedWriter(output_file.writer());
4626
4627 // In .rc files, a " within a quoted string is escaped as ""
4628 const fmtRcEscape = struct {
4629 fn formatRcEscape(bytes: []const u8, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
4630 _ = fmt;
4631 _ = options;
4632 for (bytes) |byte| switch (byte) {
4633 '"' => try writer.writeAll("\"\""),
4634 '\\' => try writer.writeAll("\\\\"),
4635 else => try writer.writeByte(byte),
4636 };
4637 }
4638
4639 pub fn fmtRcEscape(bytes: []const u8) std.fmt.Formatter(formatRcEscape) {
4640 return .{ .data = bytes };
4641 }
4642 }.fmtRcEscape;
4643
4644 // 1 is CREATEPROCESS_MANIFEST_RESOURCE_ID which is the default ID used for RT_MANIFEST resources
4645 // 24 is RT_MANIFEST
4646 const input = try std.fmt.allocPrint(arena, "1 24 \"{s}\"", .{fmtRcEscape(src_path)});
4647
4648 resinator.compile.compile(arena, input, output_buffered_stream.writer(), .{
4649 .cwd = std.fs.cwd(),
4650 .diagnostics = &diagnostics,
4651 .ignore_include_env_var = true,
4652 .default_code_page = .utf8,
4653 }) catch |err| switch (err) {
4654 error.ParseError, error.CompileError => {
4655 // Delete the output file on error
4656 output_file.close();
4657 output_file_closed = true;
4658 // Failing to delete is not really a big deal, so swallow any errors
4659 o_dir.deleteFile(res_basename) catch {
4660 const output_file_path = try comp.local_cache_directory.join(arena, &.{ o_sub_path, res_basename });
4661 log.warn("failed to delete '{s}': {s}", .{ output_file_path, @errorName(err) });
4662 };
4663 return comp.failWin32ResourceCompile(win32_resource, input, &diagnostics, null);
4664 },
4665 else => |e| return e,
4666 };
4667
4668 try output_buffered_stream.flush();
4669
4670 break :blk digest;
4671 };
4672
4673 if (man.have_exclusive_lock) {
4674 man.writeManifest() catch |err| {
4675 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ src_path, @errorName(err) });
4676 };
4677 }
4678
4679 win32_resource.status = .{
4680 .success = .{
4681 .res_path = try comp.local_cache_directory.join(comp.gpa, &[_][]const u8{
4682 "o", &digest, res_basename,
4683 }),
4684 .lock = man.toOwnedLock(),
4685 },
4686 };
4687 return;
4688 }
4689
4690 // We now know that we're compiling an .rc file
4691 const rc_src = win32_resource.src.rc;
4692
4693 _ = try man.addFile(rc_src.src_path, null);
4694 man.hash.addListOfBytes(rc_src.extra_flags);
4695
4696 const rc_basename_noext = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len];
45744697
4575 const digest = if (try man.hit()) man.final() else blk: {4698 const digest = if (try man.hit()) man.final() else blk: {
4576 const rcpp_filename = try std.fmt.allocPrint(arena, "{s}.rcpp", .{rc_basename_noext});4699 const rcpp_filename = try std.fmt.allocPrint(arena, "{s}.rcpp", .{rc_basename_noext});
...@@ -4586,11 +4709,11 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4586,11 +4709,11 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
4586 const out_res_path = try comp.tmpFilePath(arena, res_filename);4709 const out_res_path = try comp.tmpFilePath(arena, res_filename);
45874710
4588 var options = options: {4711 var options = options: {
4589 var resinator_args = try std.ArrayListUnmanaged([]const u8).initCapacity(comp.gpa, win32_resource.src.extra_flags.len + 4);4712 var resinator_args = try std.ArrayListUnmanaged([]const u8).initCapacity(comp.gpa, rc_src.extra_flags.len + 4);
4590 defer resinator_args.deinit(comp.gpa);4713 defer resinator_args.deinit(comp.gpa);
45914714
4592 resinator_args.appendAssumeCapacity(""); // dummy 'process name' arg4715 resinator_args.appendAssumeCapacity(""); // dummy 'process name' arg
4593 resinator_args.appendSliceAssumeCapacity(win32_resource.src.extra_flags);4716 resinator_args.appendSliceAssumeCapacity(rc_src.extra_flags);
4594 resinator_args.appendSliceAssumeCapacity(&.{ "--", out_rcpp_path, out_res_path });4717 resinator_args.appendSliceAssumeCapacity(&.{ "--", out_rcpp_path, out_res_path });
45954718
4596 var cli_diagnostics = resinator.cli.Diagnostics.init(comp.gpa);4719 var cli_diagnostics = resinator.cli.Diagnostics.init(comp.gpa);
...@@ -4619,7 +4742,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4619,7 +4742,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
4619 .nostdinc = false, // handled by addCCArgs4742 .nostdinc = false, // handled by addCCArgs
4620 });4743 });
46214744
4622 try argv.append(win32_resource.src.src_path);4745 try argv.append(rc_src.src_path);
4623 try argv.appendSlice(&[_][]const u8{4746 try argv.appendSlice(&[_][]const u8{
4624 "-o",4747 "-o",
4625 out_rcpp_path,4748 out_rcpp_path,
...@@ -4693,7 +4816,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4693,7 +4816,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
4693 },4816 },
4694 };4817 };
46954818
4696 var mapping_results = try resinator.source_mapping.parseAndRemoveLineCommands(arena, full_input, full_input, .{ .initial_filename = win32_resource.src.src_path });4819 var mapping_results = try resinator.source_mapping.parseAndRemoveLineCommands(arena, full_input, full_input, .{ .initial_filename = rc_src.src_path });
4697 defer mapping_results.mappings.deinit(arena);4820 defer mapping_results.mappings.deinit(arena);
46984821
4699 var final_input = resinator.comments.removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);4822 var final_input = resinator.comments.removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
...@@ -4776,7 +4899,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4776,7 +4899,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
4776 // the contents were the same, we hit the cache but the manifest is dirty and we need to update4899 // the contents were the same, we hit the cache but the manifest is dirty and we need to update
4777 // it to prevent doing a full file content comparison the next time around.4900 // it to prevent doing a full file content comparison the next time around.
4778 man.writeManifest() catch |err| {4901 man.writeManifest() catch |err| {
4779 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ win32_resource.src.src_path, @errorName(err) });4902 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ rc_src.src_path, @errorName(err) });
4780 };4903 };
4781 }4904 }
47824905
...@@ -5114,7 +5237,7 @@ pub fn addCCArgs(...@@ -5114,7 +5237,7 @@ pub fn addCCArgs(
5114 try argv.append("-fno-unwind-tables");5237 try argv.append("-fno-unwind-tables");
5115 }5238 }
5116 },5239 },
5117 .shared_library, .ll, .bc, .unknown, .static_library, .object, .def, .zig, .res => {},5240 .shared_library, .ll, .bc, .unknown, .static_library, .object, .def, .zig, .res, .manifest => {},
5118 .assembly, .assembly_with_cpp => {5241 .assembly, .assembly_with_cpp => {
5119 if (ext == .assembly_with_cpp) {5242 if (ext == .assembly_with_cpp) {
5120 const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, "include" });5243 const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, "include" });
...@@ -5340,7 +5463,10 @@ fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptim...@@ -5340,7 +5463,10 @@ fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptim
5340 try bundle.addRootErrorMessage(.{5463 try bundle.addRootErrorMessage(.{
5341 .msg = try bundle.printString(format, args),5464 .msg = try bundle.printString(format, args),
5342 .src_loc = try bundle.addSourceLocation(.{5465 .src_loc = try bundle.addSourceLocation(.{
5343 .src_path = try bundle.addString(win32_resource.src.src_path),5466 .src_path = try bundle.addString(switch (win32_resource.src) {
5467 .rc => |rc_src| rc_src.src_path,
5468 .manifest => |manifest_src| manifest_src,
5469 }),
5344 .line = 0,5470 .line = 0,
5345 .column = 0,5471 .column = 0,
5346 .span_start = 0,5472 .span_start = 0,
...@@ -5381,7 +5507,10 @@ fn failWin32ResourceCli(...@@ -5381,7 +5507,10 @@ fn failWin32ResourceCli(
5381 try bundle.addRootErrorMessage(.{5507 try bundle.addRootErrorMessage(.{
5382 .msg = try bundle.addString("invalid command line option(s)"),5508 .msg = try bundle.addString("invalid command line option(s)"),
5383 .src_loc = try bundle.addSourceLocation(.{5509 .src_loc = try bundle.addSourceLocation(.{
5384 .src_path = try bundle.addString(win32_resource.src.src_path),5510 .src_path = try bundle.addString(switch (win32_resource.src) {
5511 .rc => |rc_src| rc_src.src_path,
5512 .manifest => |manifest_src| manifest_src,
5513 }),
5385 .line = 0,5514 .line = 0,
5386 .column = 0,5515 .column = 0,
5387 .span_start = 0,5516 .span_start = 0,
...@@ -5427,7 +5556,7 @@ fn failWin32ResourceCompile(...@@ -5427,7 +5556,7 @@ fn failWin32ResourceCompile(
5427 win32_resource: *Win32Resource,5556 win32_resource: *Win32Resource,
5428 source: []const u8,5557 source: []const u8,
5429 diagnostics: *resinator.errors.Diagnostics,5558 diagnostics: *resinator.errors.Diagnostics,
5430 mappings: resinator.source_mapping.SourceMappings,5559 opt_mappings: ?resinator.source_mapping.SourceMappings,
5431) SemaError {5560) SemaError {
5432 @setCold(true);5561 @setCold(true);
54335562
...@@ -5451,19 +5580,26 @@ fn failWin32ResourceCompile(...@@ -5451,19 +5580,26 @@ fn failWin32ResourceCompile(
5451 .note => if (cur_err == null) continue,5580 .note => if (cur_err == null) continue,
5452 .err => {},5581 .err => {},
5453 }5582 }
5454 const corresponding_span = mappings.get(err_details.token.line_number);5583 const err_line, const err_filename = blk: {
5455 const corresponding_file = mappings.files.get(corresponding_span.filename_offset);5584 if (opt_mappings) |mappings| {
5585 const corresponding_span = mappings.get(err_details.token.line_number);
5586 const corresponding_file = mappings.files.get(corresponding_span.filename_offset);
5587 const err_line = corresponding_span.start_line;
5588 break :blk .{ err_line, corresponding_file };
5589 } else {
5590 break :blk .{ err_details.token.line_number, "<generated rc>" };
5591 }
5592 };
54565593
5457 const source_line_start = err_details.token.getLineStart(source);5594 const source_line_start = err_details.token.getLineStart(source);
5458 const column = err_details.token.calculateColumn(source, 1, source_line_start);5595 const column = err_details.token.calculateColumn(source, 1, source_line_start);
5459 const err_line = corresponding_span.start_line;
54605596
5461 msg_buf.clearRetainingCapacity();5597 msg_buf.clearRetainingCapacity();
5462 try err_details.render(msg_buf.writer(comp.gpa), source, diagnostics.strings.items);5598 try err_details.render(msg_buf.writer(comp.gpa), source, diagnostics.strings.items);
54635599
5464 const src_loc = src_loc: {5600 const src_loc = src_loc: {
5465 var src_loc: ErrorBundle.SourceLocation = .{5601 var src_loc: ErrorBundle.SourceLocation = .{
5466 .src_path = try bundle.addString(corresponding_file),5602 .src_path = try bundle.addString(err_filename),
5467 .line = @intCast(err_line - 1), // 1-based -> 0-based5603 .line = @intCast(err_line - 1), // 1-based -> 0-based
5468 .column = @intCast(column),5604 .column = @intCast(column),
5469 .span_start = 0,5605 .span_start = 0,
...@@ -5536,6 +5672,7 @@ pub const FileExt = enum {...@@ -5536,6 +5672,7 @@ pub const FileExt = enum {
5536 def,5672 def,
5537 rc,5673 rc,
5538 res,5674 res,
5675 manifest,
5539 unknown,5676 unknown,
55405677
5541 pub fn clangSupportsDepFile(ext: FileExt) bool {5678 pub fn clangSupportsDepFile(ext: FileExt) bool {
...@@ -5553,6 +5690,7 @@ pub const FileExt = enum {...@@ -5553,6 +5690,7 @@ pub const FileExt = enum {
5553 .def,5690 .def,
5554 .rc,5691 .rc,
5555 .res,5692 .res,
5693 .manifest,
5556 .unknown,5694 .unknown,
5557 => false,5695 => false,
5558 };5696 };
...@@ -5577,6 +5715,7 @@ pub const FileExt = enum {...@@ -5577,6 +5715,7 @@ pub const FileExt = enum {
5577 .def => ".def",5715 .def => ".def",
5578 .rc => ".rc",5716 .rc => ".rc",
5579 .res => ".res",5717 .res => ".res",
5718 .manifest => ".manifest",
5580 .unknown => "",5719 .unknown => "",
5581 };5720 };
5582 }5721 }
...@@ -5672,6 +5811,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {...@@ -5672,6 +5811,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
5672 return .rc;5811 return .rc;
5673 } else if (std.ascii.endsWithIgnoreCase(filename, ".res")) {5812 } else if (std.ascii.endsWithIgnoreCase(filename, ".res")) {
5674 return .res;5813 return .res;
5814 } else if (std.ascii.endsWithIgnoreCase(filename, ".manifest")) {
5815 return .manifest;
5675 } else {5816 } else {
5676 return .unknown;5817 return .unknown;
5677 }5818 }
src/main.zig+15
...@@ -938,6 +938,7 @@ fn buildOutputType(...@@ -938,6 +938,7 @@ fn buildOutputType(
938 var rc_source_files = std.ArrayList(Compilation.RcSourceFile).init(arena);938 var rc_source_files = std.ArrayList(Compilation.RcSourceFile).init(arena);
939 var rc_includes: Compilation.RcIncludes = .any;939 var rc_includes: Compilation.RcIncludes = .any;
940 var res_files = std.ArrayList(Compilation.LinkObject).init(arena);940 var res_files = std.ArrayList(Compilation.LinkObject).init(arena);
941 var manifest_file: ?[]const u8 = null;
941 var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);942 var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
942 var framework_dirs = std.ArrayList([]const u8).init(arena);943 var framework_dirs = std.ArrayList([]const u8).init(arena);
943 var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};944 var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
...@@ -1627,6 +1628,11 @@ fn buildOutputType(...@@ -1627,6 +1628,11 @@ fn buildOutputType(
1627 Compilation.classifyFileExt(arg)) {1628 Compilation.classifyFileExt(arg)) {
1628 .object, .static_library, .shared_library => try link_objects.append(.{ .path = arg }),1629 .object, .static_library, .shared_library => try link_objects.append(.{ .path = arg }),
1629 .res => try res_files.append(.{ .path = arg }),1630 .res => try res_files.append(.{ .path = arg }),
1631 .manifest => {
1632 if (manifest_file) |other| {
1633 fatal("only one manifest file can be specified, found '{s}' after '{s}'", .{ arg, other });
1634 } else manifest_file = arg;
1635 },
1630 .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {1636 .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {
1631 try c_source_files.append(.{1637 try c_source_files.append(.{
1632 .src_path = arg,1638 .src_path = arg,
...@@ -1734,6 +1740,11 @@ fn buildOutputType(...@@ -1734,6 +1740,11 @@ fn buildOutputType(
1734 .path = it.only_arg,1740 .path = it.only_arg,
1735 .must_link = must_link,1741 .must_link = must_link,
1736 }),1742 }),
1743 .manifest => {
1744 if (manifest_file) |other| {
1745 fatal("only one manifest file can be specified, found '{s}' after previously specified manifest '{s}'", .{ it.only_arg, other });
1746 } else manifest_file = it.only_arg;
1747 },
1737 .def => {1748 .def => {
1738 linker_module_definition_file = it.only_arg;1749 linker_module_definition_file = it.only_arg;
1739 },1750 },
...@@ -2601,6 +2612,9 @@ fn buildOutputType(...@@ -2601,6 +2612,9 @@ fn buildOutputType(
2601 try link_objects.append(res_file);2612 try link_objects.append(res_file);
2602 }2613 }
2603 } else {2614 } else {
2615 if (manifest_file != null) {
2616 fatal("manifest file is not allowed unless the target object format is coff (Windows/UEFI)", .{});
2617 }
2604 if (rc_source_files.items.len != 0) {2618 if (rc_source_files.items.len != 0) {
2605 fatal("rc files are not allowed unless the target object format is coff (Windows/UEFI)", .{});2619 fatal("rc files are not allowed unless the target object format is coff (Windows/UEFI)", .{});
2606 }2620 }
...@@ -3418,6 +3432,7 @@ fn buildOutputType(...@@ -3418,6 +3432,7 @@ fn buildOutputType(
3418 .symbol_wrap_set = symbol_wrap_set,3432 .symbol_wrap_set = symbol_wrap_set,
3419 .c_source_files = c_source_files.items,3433 .c_source_files = c_source_files.items,
3420 .rc_source_files = rc_source_files.items,3434 .rc_source_files = rc_source_files.items,
3435 .manifest_file = manifest_file,
3421 .rc_includes = rc_includes,3436 .rc_includes = rc_includes,
3422 .link_objects = link_objects.items,3437 .link_objects = link_objects.items,
3423 .framework_dirs = framework_dirs.items,3438 .framework_dirs = framework_dirs.items,