| ... | ... | @@ -358,7 +358,10 @@ pub const CObject = struct { |
| 358 | 358 | |
| 359 | 359 | pub const Win32Resource = struct { |
| 360 | 360 | /// Relative to cwd. Owned by arena. |
| 361 | | src: RcSourceFile, |
| 361 | src: union(enum) { |
| 362 | rc: RcSourceFile, |
| 363 | manifest: []const u8, |
| 364 | }, |
| 362 | 365 | status: union(enum) { |
| 363 | 366 | new, |
| 364 | 367 | success: struct { |
| ... | ... | @@ -582,6 +585,7 @@ pub const InitOptions = struct { |
| 582 | 585 | symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{}, |
| 583 | 586 | c_source_files: []const CSourceFile = &[0]CSourceFile{}, |
| 584 | 587 | rc_source_files: []const RcSourceFile = &[0]RcSourceFile{}, |
| 588 | manifest_file: ?[]const u8 = null, |
| 585 | 589 | rc_includes: RcIncludes = .any, |
| 586 | 590 | link_objects: []LinkObject = &[0]LinkObject{}, |
| 587 | 591 | framework_dirs: []const []const u8 = &[0][]const u8{}, |
| ... | ... | @@ -1749,16 +1753,26 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { |
| 1749 | 1753 | comp.c_object_table.putAssumeCapacityNoClobber(c_object, {}); |
| 1750 | 1754 | } |
| 1751 | 1755 | |
| 1752 | | // Add a `Win32Resource` for each `rc_source_files`. |
| 1756 | // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`. |
| 1753 | 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 | 1759 | for (options.rc_source_files) |rc_source_file| { |
| 1756 | 1760 | const win32_resource = try gpa.create(Win32Resource); |
| 1757 | 1761 | errdefer gpa.destroy(win32_resource); |
| 1758 | 1762 | |
| 1759 | 1763 | win32_resource.* = .{ |
| 1760 | 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 | 1777 | comp.win32_resource_table.putAssumeCapacityNoClobber(win32_resource, {}); |
| 1764 | 1778 | } |
| ... | ... | @@ -2477,8 +2491,15 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes |
| 2477 | 2491 | |
| 2478 | 2492 | if (!build_options.only_core_functionality) { |
| 2479 | 2493 | 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 | } |
| 2482 | 2503 | } |
| 2483 | 2504 | } |
| 2484 | 2505 | |
| ... | ... | @@ -4172,7 +4193,10 @@ fn reportRetryableWin32ResourceError( |
| 4172 | 4193 | try bundle.addRootErrorMessage(.{ |
| 4173 | 4194 | .msg = try bundle.printString("{s}", .{@errorName(err)}), |
| 4174 | 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 | 4200 | .line = 0, |
| 4177 | 4201 | .column = 0, |
| 4178 | 4202 | .span_start = 0, |
| ... | ... | @@ -4542,7 +4566,17 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 4542 | 4566 | const tracy_trace = trace(@src()); |
| 4543 | 4567 | defer tracy_trace.end(); |
| 4544 | 4568 | |
| 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(); |
| 4546 | 4580 | |
| 4547 | 4581 | if (win32_resource.clearStatus(comp.gpa)) { |
| 4548 | 4582 | // There was previous failure. |
| ... | ... | @@ -4553,24 +4587,113 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 4553 | 4587 | _ = comp.failed_win32_resources.swapRemove(win32_resource); |
| 4554 | 4588 | } |
| 4555 | 4589 | |
| 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 | 4595 | var man = comp.obtainWin32ResourceCacheManifest(); |
| 4557 | 4596 | defer man.deinit(); |
| 4558 | 4597 | |
| 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); |
| 4561 | 4603 | |
| 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}); |
| 4565 | 4605 | |
| 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(); |
| 4567 | 4610 | |
| 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(); |
| 4572 | 4614 | |
| 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]; |
| 4574 | 4697 | |
| 4575 | 4698 | const digest = if (try man.hit()) man.final() else blk: { |
| 4576 | 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 | 4709 | const out_res_path = try comp.tmpFilePath(arena, res_filename); |
| 4587 | 4710 | |
| 4588 | 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 | 4713 | defer resinator_args.deinit(comp.gpa); |
| 4591 | 4714 | |
| 4592 | 4715 | 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 | 4717 | resinator_args.appendSliceAssumeCapacity(&.{ "--", out_rcpp_path, out_res_path }); |
| 4595 | 4718 | |
| 4596 | 4719 | var cli_diagnostics = resinator.cli.Diagnostics.init(comp.gpa); |
| ... | ... | @@ -4619,7 +4742,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 4619 | 4742 | .nostdinc = false, // handled by addCCArgs |
| 4620 | 4743 | }); |
| 4621 | 4744 | |
| 4622 | | try argv.append(win32_resource.src.src_path); |
| 4745 | try argv.append(rc_src.src_path); |
| 4623 | 4746 | try argv.appendSlice(&[_][]const u8{ |
| 4624 | 4747 | "-o", |
| 4625 | 4748 | out_rcpp_path, |
| ... | ... | @@ -4693,7 +4816,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 4693 | 4816 | }, |
| 4694 | 4817 | }; |
| 4695 | 4818 | |
| 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 | 4820 | defer mapping_results.mappings.deinit(arena); |
| 4698 | 4821 | |
| 4699 | 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 | 4899 | // the contents were the same, we hit the cache but the manifest is dirty and we need to update |
| 4777 | 4900 | // it to prevent doing a full file content comparison the next time around. |
| 4778 | 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 | } |
| 4782 | 4905 | |
| ... | ... | @@ -5114,7 +5237,7 @@ pub fn addCCArgs( |
| 5114 | 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 | 5241 | .assembly, .assembly_with_cpp => { |
| 5119 | 5242 | if (ext == .assembly_with_cpp) { |
| 5120 | 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 | 5463 | try bundle.addRootErrorMessage(.{ |
| 5341 | 5464 | .msg = try bundle.printString(format, args), |
| 5342 | 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 | 5470 | .line = 0, |
| 5345 | 5471 | .column = 0, |
| 5346 | 5472 | .span_start = 0, |
| ... | ... | @@ -5381,7 +5507,10 @@ fn failWin32ResourceCli( |
| 5381 | 5507 | try bundle.addRootErrorMessage(.{ |
| 5382 | 5508 | .msg = try bundle.addString("invalid command line option(s)"), |
| 5383 | 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 | 5514 | .line = 0, |
| 5386 | 5515 | .column = 0, |
| 5387 | 5516 | .span_start = 0, |
| ... | ... | @@ -5427,7 +5556,7 @@ fn failWin32ResourceCompile( |
| 5427 | 5556 | win32_resource: *Win32Resource, |
| 5428 | 5557 | source: []const u8, |
| 5429 | 5558 | diagnostics: *resinator.errors.Diagnostics, |
| 5430 | | mappings: resinator.source_mapping.SourceMappings, |
| 5559 | opt_mappings: ?resinator.source_mapping.SourceMappings, |
| 5431 | 5560 | ) SemaError { |
| 5432 | 5561 | @setCold(true); |
| 5433 | 5562 | |
| ... | ... | @@ -5451,19 +5580,26 @@ fn failWin32ResourceCompile( |
| 5451 | 5580 | .note => if (cur_err == null) continue, |
| 5452 | 5581 | .err => {}, |
| 5453 | 5582 | } |
| 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 | }; |
| 5456 | 5593 | |
| 5457 | 5594 | const source_line_start = err_details.token.getLineStart(source); |
| 5458 | 5595 | const column = err_details.token.calculateColumn(source, 1, source_line_start); |
| 5459 | | const err_line = corresponding_span.start_line; |
| 5460 | 5596 | |
| 5461 | 5597 | msg_buf.clearRetainingCapacity(); |
| 5462 | 5598 | try err_details.render(msg_buf.writer(comp.gpa), source, diagnostics.strings.items); |
| 5463 | 5599 | |
| 5464 | 5600 | const src_loc = src_loc: { |
| 5465 | 5601 | var src_loc: ErrorBundle.SourceLocation = .{ |
| 5466 | | .src_path = try bundle.addString(corresponding_file), |
| 5602 | .src_path = try bundle.addString(err_filename), |
| 5467 | 5603 | .line = @intCast(err_line - 1), // 1-based -> 0-based |
| 5468 | 5604 | .column = @intCast(column), |
| 5469 | 5605 | .span_start = 0, |
| ... | ... | @@ -5536,6 +5672,7 @@ pub const FileExt = enum { |
| 5536 | 5672 | def, |
| 5537 | 5673 | rc, |
| 5538 | 5674 | res, |
| 5675 | manifest, |
| 5539 | 5676 | unknown, |
| 5540 | 5677 | |
| 5541 | 5678 | pub fn clangSupportsDepFile(ext: FileExt) bool { |
| ... | ... | @@ -5553,6 +5690,7 @@ pub const FileExt = enum { |
| 5553 | 5690 | .def, |
| 5554 | 5691 | .rc, |
| 5555 | 5692 | .res, |
| 5693 | .manifest, |
| 5556 | 5694 | .unknown, |
| 5557 | 5695 | => false, |
| 5558 | 5696 | }; |
| ... | ... | @@ -5577,6 +5715,7 @@ pub const FileExt = enum { |
| 5577 | 5715 | .def => ".def", |
| 5578 | 5716 | .rc => ".rc", |
| 5579 | 5717 | .res => ".res", |
| 5718 | .manifest => ".manifest", |
| 5580 | 5719 | .unknown => "", |
| 5581 | 5720 | }; |
| 5582 | 5721 | } |
| ... | ... | @@ -5672,6 +5811,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt { |
| 5672 | 5811 | return .rc; |
| 5673 | 5812 | } else if (std.ascii.endsWithIgnoreCase(filename, ".res")) { |
| 5674 | 5813 | return .res; |
| 5814 | } else if (std.ascii.endsWithIgnoreCase(filename, ".manifest")) { |
| 5815 | return .manifest; |
| 5675 | 5816 | } else { |
| 5676 | 5817 | return .unknown; |
| 5677 | 5818 | } |