authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-15 19:47:27-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-15 19:47:27-07:00
logca690ffa22ab4463e53b547d109a2b2081771e05
tree3557c38c0f5b1c792a97fc6a1838bd9952b97c0e
parentebde525ccee41a8eefbb868cf4df756f1a937dd2
parentb51147889f1c44d380818977a4dbefebffb8d91f
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17448 from squeek502/win32-manifest

Add preliminary support for Windows `.manifest` files

4 files changed, 231 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+18
......@@ -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,
......@@ -1647,6 +1653,9 @@ fn buildOutputType(
16471653 } else root_src_file = arg;
16481654 },
16491655 .def, .unknown => {
1656 if (std.ascii.eqlIgnoreCase(".xml", std.fs.path.extension(arg))) {
1657 std.log.warn("embedded manifest files must have the extension '.manifest'", .{});
1658 }
16501659 fatal("unrecognized file extension of parameter '{s}'", .{arg});
16511660 },
16521661 }
......@@ -1734,6 +1743,11 @@ fn buildOutputType(
17341743 .path = it.only_arg,
17351744 .must_link = must_link,
17361745 }),
1746 .manifest => {
1747 if (manifest_file) |other| {
1748 fatal("only one manifest file can be specified, found '{s}' after previously specified manifest '{s}'", .{ it.only_arg, other });
1749 } else manifest_file = it.only_arg;
1750 },
17371751 .def => {
17381752 linker_module_definition_file = it.only_arg;
17391753 },
......@@ -2601,6 +2615,9 @@ fn buildOutputType(
26012615 try link_objects.append(res_file);
26022616 }
26032617 } else {
2618 if (manifest_file != null) {
2619 fatal("manifest file is not allowed unless the target object format is coff (Windows/UEFI)", .{});
2620 }
26042621 if (rc_source_files.items.len != 0) {
26052622 fatal("rc files are not allowed unless the target object format is coff (Windows/UEFI)", .{});
26062623 }
......@@ -3418,6 +3435,7 @@ fn buildOutputType(
34183435 .symbol_wrap_set = symbol_wrap_set,
34193436 .c_source_files = c_source_files.items,
34203437 .rc_source_files = rc_source_files.items,
3438 .manifest_file = manifest_file,
34213439 .rc_includes = rc_includes,
34223440 .link_objects = link_objects.items,
34233441 .framework_dirs = framework_dirs.items,