authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-10-05 03:30:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-12 16:19:34-07:00
log2769215b9037bb1f407e07887d4d21957ce1a9e0
tree9a89a0975e83ca7be8c407e24ad52cc88f370f2b
parent375bb5f4a1ebfc1d002e252d97077da7e1db7e4d

Add `zig rc` subcommand, a drop-in replacement for rc.exe

Uses resinator under-the-hood (see https://github.com/ziglang/zig/pull/17069) Closes #9564

6 files changed, 407 insertions(+), 55 deletions(-)

src/Compilation.zig+8-53
...@@ -4607,63 +4607,18 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -4607,63 +4607,18 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
46074607
4608 var argv = std.ArrayList([]const u8).init(comp.gpa);4608 var argv = std.ArrayList([]const u8).init(comp.gpa);
4609 defer argv.deinit();4609 defer argv.deinit();
4610 var temp_strings = std.ArrayList([]const u8).init(comp.gpa);
4611 defer {
4612 for (temp_strings.items) |temp_string| {
4613 comp.gpa.free(temp_string);
4614 }
4615 temp_strings.deinit();
4616 }
46174610
4618 // TODO: support options.preprocess == .no and .only4611 // TODO: support options.preprocess == .no and .only
4619 // alternatively, error if those options are used4612 // alternatively, error if those options are used
4620 try argv.appendSlice(&[_][]const u8{4613 try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang" });
4621 self_exe_path,4614
4622 "clang",4615 try resinator.preprocess.appendClangArgs(arena, &argv, options, .{
4623 "-E", // preprocessor only4616 .clang_target = null, // handled by addCCArgs
4624 "--comments",4617 .system_include_paths = &.{}, // handled by addCCArgs
4625 "-fuse-line-directives", // #line <num> instead of # <num>4618 .needs_gnu_workaround = comp.getTarget().isGnu(),
4626 "-xc", // output c4619 .nostdinc = false, // handled by addCCArgs
4627 "-Werror=null-character", // error on null characters instead of converting them to spaces
4628 "-fms-compatibility", // Allow things like "header.h" to be resolved relative to the 'root' .rc file, among other things
4629 "-DRC_INVOKED", // https://learn.microsoft.com/en-us/windows/win32/menurc/predefined-macros
4630 });4620 });
4631 // Using -fms-compatibility and targeting the gnu abi interact in a strange way:4621
4632 // - Targeting the GNU abi stops _MSC_VER from being defined
4633 // - Passing -fms-compatibility stops __GNUC__ from being defined
4634 // Neither being defined is a problem for things like things like MinGW's
4635 // vadefs.h, which will fail during preprocessing if neither are defined.
4636 // So, when targeting the GNU abi, we need to force __GNUC__ to be defined.
4637 //
4638 // TODO: This is a workaround that should be removed if possible.
4639 if (comp.getTarget().isGnu()) {
4640 // This is the same default gnuc version that Clang uses:
4641 // https://github.com/llvm/llvm-project/blob/4b5366c9512aa273a5272af1d833961e1ed156e7/clang/lib/Driver/ToolChains/Clang.cpp#L6738
4642 try argv.append("-fgnuc-version=4.2.1");
4643 }
4644 for (options.extra_include_paths.items) |extra_include_path| {
4645 try argv.append("--include-directory");
4646 try argv.append(extra_include_path);
4647 }
4648 var symbol_it = options.symbols.iterator();
4649 while (symbol_it.next()) |entry| {
4650 switch (entry.value_ptr.*) {
4651 .define => |value| {
4652 try argv.append("-D");
4653 const define_arg = arg: {
4654 const arg = try std.fmt.allocPrint(comp.gpa, "{s}={s}", .{ entry.key_ptr.*, value });
4655 errdefer comp.gpa.free(arg);
4656 try temp_strings.append(arg);
4657 break :arg arg;
4658 };
4659 try argv.append(define_arg);
4660 },
4661 .undefine => {
4662 try argv.append("-U");
4663 try argv.append(entry.key_ptr.*);
4664 },
4665 }
4666 }
4667 try argv.append(win32_resource.src.src_path);4622 try argv.append(win32_resource.src.src_path);
4668 try argv.appendSlice(&[_][]const u8{4623 try argv.appendSlice(&[_][]const u8{
4669 "-o",4624 "-o",
src/main.zig+267
...@@ -104,6 +104,7 @@ const normal_usage =...@@ -104,6 +104,7 @@ const normal_usage =
104 \\ lib Use Zig as a drop-in lib.exe104 \\ lib Use Zig as a drop-in lib.exe
105 \\ ranlib Use Zig as a drop-in ranlib105 \\ ranlib Use Zig as a drop-in ranlib
106 \\ objcopy Use Zig as a drop-in objcopy106 \\ objcopy Use Zig as a drop-in objcopy
107 \\ rc Use Zig as a drop-in rc.exe
107 \\108 \\
108 \\ env Print lib path, std path, cache directory, and version109 \\ env Print lib path, std path, cache directory, and version
109 \\ help Print this help and exit110 \\ help Print this help and exit
...@@ -300,6 +301,8 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -300,6 +301,8 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
300 return buildOutputType(gpa, arena, args, .cpp);301 return buildOutputType(gpa, arena, args, .cpp);
301 } else if (mem.eql(u8, cmd, "translate-c")) {302 } else if (mem.eql(u8, cmd, "translate-c")) {
302 return buildOutputType(gpa, arena, args, .translate_c);303 return buildOutputType(gpa, arena, args, .translate_c);
304 } else if (mem.eql(u8, cmd, "rc")) {
305 return cmdRc(gpa, arena, args[1..]);
303 } else if (mem.eql(u8, cmd, "fmt")) {306 } else if (mem.eql(u8, cmd, "fmt")) {
304 return cmdFmt(gpa, arena, cmd_args);307 return cmdFmt(gpa, arena, cmd_args);
305 } else if (mem.eql(u8, cmd, "objcopy")) {308 } else if (mem.eql(u8, cmd, "objcopy")) {
...@@ -4372,6 +4375,270 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*Compilati...@@ -4372,6 +4375,270 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*Compilati
4372 }4375 }
4373}4376}
43744377
4378fn cmdRc(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4379 const resinator = @import("resinator.zig");
4380
4381 const stderr = std.io.getStdErr();
4382 const stderr_config = std.io.tty.detectConfig(stderr);
4383
4384 var options = options: {
4385 var cli_diagnostics = resinator.cli.Diagnostics.init(gpa);
4386 defer cli_diagnostics.deinit();
4387 var options = resinator.cli.parse(gpa, args, &cli_diagnostics) catch |err| switch (err) {
4388 error.ParseError => {
4389 cli_diagnostics.renderToStdErr(args, stderr_config);
4390 process.exit(1);
4391 },
4392 else => |e| return e,
4393 };
4394 try options.maybeAppendRC(std.fs.cwd());
4395
4396 // print any warnings/notes
4397 cli_diagnostics.renderToStdErr(args, stderr_config);
4398 // If there was something printed, then add an extra newline separator
4399 // so that there is a clear separation between the cli diagnostics and whatever
4400 // gets printed after
4401 if (cli_diagnostics.errors.items.len > 0) {
4402 std.debug.print("\n", .{});
4403 }
4404 break :options options;
4405 };
4406 defer options.deinit();
4407
4408 if (options.print_help_and_exit) {
4409 try resinator.cli.writeUsage(stderr.writer(), "zig rc");
4410 return;
4411 }
4412
4413 const stdout_writer = std.io.getStdOut().writer();
4414 if (options.verbose) {
4415 try options.dumpVerbose(stdout_writer);
4416 try stdout_writer.writeByte('\n');
4417 }
4418
4419 var full_input = full_input: {
4420 if (options.preprocess != .no) {
4421 if (!build_options.have_llvm) {
4422 fatal("clang not available: compiler built without LLVM extensions", .{});
4423 }
4424
4425 var argv = std.ArrayList([]const u8).init(gpa);
4426 defer argv.deinit();
4427
4428 const self_exe_path = try introspect.findZigExePath(arena);
4429 var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
4430 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to find zig installation directory: {s}", .{@errorName(err)});
4431 process.exit(1);
4432 };
4433 defer zig_lib_directory.handle.close();
4434
4435 const include_args = detectRcIncludeDirs(arena, zig_lib_directory.path.?, options.auto_includes) catch |err| {
4436 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to detect system include directories: {s}", .{@errorName(err)});
4437 process.exit(1);
4438 };
4439
4440 try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang" });
4441
4442 const clang_target = clang_target: {
4443 if (include_args.target_abi) |abi| {
4444 break :clang_target try std.fmt.allocPrint(arena, "x86_64-unknown-windows-{s}", .{abi});
4445 }
4446 break :clang_target "x86_64-unknown-windows";
4447 };
4448 try resinator.preprocess.appendClangArgs(arena, &argv, options, .{
4449 .clang_target = clang_target,
4450 .system_include_paths = include_args.include_paths,
4451 .needs_gnu_workaround = if (include_args.target_abi) |abi| std.mem.eql(u8, abi, "gnu") else false,
4452 .nostdinc = true,
4453 });
4454
4455 try argv.append(options.input_filename);
4456
4457 if (options.verbose) {
4458 try stdout_writer.writeAll("Preprocessor: zig clang\n");
4459 for (argv.items[0 .. argv.items.len - 1]) |arg| {
4460 try stdout_writer.print("{s} ", .{arg});
4461 }
4462 try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
4463 }
4464
4465 if (std.process.can_spawn) {
4466 var result = std.ChildProcess.exec(.{
4467 .allocator = gpa,
4468 .argv = argv.items,
4469 .max_output_bytes = std.math.maxInt(u32),
4470 }) catch |err| {
4471 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to spawn preprocessor child process: {s}", .{@errorName(err)});
4472 process.exit(1);
4473 };
4474 errdefer gpa.free(result.stdout);
4475 defer gpa.free(result.stderr);
4476
4477 switch (result.term) {
4478 .Exited => |code| {
4479 if (code != 0) {
4480 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "the preprocessor failed with exit code {}:", .{code});
4481 try stderr.writeAll(result.stderr);
4482 try stderr.writeAll("\n");
4483 process.exit(1);
4484 }
4485 },
4486 .Signal, .Stopped, .Unknown => {
4487 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "the preprocessor terminated unexpectedly ({s}):", .{@tagName(result.term)});
4488 try stderr.writeAll(result.stderr);
4489 try stderr.writeAll("\n");
4490 process.exit(1);
4491 },
4492 }
4493
4494 break :full_input result.stdout;
4495 } else {
4496 // need to use an intermediate file
4497 const rand_int = std.crypto.random.int(u64);
4498 const preprocessed_path = try std.fmt.allocPrint(gpa, "resinator{x}.rcpp", .{rand_int});
4499 defer gpa.free(preprocessed_path);
4500 defer std.fs.cwd().deleteFile(preprocessed_path) catch {};
4501
4502 try argv.appendSlice(&.{ "-o", preprocessed_path });
4503 const exit_code = try clangMain(arena, argv.items);
4504 if (exit_code != 0) {
4505 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "the preprocessor failed with exit code {}:", .{exit_code});
4506 process.exit(1);
4507 }
4508 break :full_input std.fs.cwd().readFileAlloc(gpa, preprocessed_path, std.math.maxInt(usize)) catch |err| {
4509 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to read preprocessed file path '{s}': {s}", .{ preprocessed_path, @errorName(err) });
4510 process.exit(1);
4511 };
4512 }
4513 } else {
4514 break :full_input std.fs.cwd().readFileAlloc(gpa, options.input_filename, std.math.maxInt(usize)) catch |err| {
4515 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to read input file path '{s}': {s}", .{ options.input_filename, @errorName(err) });
4516 process.exit(1);
4517 };
4518 }
4519 };
4520 defer gpa.free(full_input);
4521
4522 if (options.preprocess == .only) {
4523 std.fs.cwd().writeFile(options.output_filename, full_input) catch |err| {
4524 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to write output file '{s}': {s}", .{ options.output_filename, @errorName(err) });
4525 process.exit(1);
4526 };
4527 return cleanExit();
4528 }
4529
4530 var mapping_results = try resinator.source_mapping.parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_filename });
4531 defer mapping_results.mappings.deinit(gpa);
4532
4533 var final_input = resinator.comments.removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
4534
4535 var output_file = std.fs.cwd().createFile(options.output_filename, .{}) catch |err| {
4536 try resinator.utils.renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to create output file '{s}': {s}", .{ options.output_filename, @errorName(err) });
4537 process.exit(1);
4538 };
4539 var output_file_closed = false;
4540 defer if (!output_file_closed) output_file.close();
4541
4542 var diagnostics = resinator.errors.Diagnostics.init(gpa);
4543 defer diagnostics.deinit();
4544
4545 var output_buffered_stream = std.io.bufferedWriter(output_file.writer());
4546
4547 resinator.compile.compile(gpa, final_input, output_buffered_stream.writer(), .{
4548 .cwd = std.fs.cwd(),
4549 .diagnostics = &diagnostics,
4550 .source_mappings = &mapping_results.mappings,
4551 .dependencies_list = null,
4552 .ignore_include_env_var = options.ignore_include_env_var,
4553 .extra_include_paths = options.extra_include_paths.items,
4554 .default_language_id = options.default_language_id,
4555 .default_code_page = options.default_code_page orelse .windows1252,
4556 .verbose = options.verbose,
4557 .null_terminate_string_table_strings = options.null_terminate_string_table_strings,
4558 .max_string_literal_codepoints = options.max_string_literal_codepoints,
4559 .silent_duplicate_control_ids = options.silent_duplicate_control_ids,
4560 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
4561 }) catch |err| switch (err) {
4562 error.ParseError, error.CompileError => {
4563 diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
4564 // Delete the output file on error
4565 output_file.close();
4566 output_file_closed = true;
4567 // Failing to delete is not really a big deal, so swallow any errors
4568 std.fs.cwd().deleteFile(options.output_filename) catch {};
4569 process.exit(1);
4570 },
4571 else => |e| return e,
4572 };
4573
4574 try output_buffered_stream.flush();
4575
4576 // print any warnings/notes
4577 diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
4578
4579 return cleanExit();
4580}
4581
4582const RcIncludeArgs = struct {
4583 include_paths: []const []const u8 = &.{},
4584 target_abi: ?[]const u8 = null,
4585};
4586
4587fn detectRcIncludeDirs(arena: Allocator, zig_lib_dir: []const u8, auto_includes: @import("resinator.zig").cli.Options.AutoIncludes) !RcIncludeArgs {
4588 if (auto_includes == .none) return .{};
4589 var cur_includes = auto_includes;
4590 if (builtin.target.os.tag != .windows) {
4591 switch (cur_includes) {
4592 // MSVC can't be found when the host isn't Windows, so short-circuit.
4593 .msvc => return error.WindowsSdkNotFound,
4594 // Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts.
4595 .any => cur_includes = .gnu,
4596 .gnu => {},
4597 .none => unreachable,
4598 }
4599 }
4600 while (true) {
4601 switch (cur_includes) {
4602 .any, .msvc => {
4603 const cross_target = std.zig.CrossTarget.parse(.{ .arch_os_abi = "native-windows-msvc" }) catch unreachable;
4604 const target = cross_target.toTarget();
4605 const is_native_abi = cross_target.isNativeAbi();
4606 const detected_libc = Compilation.detectLibCIncludeDirs(arena, zig_lib_dir, target, is_native_abi, true, null) catch |err| {
4607 if (cur_includes == .any) {
4608 // fall back to mingw
4609 cur_includes = .gnu;
4610 continue;
4611 }
4612 return err;
4613 };
4614 if (detected_libc.libc_include_dir_list.len == 0) {
4615 if (cur_includes == .any) {
4616 // fall back to mingw
4617 cur_includes = .gnu;
4618 continue;
4619 }
4620 return error.WindowsSdkNotFound;
4621 }
4622 return .{
4623 .include_paths = detected_libc.libc_include_dir_list,
4624 .target_abi = "msvc",
4625 };
4626 },
4627 .gnu => {
4628 const cross_target = std.zig.CrossTarget.parse(.{ .arch_os_abi = "native-windows-gnu" }) catch unreachable;
4629 const target = cross_target.toTarget();
4630 const is_native_abi = cross_target.isNativeAbi();
4631 const detected_libc = try Compilation.detectLibCIncludeDirs(arena, zig_lib_dir, target, is_native_abi, true, null);
4632 return .{
4633 .include_paths = detected_libc.libc_include_dir_list,
4634 .target_abi = "gnu",
4635 };
4636 },
4637 .none => unreachable,
4638 }
4639 }
4640}
4641
4375pub const usage_libc =4642pub const usage_libc =
4376 \\Usage: zig libc4643 \\Usage: zig libc
4377 \\4644 \\
src/resinator.zig+1
...@@ -17,6 +17,7 @@ pub const lang = @import("resinator/lang.zig");...@@ -17,6 +17,7 @@ pub const lang = @import("resinator/lang.zig");
17pub const lex = @import("resinator/lex.zig");17pub const lex = @import("resinator/lex.zig");
18pub const literals = @import("resinator/literals.zig");18pub const literals = @import("resinator/literals.zig");
19pub const parse = @import("resinator/parse.zig");19pub const parse = @import("resinator/parse.zig");
20pub const preprocess = @import("resinator/preprocess.zig");
20pub const rc = @import("resinator/rc.zig");21pub const rc = @import("resinator/rc.zig");
21pub const res = @import("resinator/res.zig");22pub const res = @import("resinator/res.zig");
22pub const source_mapping = @import("resinator/source_mapping.zig");23pub const source_mapping = @import("resinator/source_mapping.zig");
src/resinator/cli.zig+8-2
...@@ -8,8 +8,8 @@ const lex = @import("lex.zig");...@@ -8,8 +8,8 @@ const lex = @import("lex.zig");
8/// This is what /SL 100 will set the maximum string literal length to8/// This is what /SL 100 will set the maximum string literal length to
9pub const max_string_literal_length_100_percent = 8192;9pub const max_string_literal_length_100_percent = 8192;
1010
11pub const usage_string =11pub const usage_string_after_command_name =
12 \\Usage: resinator [options] [--] <INPUT> [<OUTPUT>]12 \\ [options] [--] <INPUT> [<OUTPUT>]
13 \\13 \\
14 \\The sequence -- can be used to signify when to stop parsing options.14 \\The sequence -- can be used to signify when to stop parsing options.
15 \\This is necessary when the input path begins with a forward slash.15 \\This is necessary when the input path begins with a forward slash.
...@@ -57,6 +57,12 @@ pub const usage_string =...@@ -57,6 +57,12 @@ pub const usage_string =
57 \\57 \\
58;58;
5959
60pub fn writeUsage(writer: anytype, command_name: []const u8) !void {
61 try writer.writeAll("Usage: ");
62 try writer.writeAll(command_name);
63 try writer.writeAll(usage_string_after_command_name);
64}
65
60pub const Diagnostics = struct {66pub const Diagnostics = struct {
61 errors: std.ArrayListUnmanaged(ErrorDetails) = .{},67 errors: std.ArrayListUnmanaged(ErrorDetails) = .{},
62 allocator: Allocator,68 allocator: Allocator,
src/resinator/preprocess.zig created+94
...@@ -0,0 +1,94 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const cli = @import("cli.zig");
4
5pub const IncludeArgs = struct {
6 clang_target: ?[]const u8 = null,
7 system_include_paths: []const []const u8,
8 /// Should be set to `true` when -target has the GNU abi
9 /// (either because `clang_target` has `-gnu` or `-target`
10 /// is appended via other means and it has `-gnu`)
11 needs_gnu_workaround: bool = false,
12 nostdinc: bool = false,
13
14 pub const IncludeAbi = enum {
15 msvc,
16 gnu,
17 };
18};
19
20/// `arena` is used for temporary -D argument strings and the INCLUDE environment variable.
21/// The arena should be kept alive at least as long as `argv`.
22pub fn appendClangArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, include_args: IncludeArgs) !void {
23 try argv.appendSlice(&[_][]const u8{
24 "-E", // preprocessor only
25 "--comments",
26 "-fuse-line-directives", // #line <num> instead of # <num>
27 // TODO: could use --trace-includes to give info about what's included from where
28 "-xc", // output c
29 // TODO: Turn this off, check the warnings, and convert the spaces back to NUL
30 "-Werror=null-character", // error on null characters instead of converting them to spaces
31 // TODO: could remove -Werror=null-character and instead parse warnings looking for 'warning: null character ignored'
32 // since the only real problem is when clang doesn't preserve null characters
33 //"-Werror=invalid-pp-token", // will error on unfinished string literals
34 // TODO: could use -Werror instead
35 "-fms-compatibility", // Allow things like "header.h" to be resolved relative to the 'root' .rc file, among other things
36 // https://learn.microsoft.com/en-us/windows/win32/menurc/predefined-macros
37 "-DRC_INVOKED",
38 });
39 for (options.extra_include_paths.items) |extra_include_path| {
40 try argv.append("-I");
41 try argv.append(extra_include_path);
42 }
43
44 if (include_args.nostdinc) {
45 try argv.append("-nostdinc");
46 }
47 for (include_args.system_include_paths) |include_path| {
48 try argv.append("-isystem");
49 try argv.append(include_path);
50 }
51 if (include_args.clang_target) |target| {
52 try argv.append("-target");
53 try argv.append(target);
54 }
55 // Using -fms-compatibility and targeting the GNU abi interact in a strange way:
56 // - Targeting the GNU abi stops _MSC_VER from being defined
57 // - Passing -fms-compatibility stops __GNUC__ from being defined
58 // Neither being defined is a problem for things like MinGW's vadefs.h,
59 // which will fail during preprocessing if neither are defined.
60 // So, when targeting the GNU abi, we need to force __GNUC__ to be defined.
61 //
62 // TODO: This is a workaround that should be removed if possible.
63 if (include_args.needs_gnu_workaround) {
64 // This is the same default gnuc version that Clang uses:
65 // https://github.com/llvm/llvm-project/blob/4b5366c9512aa273a5272af1d833961e1ed156e7/clang/lib/Driver/ToolChains/Clang.cpp#L6738
66 try argv.append("-fgnuc-version=4.2.1");
67 }
68
69 if (!options.ignore_include_env_var) {
70 const INCLUDE = std.process.getEnvVarOwned(arena, "INCLUDE") catch "";
71
72 // TODO: Should this be platform-specific? How does windres/llvm-rc handle this (if at all)?
73 var it = std.mem.tokenize(u8, INCLUDE, ";");
74 while (it.next()) |include_path| {
75 try argv.append("-isystem");
76 try argv.append(include_path);
77 }
78 }
79
80 var symbol_it = options.symbols.iterator();
81 while (symbol_it.next()) |entry| {
82 switch (entry.value_ptr.*) {
83 .define => |value| {
84 try argv.append("-D");
85 const define_arg = try std.fmt.allocPrint(arena, "{s}={s}", .{ entry.key_ptr.*, value });
86 try argv.append(define_arg);
87 },
88 .undefine => {
89 try argv.append("-U");
90 try argv.append(entry.key_ptr.*);
91 },
92 }
93 }
94}
src/resinator/utils.zig+29
...@@ -81,3 +81,32 @@ pub fn isNonAsciiDigit(c: u21) bool {...@@ -81,3 +81,32 @@ pub fn isNonAsciiDigit(c: u21) bool {
81 else => false,81 else => false,
82 };82 };
83}83}
84
85/// Used for generic colored errors/warnings/notes, more context-specific error messages
86/// are handled elsewhere.
87pub fn renderErrorMessage(writer: anytype, config: std.io.tty.Config, msg_type: enum { err, warning, note }, comptime format: []const u8, args: anytype) !void {
88 switch (msg_type) {
89 .err => {
90 try config.setColor(writer, .bold);
91 try config.setColor(writer, .red);
92 try writer.writeAll("error: ");
93 },
94 .warning => {
95 try config.setColor(writer, .bold);
96 try config.setColor(writer, .yellow);
97 try writer.writeAll("warning: ");
98 },
99 .note => {
100 try config.setColor(writer, .reset);
101 try config.setColor(writer, .cyan);
102 try writer.writeAll("note: ");
103 },
104 }
105 try config.setColor(writer, .reset);
106 if (msg_type == .err) {
107 try config.setColor(writer, .bold);
108 }
109 try writer.print(format, args);
110 try writer.writeByte('\n');
111 try config.setColor(writer, .reset);
112}