| ... | @@ -104,6 +104,7 @@ const normal_usage = | ... | @@ -104,6 +104,7 @@ const normal_usage = |
| 104 | \\ lib Use Zig as a drop-in lib.exe | 104 | \\ lib Use Zig as a drop-in lib.exe |
| 105 | \\ ranlib Use Zig as a drop-in ranlib | 105 | \\ ranlib Use Zig as a drop-in ranlib |
| 106 | \\ objcopy Use Zig as a drop-in objcopy | 106 | \\ 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 version | 109 | \\ env Print lib path, std path, cache directory, and version |
| 109 | \\ help Print this help and exit | 110 | \\ 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 | } |
| 4374 | | 4377 | |
| | 4378 | fn 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 | |
| | 4582 | const RcIncludeArgs = struct { |
| | 4583 | include_paths: []const []const u8 = &.{}, |
| | 4584 | target_abi: ?[]const u8 = null, |
| | 4585 | }; |
| | 4586 | |
| | 4587 | fn 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 | |
| 4375 | pub const usage_libc = | 4642 | pub const usage_libc = |
| 4376 | \\Usage: zig libc | 4643 | \\Usage: zig libc |
| 4377 | \\ | 4644 | \\ |