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 {
635635 use_lld: ?bool = null,
636636 zig_lib_dir: ?LazyPath = null,
637637 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
639645 /// Deprecated; use `main_mod_path`.
640646 main_pkg_path: ?LazyPath = null,
......@@ -656,6 +662,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
656662 .use_lld = options.use_lld,
657663 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
658664 .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
665 .win32_manifest = options.win32_manifest,
659666 });
660667}
661668
......@@ -706,6 +713,12 @@ pub const SharedLibraryOptions = struct {
706713 use_lld: ?bool = null,
707714 zig_lib_dir: ?LazyPath = null,
708715 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
710723 /// Deprecated; use `main_mod_path`.
711724 main_pkg_path: ?LazyPath = null,
......@@ -727,6 +740,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile
727740 .use_lld = options.use_lld,
728741 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
729742 .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
743 .win32_manifest = options.win32_manifest,
730744 });
731745}
732746
lib/std/Build/Step/Compile.zig+26
......@@ -98,6 +98,10 @@ vcpkg_bin_path: ?[]const u8 = null,
9898/// none: Do not use any autodetected include paths.
9999rc_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
101105installed_path: ?[]const u8,
102106
103107/// Base address for an executable image.
......@@ -319,6 +323,12 @@ pub const Options = struct {
319323 use_lld: ?bool = null,
320324 zig_lib_dir: ?LazyPath = null,
321325 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
323333 /// deprecated; use `main_mod_path`.
324334 main_pkg_path: ?LazyPath = null,
......@@ -525,6 +535,15 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
525535 lp.addStepDependencies(&self.step);
526536 }
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
528547 if (self.kind == .lib) {
529548 if (self.linkage != null and self.linkage.? == .static) {
530549 self.out_lib_filename = self.out_filename;
......@@ -957,6 +976,9 @@ pub fn addCSourceFile(self: *Compile, source: CSourceFile) void {
957976 source.file.addStepDependencies(&self.step);
958977}
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.
960982pub fn addWin32ResourceFile(self: *Compile, source: RcSourceFile) void {
961983 // Only the PE/COFF format has a Resource Table, so for any other target
962984 // the resource file is just ignored.
......@@ -1593,6 +1615,10 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
15931615 }
15941616 }
15951617
1618 if (self.win32_manifest) |manifest_file| {
1619 try zig_args.append(manifest_file.getPath(b));
1620 }
1621
15961622 if (transitive_deps.is_linking_libcpp) {
15971623 try zig_args.append("-lc++");
15981624 }
src/Compilation.zig+173-32
......@@ -358,7 +358,10 @@ pub const CObject = struct {
358358
359359pub const Win32Resource = struct {
360360 /// Relative to cwd. Owned by arena.
361 src: RcSourceFile,
361 src: union(enum) {
362 rc: RcSourceFile,
363 manifest: []const u8,
364 },
362365 status: union(enum) {
363366 new,
364367 success: struct {
......@@ -582,6 +585,7 @@ pub const InitOptions = struct {
582585 symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{},
583586 c_source_files: []const CSourceFile = &[0]CSourceFile{},
584587 rc_source_files: []const RcSourceFile = &[0]RcSourceFile{},
588 manifest_file: ?[]const u8 = null,
585589 rc_includes: RcIncludes = .any,
586590 link_objects: []LinkObject = &[0]LinkObject{},
587591 framework_dirs: []const []const u8 = &[0][]const u8{},
......@@ -1749,16 +1753,26 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
17491753 comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});
17501754 }
17511755
1752 // Add a `Win32Resource` for each `rc_source_files`.
1756 // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`.
17531757 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));
17551759 for (options.rc_source_files) |rc_source_file| {
17561760 const win32_resource = try gpa.create(Win32Resource);
17571761 errdefer gpa.destroy(win32_resource);
17581762
17591763 win32_resource.* = .{
17601764 .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 },
17621776 };
17631777 comp.win32_resource_table.putAssumeCapacityNoClobber(win32_resource, {});
17641778 }
......@@ -2477,8 +2491,15 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24772491
24782492 if (!build_options.only_core_functionality) {
24792493 for (comp.win32_resource_table.keys()) |key| {
2480 _ = try man.addFile(key.src.src_path, null);
2481 man.hash.addListOfBytes(key.src.extra_flags);
2494 switch (key.src) {
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 }
24822503 }
24832504 }
24842505
......@@ -4172,7 +4193,10 @@ fn reportRetryableWin32ResourceError(
41724193 try bundle.addRootErrorMessage(.{
41734194 .msg = try bundle.printString("{s}", .{@errorName(err)}),
41744195 .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 }),
41764200 .line = 0,
41774201 .column = 0,
41784202 .span_start = 0,
......@@ -4542,7 +4566,17 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
45424566 const tracy_trace = trace(@src());
45434567 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
45474581 if (win32_resource.clearStatus(comp.gpa)) {
45484582 // There was previous failure.
......@@ -4553,24 +4587,113 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
45534587 _ = comp.failed_win32_resources.swapRemove(win32_resource);
45544588 }
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
45564595 var man = comp.obtainWin32ResourceCacheManifest();
45574596 defer man.deinit();
45584597
4559 _ = try man.addFile(win32_resource.src.src_path, null);
4560 man.hash.addListOfBytes(win32_resource.src.extra_flags);
4598 // For .manifest files, we ultimately just want to generate a .res with
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);
4563 defer arena_allocator.deinit();
4564 const arena = arena_allocator.allocator();
4604 const res_basename = try std.fmt.allocPrint(arena, "{s}.res", .{src_basename});
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();
4569 var child_progress_node = win32_resource_prog_node.start(rc_basename, 0);
4570 child_progress_node.activate();
4571 defer child_progress_node.end();
4611 const o_sub_path = try std.fs.path.join(arena, &.{ "o", &digest });
4612 var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{});
4613 defer o_dir.close();
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
45754698 const digest = if (try man.hit()) man.final() else blk: {
45764699 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
45864709 const out_res_path = try comp.tmpFilePath(arena, res_filename);
45874710
45884711 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);
45904713 defer resinator_args.deinit(comp.gpa);
45914714
45924715 resinator_args.appendAssumeCapacity(""); // dummy 'process name' arg
4593 resinator_args.appendSliceAssumeCapacity(win32_resource.src.extra_flags);
4716 resinator_args.appendSliceAssumeCapacity(rc_src.extra_flags);
45944717 resinator_args.appendSliceAssumeCapacity(&.{ "--", out_rcpp_path, out_res_path });
45954718
45964719 var cli_diagnostics = resinator.cli.Diagnostics.init(comp.gpa);
......@@ -4619,7 +4742,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
46194742 .nostdinc = false, // handled by addCCArgs
46204743 });
46214744
4622 try argv.append(win32_resource.src.src_path);
4745 try argv.append(rc_src.src_path);
46234746 try argv.appendSlice(&[_][]const u8{
46244747 "-o",
46254748 out_rcpp_path,
......@@ -4693,7 +4816,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
46934816 },
46944817 };
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 });
46974820 defer mapping_results.mappings.deinit(arena);
46984821
46994822 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
47764899 // the contents were the same, we hit the cache but the manifest is dirty and we need to update
47774900 // it to prevent doing a full file content comparison the next time around.
47784901 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) });
47804903 };
47814904 }
47824905
......@@ -5114,7 +5237,7 @@ pub fn addCCArgs(
51145237 try argv.append("-fno-unwind-tables");
51155238 }
51165239 },
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 => {},
51185241 .assembly, .assembly_with_cpp => {
51195242 if (ext == .assembly_with_cpp) {
51205243 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
53405463 try bundle.addRootErrorMessage(.{
53415464 .msg = try bundle.printString(format, args),
53425465 .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 }),
53445470 .line = 0,
53455471 .column = 0,
53465472 .span_start = 0,
......@@ -5381,7 +5507,10 @@ fn failWin32ResourceCli(
53815507 try bundle.addRootErrorMessage(.{
53825508 .msg = try bundle.addString("invalid command line option(s)"),
53835509 .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 }),
53855514 .line = 0,
53865515 .column = 0,
53875516 .span_start = 0,
......@@ -5427,7 +5556,7 @@ fn failWin32ResourceCompile(
54275556 win32_resource: *Win32Resource,
54285557 source: []const u8,
54295558 diagnostics: *resinator.errors.Diagnostics,
5430 mappings: resinator.source_mapping.SourceMappings,
5559 opt_mappings: ?resinator.source_mapping.SourceMappings,
54315560) SemaError {
54325561 @setCold(true);
54335562
......@@ -5451,19 +5580,26 @@ fn failWin32ResourceCompile(
54515580 .note => if (cur_err == null) continue,
54525581 .err => {},
54535582 }
5454 const corresponding_span = mappings.get(err_details.token.line_number);
5455 const corresponding_file = mappings.files.get(corresponding_span.filename_offset);
5583 const err_line, const err_filename = blk: {
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
54575594 const source_line_start = err_details.token.getLineStart(source);
54585595 const column = err_details.token.calculateColumn(source, 1, source_line_start);
5459 const err_line = corresponding_span.start_line;
54605596
54615597 msg_buf.clearRetainingCapacity();
54625598 try err_details.render(msg_buf.writer(comp.gpa), source, diagnostics.strings.items);
54635599
54645600 const src_loc = src_loc: {
54655601 var src_loc: ErrorBundle.SourceLocation = .{
5466 .src_path = try bundle.addString(corresponding_file),
5602 .src_path = try bundle.addString(err_filename),
54675603 .line = @intCast(err_line - 1), // 1-based -> 0-based
54685604 .column = @intCast(column),
54695605 .span_start = 0,
......@@ -5536,6 +5672,7 @@ pub const FileExt = enum {
55365672 def,
55375673 rc,
55385674 res,
5675 manifest,
55395676 unknown,
55405677
55415678 pub fn clangSupportsDepFile(ext: FileExt) bool {
......@@ -5553,6 +5690,7 @@ pub const FileExt = enum {
55535690 .def,
55545691 .rc,
55555692 .res,
5693 .manifest,
55565694 .unknown,
55575695 => false,
55585696 };
......@@ -5577,6 +5715,7 @@ pub const FileExt = enum {
55775715 .def => ".def",
55785716 .rc => ".rc",
55795717 .res => ".res",
5718 .manifest => ".manifest",
55805719 .unknown => "",
55815720 };
55825721 }
......@@ -5672,6 +5811,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
56725811 return .rc;
56735812 } else if (std.ascii.endsWithIgnoreCase(filename, ".res")) {
56745813 return .res;
5814 } else if (std.ascii.endsWithIgnoreCase(filename, ".manifest")) {
5815 return .manifest;
56755816 } else {
56765817 return .unknown;
56775818 }
src/main.zig+15
......@@ -938,6 +938,7 @@ fn buildOutputType(
938938 var rc_source_files = std.ArrayList(Compilation.RcSourceFile).init(arena);
939939 var rc_includes: Compilation.RcIncludes = .any;
940940 var res_files = std.ArrayList(Compilation.LinkObject).init(arena);
941 var manifest_file: ?[]const u8 = null;
941942 var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
942943 var framework_dirs = std.ArrayList([]const u8).init(arena);
943944 var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
......@@ -1627,6 +1628,11 @@ fn buildOutputType(
16271628 Compilation.classifyFileExt(arg)) {
16281629 .object, .static_library, .shared_library => try link_objects.append(.{ .path = arg }),
16291630 .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 },
16301636 .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {
16311637 try c_source_files.append(.{
16321638 .src_path = arg,
......@@ -1734,6 +1740,11 @@ fn buildOutputType(
17341740 .path = it.only_arg,
17351741 .must_link = must_link,
17361742 }),
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 },
17371748 .def => {
17381749 linker_module_definition_file = it.only_arg;
17391750 },
......@@ -2601,6 +2612,9 @@ fn buildOutputType(
26012612 try link_objects.append(res_file);
26022613 }
26032614 } else {
2615 if (manifest_file != null) {
2616 fatal("manifest file is not allowed unless the target object format is coff (Windows/UEFI)", .{});
2617 }
26042618 if (rc_source_files.items.len != 0) {
26052619 fatal("rc files are not allowed unless the target object format is coff (Windows/UEFI)", .{});
26062620 }
......@@ -3418,6 +3432,7 @@ fn buildOutputType(
34183432 .symbol_wrap_set = symbol_wrap_set,
34193433 .c_source_files = c_source_files.items,
34203434 .rc_source_files = rc_source_files.items,
3435 .manifest_file = manifest_file,
34213436 .rc_includes = rc_includes,
34223437 .link_objects = link_objects.items,
34233438 .framework_dirs = framework_dirs.items,