mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
zig rc: Add COFF object file creation for CMake cross-compilation use case
In #22522 I said:
> RC="zig rc" will now work in combination with zig cc and CMake. Here's an example of cross-compiling a simple Windows GUI CMake project
>
> $ RC="zig rc" CC="zig cc --target=x86_64-windows-gnu" cmake .. -DCMAKE_SYSTEM_NAME=Windows -G Ninja
However, I didn't realize that the time that this only works because of the `-G Ninja` part. When not using Ninja as the build tool, CMake adds a workaround for 'very long lists of object files' where it takes all object files and runs them through `ar` to combine them into one archive:
4a11fd8dde/Modules/Platform/Windows-GNU.cmake (L141-L158)
This is a problem for the Windows resource use-case, because `ar` doesn't know how to deal with `.res` files and so this object combining step fails with:
unknown file type: foo.rc.res
Only the linker knows what to do with .res files (since it has its own `.res` -> `.obj` ('cvtres') conversion mechanism). So, when using Ninja, this object file combining step is skipped and the .res file gets passed to the linker and everyone is happy.
Note: When CMake thinks that its using `windres` as the Windows resource compiler, it will pass `-O coff` to windres which causes it to output a COFF object file instead of a `.res` file, which means that the `ar` step can succeed because it's only working on actual object files.
---
This commit gives `zig rc` the ability to output COFF object files directly when `/:output-format coff` is provided as an argument. This effectively matches what happens when CMake uses `windres` for resource compilation, but requires the argument to be provided explicitly.
So, after this change, the following CMake cross-compilation use case will work, even when not using Ninja as the generator:
RC="zig rc /:output-format coff" CC="zig cc --target=x86_64-windows-gnu" cmake .. -DCMAKE_SYSTEM_NAME=Windows
This commit is contained in:
parent
8683f25d24
commit
a502301b5e
3 changed files with 2012 additions and 154 deletions
|
|
@ -5,6 +5,7 @@ const lang = @import("lang.zig");
|
||||||
const res = @import("res.zig");
|
const res = @import("res.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const lex = @import("lex.zig");
|
const lex = @import("lex.zig");
|
||||||
|
const cvtres = @import("cvtres.zig");
|
||||||
|
|
||||||
/// This is what /SL 100 will set the maximum string literal length to
|
/// This is what /SL 100 will set the maximum string literal length to
|
||||||
pub const max_string_literal_length_100_percent = 8192;
|
pub const max_string_literal_length_100_percent = 8192;
|
||||||
|
|
@ -59,6 +60,20 @@ pub const usage_string_after_command_name =
|
||||||
\\ the .rc includes or otherwise depends on.
|
\\ the .rc includes or otherwise depends on.
|
||||||
\\ /:depfile-fmt <value> Output format of the depfile, if /:depfile is set.
|
\\ /:depfile-fmt <value> Output format of the depfile, if /:depfile is set.
|
||||||
\\ json (default) A top-level JSON array of paths
|
\\ json (default) A top-level JSON array of paths
|
||||||
|
\\ /:input-format <value> If not specified, the input format is inferred.
|
||||||
|
\\ rc (default if input format cannot be inferred)
|
||||||
|
\\ res Compiled .rc file, implies /:output-format coff
|
||||||
|
\\ rcpp Preprocessed .rc file, implies /:no-preprocess
|
||||||
|
\\ /:output-format <value> If not specified, the output format is inferred.
|
||||||
|
\\ res (default if output format cannot be inferred)
|
||||||
|
\\ coff COFF object file (extension: .obj or .o)
|
||||||
|
\\ rcpp Preprocessed .rc file, implies /p
|
||||||
|
\\ /:target <arch> Set the target machine for COFF object files.
|
||||||
|
\\ Can be specified either as PE/COFF machine constant
|
||||||
|
\\ name (X64, ARM64, etc) or Zig/LLVM CPU name (x86_64,
|
||||||
|
\\ aarch64, etc). The default is X64 (aka x86_64).
|
||||||
|
\\ Also accepts a full Zig/LLVM triple, but everything
|
||||||
|
\\ except the architecture is ignored.
|
||||||
\\
|
\\
|
||||||
\\Note: For compatibility reasons, all custom options start with :
|
\\Note: For compatibility reasons, all custom options start with :
|
||||||
\\
|
\\
|
||||||
|
|
@ -131,8 +146,8 @@ pub const Diagnostics = struct {
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
input_filename: []const u8 = &[_]u8{},
|
input_source: IoSource = .{ .filename = &[_]u8{} },
|
||||||
output_filename: []const u8 = &[_]u8{},
|
output_source: IoSource = .{ .filename = &[_]u8{} },
|
||||||
extra_include_paths: std.ArrayListUnmanaged([]const u8) = .empty,
|
extra_include_paths: std.ArrayListUnmanaged([]const u8) = .empty,
|
||||||
ignore_include_env_var: bool = false,
|
ignore_include_env_var: bool = false,
|
||||||
preprocess: Preprocess = .yes,
|
preprocess: Preprocess = .yes,
|
||||||
|
|
@ -149,9 +164,30 @@ pub const Options = struct {
|
||||||
auto_includes: AutoIncludes = .any,
|
auto_includes: AutoIncludes = .any,
|
||||||
depfile_path: ?[]const u8 = null,
|
depfile_path: ?[]const u8 = null,
|
||||||
depfile_fmt: DepfileFormat = .json,
|
depfile_fmt: DepfileFormat = .json,
|
||||||
|
input_format: InputFormat = .rc,
|
||||||
|
output_format: OutputFormat = .res,
|
||||||
|
coff_options: cvtres.CoffOptions = .{},
|
||||||
|
|
||||||
|
pub const IoSource = union(enum) {
|
||||||
|
stdio: std.fs.File,
|
||||||
|
filename: []const u8,
|
||||||
|
};
|
||||||
pub const AutoIncludes = enum { any, msvc, gnu, none };
|
pub const AutoIncludes = enum { any, msvc, gnu, none };
|
||||||
pub const DepfileFormat = enum { json };
|
pub const DepfileFormat = enum { json };
|
||||||
|
pub const InputFormat = enum { rc, res, rcpp };
|
||||||
|
pub const OutputFormat = enum {
|
||||||
|
res,
|
||||||
|
coff,
|
||||||
|
rcpp,
|
||||||
|
|
||||||
|
pub fn extension(format: OutputFormat) []const u8 {
|
||||||
|
return switch (format) {
|
||||||
|
.rcpp => ".rcpp",
|
||||||
|
.coff => ".obj",
|
||||||
|
.res => ".res",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
pub const Preprocess = enum { no, yes, only };
|
pub const Preprocess = enum { no, yes, only };
|
||||||
pub const SymbolAction = enum { define, undefine };
|
pub const SymbolAction = enum { define, undefine };
|
||||||
pub const SymbolValue = union(SymbolAction) {
|
pub const SymbolValue = union(SymbolAction) {
|
||||||
|
|
@ -198,9 +234,10 @@ pub const Options = struct {
|
||||||
try self.symbols.put(self.allocator, duped_key, .{ .undefine = {} });
|
try self.symbols.put(self.allocator, duped_key, .{ .undefine = {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the current input filename both:
|
/// If the current input filename:
|
||||||
/// - does not have an extension, and
|
/// - does not have an extension, and
|
||||||
/// - does not exist in the cwd
|
/// - does not exist in the cwd, and
|
||||||
|
/// - the input format is .rc
|
||||||
/// then this function will append `.rc` to the input filename
|
/// then this function will append `.rc` to the input filename
|
||||||
///
|
///
|
||||||
/// Note: This behavior is different from the Win32 compiler.
|
/// Note: This behavior is different from the Win32 compiler.
|
||||||
|
|
@ -213,14 +250,18 @@ pub const Options = struct {
|
||||||
/// of the .rc extension being omitted from the CLI args, but still
|
/// of the .rc extension being omitted from the CLI args, but still
|
||||||
/// work fine if the file itself does not have an extension.
|
/// work fine if the file itself does not have an extension.
|
||||||
pub fn maybeAppendRC(options: *Options, cwd: std.fs.Dir) !void {
|
pub fn maybeAppendRC(options: *Options, cwd: std.fs.Dir) !void {
|
||||||
if (std.fs.path.extension(options.input_filename).len == 0) {
|
switch (options.input_source) {
|
||||||
cwd.access(options.input_filename, .{}) catch |err| switch (err) {
|
.stdio => return,
|
||||||
|
.filename => {},
|
||||||
|
}
|
||||||
|
if (options.input_format == .rc and std.fs.path.extension(options.input_source.filename).len == 0) {
|
||||||
|
cwd.access(options.input_source.filename, .{}) catch |err| switch (err) {
|
||||||
error.FileNotFound => {
|
error.FileNotFound => {
|
||||||
var filename_bytes = try options.allocator.alloc(u8, options.input_filename.len + 3);
|
var filename_bytes = try options.allocator.alloc(u8, options.input_source.filename.len + 3);
|
||||||
@memcpy(filename_bytes[0..options.input_filename.len], options.input_filename);
|
@memcpy(filename_bytes[0..options.input_source.filename.len], options.input_source.filename);
|
||||||
@memcpy(filename_bytes[filename_bytes.len - 3 ..], ".rc");
|
@memcpy(filename_bytes[filename_bytes.len - 3 ..], ".rc");
|
||||||
options.allocator.free(options.input_filename);
|
options.allocator.free(options.input_source.filename);
|
||||||
options.input_filename = filename_bytes;
|
options.input_source = .{ .filename = filename_bytes };
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
};
|
};
|
||||||
|
|
@ -232,8 +273,14 @@ pub const Options = struct {
|
||||||
self.allocator.free(extra_include_path);
|
self.allocator.free(extra_include_path);
|
||||||
}
|
}
|
||||||
self.extra_include_paths.deinit(self.allocator);
|
self.extra_include_paths.deinit(self.allocator);
|
||||||
self.allocator.free(self.input_filename);
|
switch (self.input_source) {
|
||||||
self.allocator.free(self.output_filename);
|
.stdio => {},
|
||||||
|
.filename => |filename| self.allocator.free(filename),
|
||||||
|
}
|
||||||
|
switch (self.output_source) {
|
||||||
|
.stdio => {},
|
||||||
|
.filename => |filename| self.allocator.free(filename),
|
||||||
|
}
|
||||||
var symbol_it = self.symbols.iterator();
|
var symbol_it = self.symbols.iterator();
|
||||||
while (symbol_it.next()) |entry| {
|
while (symbol_it.next()) |entry| {
|
||||||
self.allocator.free(entry.key_ptr.*);
|
self.allocator.free(entry.key_ptr.*);
|
||||||
|
|
@ -243,11 +290,26 @@ pub const Options = struct {
|
||||||
if (self.depfile_path) |depfile_path| {
|
if (self.depfile_path) |depfile_path| {
|
||||||
self.allocator.free(depfile_path);
|
self.allocator.free(depfile_path);
|
||||||
}
|
}
|
||||||
|
if (self.coff_options.define_external_symbol) |symbol_name| {
|
||||||
|
self.allocator.free(symbol_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dumpVerbose(self: *const Options, writer: anytype) !void {
|
pub fn dumpVerbose(self: *const Options, writer: anytype) !void {
|
||||||
try writer.print("Input filename: {s}\n", .{self.input_filename});
|
const input_source_name = switch (self.input_source) {
|
||||||
try writer.print("Output filename: {s}\n", .{self.output_filename});
|
.stdio => "<stdin>",
|
||||||
|
.filename => |filename| filename,
|
||||||
|
};
|
||||||
|
const output_source_name = switch (self.output_source) {
|
||||||
|
.stdio => "<stdout>",
|
||||||
|
.filename => |filename| filename,
|
||||||
|
};
|
||||||
|
try writer.print("Input filename: {s} (format={s})\n", .{ input_source_name, @tagName(self.input_format) });
|
||||||
|
try writer.print("Output filename: {s} (format={s})\n", .{ output_source_name, @tagName(self.output_format) });
|
||||||
|
if (self.output_format == .coff) {
|
||||||
|
try writer.print(" Target machine type for COFF: {s}\n", .{@tagName(self.coff_options.target)});
|
||||||
|
}
|
||||||
|
|
||||||
if (self.extra_include_paths.items.len > 0) {
|
if (self.extra_include_paths.items.len > 0) {
|
||||||
try writer.writeAll(" Extra include paths:\n");
|
try writer.writeAll(" Extra include paths:\n");
|
||||||
for (self.extra_include_paths.items) |extra_include_path| {
|
for (self.extra_include_paths.items) |extra_include_path| {
|
||||||
|
|
@ -331,6 +393,7 @@ pub const Arg = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn optionWithoutPrefix(self: Arg, option_len: usize) []const u8 {
|
pub fn optionWithoutPrefix(self: Arg, option_len: usize) []const u8 {
|
||||||
|
if (option_len == 0) return self.name();
|
||||||
return self.name()[0..option_len];
|
return self.name()[0..option_len];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,6 +443,8 @@ pub const Arg = struct {
|
||||||
|
|
||||||
pub const Value = struct {
|
pub const Value = struct {
|
||||||
slice: []const u8,
|
slice: []const u8,
|
||||||
|
/// Amount to increment the arg index to skip over both the option and the value arg(s)
|
||||||
|
/// e.g. 1 if /<option><value>, 2 if /<option> <value>
|
||||||
index_increment: u2 = 1,
|
index_increment: u2 = 1,
|
||||||
|
|
||||||
pub fn argSpan(self: Value, arg: Arg) Diagnostics.ErrorDetails.ArgSpan {
|
pub fn argSpan(self: Value, arg: Arg) Diagnostics.ErrorDetails.ArgSpan {
|
||||||
|
|
@ -414,6 +479,7 @@ pub const Arg = struct {
|
||||||
|
|
||||||
pub const Context = struct {
|
pub const Context = struct {
|
||||||
index: usize,
|
index: usize,
|
||||||
|
option_len: usize,
|
||||||
arg: Arg,
|
arg: Arg,
|
||||||
value: Value,
|
value: Value,
|
||||||
};
|
};
|
||||||
|
|
@ -428,7 +494,18 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
errdefer options.deinit();
|
errdefer options.deinit();
|
||||||
|
|
||||||
var output_filename: ?[]const u8 = null;
|
var output_filename: ?[]const u8 = null;
|
||||||
var output_filename_context: Arg.Context = undefined;
|
var output_filename_context: union(enum) {
|
||||||
|
unspecified: void,
|
||||||
|
positional: usize,
|
||||||
|
arg: Arg.Context,
|
||||||
|
} = .{ .unspecified = {} };
|
||||||
|
var output_format: ?Options.OutputFormat = null;
|
||||||
|
var output_format_context: Arg.Context = undefined;
|
||||||
|
var input_format: ?Options.InputFormat = null;
|
||||||
|
var input_format_context: Arg.Context = undefined;
|
||||||
|
var input_filename_arg_i: usize = undefined;
|
||||||
|
var preprocess_only_context: Arg.Context = undefined;
|
||||||
|
var depfile_context: Arg.Context = undefined;
|
||||||
|
|
||||||
var arg_i: usize = 0;
|
var arg_i: usize = 0;
|
||||||
next_arg: while (arg_i < args.len) {
|
next_arg: while (arg_i < args.len) {
|
||||||
|
|
@ -470,6 +547,25 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
if (std.ascii.startsWithIgnoreCase(arg_name, ":no-preprocess")) {
|
if (std.ascii.startsWithIgnoreCase(arg_name, ":no-preprocess")) {
|
||||||
options.preprocess = .no;
|
options.preprocess = .no;
|
||||||
arg.name_offset += ":no-preprocess".len;
|
arg.name_offset += ":no-preprocess".len;
|
||||||
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":output-format")) {
|
||||||
|
const value = arg.value(":output-format".len, arg_i, args) catch {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":output-format".len) });
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
arg_i += 1;
|
||||||
|
break :next_arg;
|
||||||
|
};
|
||||||
|
output_format = std.meta.stringToEnum(Options.OutputFormat, value.slice) orelse blk: {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("invalid output format setting: {s} ", .{value.slice});
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
break :blk output_format;
|
||||||
|
};
|
||||||
|
output_format_context = .{ .index = arg_i, .option_len = ":output-format".len, .arg = arg, .value = value };
|
||||||
|
arg_i += value.index_increment;
|
||||||
|
continue :next_arg;
|
||||||
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":auto-includes")) {
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":auto-includes")) {
|
||||||
const value = arg.value(":auto-includes".len, arg_i, args) catch {
|
const value = arg.value(":auto-includes".len, arg_i, args) catch {
|
||||||
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
||||||
|
|
@ -488,6 +584,25 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
};
|
};
|
||||||
arg_i += value.index_increment;
|
arg_i += value.index_increment;
|
||||||
continue :next_arg;
|
continue :next_arg;
|
||||||
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":input-format")) {
|
||||||
|
const value = arg.value(":input-format".len, arg_i, args) catch {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":input-format".len) });
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
arg_i += 1;
|
||||||
|
break :next_arg;
|
||||||
|
};
|
||||||
|
input_format = std.meta.stringToEnum(Options.InputFormat, value.slice) orelse blk: {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("invalid input format setting: {s} ", .{value.slice});
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
break :blk input_format;
|
||||||
|
};
|
||||||
|
input_format_context = .{ .index = arg_i, .option_len = ":input-format".len, .arg = arg, .value = value };
|
||||||
|
arg_i += value.index_increment;
|
||||||
|
continue :next_arg;
|
||||||
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":depfile-fmt")) {
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":depfile-fmt")) {
|
||||||
const value = arg.value(":depfile-fmt".len, arg_i, args) catch {
|
const value = arg.value(":depfile-fmt".len, arg_i, args) catch {
|
||||||
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
||||||
|
|
@ -522,6 +637,31 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
const path = try allocator.dupe(u8, value.slice);
|
const path = try allocator.dupe(u8, value.slice);
|
||||||
errdefer allocator.free(path);
|
errdefer allocator.free(path);
|
||||||
options.depfile_path = path;
|
options.depfile_path = path;
|
||||||
|
depfile_context = .{ .index = arg_i, .option_len = ":depfile".len, .arg = arg, .value = value };
|
||||||
|
arg_i += value.index_increment;
|
||||||
|
continue :next_arg;
|
||||||
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":target")) {
|
||||||
|
const value = arg.value(":target".len, arg_i, args) catch {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":target".len) });
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
arg_i += 1;
|
||||||
|
break :next_arg;
|
||||||
|
};
|
||||||
|
// Take the substring up to the first dash so that a full target triple
|
||||||
|
// can be used, e.g. x86_64-windows-gnu becomes x86_64
|
||||||
|
var target_it = std.mem.splitScalar(u8, value.slice, '-');
|
||||||
|
const arch_str = target_it.first();
|
||||||
|
const arch = cvtres.supported_targets.Arch.fromStringIgnoreCase(arch_str) orelse {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("invalid or unsupported target architecture: {s}", .{arch_str});
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
arg_i += value.index_increment;
|
||||||
|
continue :next_arg;
|
||||||
|
};
|
||||||
|
options.coff_options.target = arch.toCoffMachineType();
|
||||||
arg_i += value.index_increment;
|
arg_i += value.index_increment;
|
||||||
continue :next_arg;
|
continue :next_arg;
|
||||||
} else if (std.ascii.startsWithIgnoreCase(arg_name, "nologo")) {
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, "nologo")) {
|
||||||
|
|
@ -620,7 +760,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
arg_i += 1;
|
arg_i += 1;
|
||||||
break :next_arg;
|
break :next_arg;
|
||||||
};
|
};
|
||||||
output_filename_context = .{ .index = arg_i, .arg = arg, .value = value };
|
output_filename_context = .{ .arg = .{ .index = arg_i, .option_len = "fo".len, .arg = arg, .value = value } };
|
||||||
output_filename = value.slice;
|
output_filename = value.slice;
|
||||||
arg_i += value.index_increment;
|
arg_i += value.index_increment;
|
||||||
continue :next_arg;
|
continue :next_arg;
|
||||||
|
|
@ -812,6 +952,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
arg.name_offset += 1;
|
arg.name_offset += 1;
|
||||||
} else if (std.ascii.startsWithIgnoreCase(arg_name, "p")) {
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, "p")) {
|
||||||
options.preprocess = .only;
|
options.preprocess = .only;
|
||||||
|
preprocess_only_context = .{ .index = arg_i, .option_len = "p".len, .arg = arg, .value = undefined };
|
||||||
arg.name_offset += 1;
|
arg.name_offset += 1;
|
||||||
} else if (std.ascii.startsWithIgnoreCase(arg_name, "i")) {
|
} else if (std.ascii.startsWithIgnoreCase(arg_name, "i")) {
|
||||||
const value = arg.value(1, arg_i, args) catch {
|
const value = arg.value(1, arg_i, args) catch {
|
||||||
|
|
@ -920,10 +1061,10 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
|
|
||||||
if (args.len > 0) {
|
if (args.len > 0) {
|
||||||
const last_arg = args[args.len - 1];
|
const last_arg = args[args.len - 1];
|
||||||
if (arg_i > 0 and last_arg.len > 0 and last_arg[0] == '/' and std.ascii.endsWithIgnoreCase(last_arg, ".rc")) {
|
if (arg_i > 0 and last_arg.len > 0 and last_arg[0] == '/' and isSupportedInputExtension(std.fs.path.extension(last_arg))) {
|
||||||
var note_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i - 1 };
|
var note_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i - 1 };
|
||||||
var note_writer = note_details.msg.writer(allocator);
|
var note_writer = note_details.msg.writer(allocator);
|
||||||
try note_writer.writeAll("if this argument was intended to be the input filename, then -- should be specified in front of it to exclude it from option parsing");
|
try note_writer.writeAll("if this argument was intended to be the input filename, adding -- in front of it will exclude it from option parsing");
|
||||||
try diagnostics.append(note_details);
|
try diagnostics.append(note_details);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -932,7 +1073,28 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
// things after this rely on the value of the input filename.
|
// things after this rely on the value of the input filename.
|
||||||
return error.ParseError;
|
return error.ParseError;
|
||||||
}
|
}
|
||||||
options.input_filename = try allocator.dupe(u8, positionals[0]);
|
options.input_source = .{ .filename = try allocator.dupe(u8, positionals[0]) };
|
||||||
|
input_filename_arg_i = arg_i;
|
||||||
|
|
||||||
|
const InputFormatSource = enum {
|
||||||
|
inferred_from_input_filename,
|
||||||
|
input_format_arg,
|
||||||
|
};
|
||||||
|
|
||||||
|
var input_format_source: InputFormatSource = undefined;
|
||||||
|
if (input_format == null) {
|
||||||
|
const ext = std.fs.path.extension(options.input_source.filename);
|
||||||
|
if (std.ascii.eqlIgnoreCase(ext, ".res")) {
|
||||||
|
input_format = .res;
|
||||||
|
} else if (std.ascii.eqlIgnoreCase(ext, ".rcpp")) {
|
||||||
|
input_format = .rcpp;
|
||||||
|
} else {
|
||||||
|
input_format = .rc;
|
||||||
|
}
|
||||||
|
input_format_source = .inferred_from_input_filename;
|
||||||
|
} else {
|
||||||
|
input_format_source = .input_format_arg;
|
||||||
|
}
|
||||||
|
|
||||||
if (positionals.len > 1) {
|
if (positionals.len > 1) {
|
||||||
if (output_filename != null) {
|
if (output_filename != null) {
|
||||||
|
|
@ -942,53 +1104,233 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
|
||||||
try diagnostics.append(err_details);
|
try diagnostics.append(err_details);
|
||||||
var note_details = Diagnostics.ErrorDetails{
|
var note_details = Diagnostics.ErrorDetails{
|
||||||
.type = .note,
|
.type = .note,
|
||||||
.arg_index = output_filename_context.value.index(output_filename_context.index),
|
.arg_index = output_filename_context.arg.index,
|
||||||
.arg_span = output_filename_context.value.argSpan(output_filename_context.arg),
|
.arg_span = output_filename_context.arg.value.argSpan(output_filename_context.arg.arg),
|
||||||
};
|
};
|
||||||
var note_writer = note_details.msg.writer(allocator);
|
var note_writer = note_details.msg.writer(allocator);
|
||||||
try note_writer.writeAll("output filename previously specified here");
|
try note_writer.writeAll("output filename previously specified here");
|
||||||
try diagnostics.append(note_details);
|
try diagnostics.append(note_details);
|
||||||
} else {
|
} else {
|
||||||
output_filename = positionals[1];
|
output_filename = positionals[1];
|
||||||
|
output_filename_context = .{ .positional = arg_i + 1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OutputFormatSource = enum {
|
||||||
|
inferred_from_input_filename,
|
||||||
|
inferred_from_output_filename,
|
||||||
|
output_format_arg,
|
||||||
|
unable_to_infer_from_input_filename,
|
||||||
|
unable_to_infer_from_output_filename,
|
||||||
|
inferred_from_preprocess_only,
|
||||||
|
};
|
||||||
|
|
||||||
|
var output_format_source: OutputFormatSource = undefined;
|
||||||
if (output_filename == null) {
|
if (output_filename == null) {
|
||||||
var buf = std.ArrayList(u8).init(allocator);
|
if (output_format == null) {
|
||||||
errdefer buf.deinit();
|
output_format_source = .inferred_from_input_filename;
|
||||||
|
const input_ext = std.fs.path.extension(options.input_source.filename);
|
||||||
if (std.fs.path.dirname(options.input_filename)) |dirname| {
|
if (std.ascii.eqlIgnoreCase(input_ext, ".res")) {
|
||||||
var end_pos = dirname.len;
|
output_format = .coff;
|
||||||
// We want to ensure that we write a path separator at the end, so if the dirname
|
} else if (options.preprocess == .only and (input_format.? == .rc or std.ascii.eqlIgnoreCase(input_ext, ".rc"))) {
|
||||||
// doesn't end with a path sep then include the char after the dirname
|
output_format = .rcpp;
|
||||||
// which must be a path sep.
|
output_format_source = .inferred_from_preprocess_only;
|
||||||
if (!std.fs.path.isSep(dirname[dirname.len - 1])) end_pos += 1;
|
} else {
|
||||||
try buf.appendSlice(options.input_filename[0..end_pos]);
|
if (!std.ascii.eqlIgnoreCase(input_ext, ".res")) {
|
||||||
|
output_format_source = .unable_to_infer_from_input_filename;
|
||||||
|
}
|
||||||
|
output_format = .res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try buf.appendSlice(std.fs.path.stem(options.input_filename));
|
options.output_source = .{ .filename = try filepathWithExtension(allocator, options.input_source.filename, output_format.?.extension()) };
|
||||||
if (options.preprocess == .only) {
|
|
||||||
try buf.appendSlice(".rcpp");
|
|
||||||
} else {
|
|
||||||
try buf.appendSlice(".res");
|
|
||||||
}
|
|
||||||
|
|
||||||
options.output_filename = try buf.toOwnedSlice();
|
|
||||||
} else {
|
} else {
|
||||||
options.output_filename = try allocator.dupe(u8, output_filename.?);
|
options.output_source = .{ .filename = try allocator.dupe(u8, output_filename.?) };
|
||||||
|
if (output_format == null) {
|
||||||
|
output_format_source = .inferred_from_output_filename;
|
||||||
|
const ext = std.fs.path.extension(options.output_source.filename);
|
||||||
|
if (std.ascii.eqlIgnoreCase(ext, ".obj") or std.ascii.eqlIgnoreCase(ext, ".o")) {
|
||||||
|
output_format = .coff;
|
||||||
|
} else if (std.ascii.eqlIgnoreCase(ext, ".rcpp")) {
|
||||||
|
output_format = .rcpp;
|
||||||
|
} else {
|
||||||
|
if (!std.ascii.eqlIgnoreCase(ext, ".res")) {
|
||||||
|
output_format_source = .unable_to_infer_from_output_filename;
|
||||||
|
}
|
||||||
|
output_format = .res;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output_format_source = .output_format_arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.input_format = input_format.?;
|
||||||
|
options.output_format = output_format.?;
|
||||||
|
|
||||||
|
// Check for incompatible options
|
||||||
|
var print_input_format_source_note: bool = false;
|
||||||
|
var print_output_format_source_note: bool = false;
|
||||||
|
if (options.depfile_path != null and (options.input_format == .res or options.output_format == .rcpp)) {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .type = .warning, .arg_index = depfile_context.index, .arg_span = depfile_context.value.argSpan(depfile_context.arg) };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
if (options.input_format == .res) {
|
||||||
|
try msg_writer.print("the {s}{s} option was ignored because the input format is '{s}'", .{
|
||||||
|
depfile_context.arg.prefixSlice(),
|
||||||
|
depfile_context.arg.optionWithoutPrefix(depfile_context.option_len),
|
||||||
|
@tagName(options.input_format),
|
||||||
|
});
|
||||||
|
print_input_format_source_note = true;
|
||||||
|
} else if (options.output_format == .rcpp) {
|
||||||
|
try msg_writer.print("the {s}{s} option was ignored because the output format is '{s}'", .{
|
||||||
|
depfile_context.arg.prefixSlice(),
|
||||||
|
depfile_context.arg.optionWithoutPrefix(depfile_context.option_len),
|
||||||
|
@tagName(options.output_format),
|
||||||
|
});
|
||||||
|
print_output_format_source_note = true;
|
||||||
|
}
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
}
|
||||||
|
if (!isSupportedTransformation(options.input_format, options.output_format)) {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = input_filename_arg_i, .print_args = false };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("input format '{s}' cannot be converted to output format '{s}'", .{ @tagName(options.input_format), @tagName(options.output_format) });
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
print_input_format_source_note = true;
|
||||||
|
print_output_format_source_note = true;
|
||||||
|
}
|
||||||
|
if (options.preprocess == .only and options.output_format != .rcpp) {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .arg_index = preprocess_only_context.index };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("the {s}{s} option cannot be used with output format '{s}'", .{
|
||||||
|
preprocess_only_context.arg.prefixSlice(),
|
||||||
|
preprocess_only_context.arg.optionWithoutPrefix(preprocess_only_context.option_len),
|
||||||
|
@tagName(options.output_format),
|
||||||
|
});
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
print_output_format_source_note = true;
|
||||||
|
}
|
||||||
|
if (print_input_format_source_note) {
|
||||||
|
switch (input_format_source) {
|
||||||
|
.inferred_from_input_filename => {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .type = .note, .arg_index = input_filename_arg_i };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.writeAll("the input format was inferred from the input filename");
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
},
|
||||||
|
.input_format_arg => {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{
|
||||||
|
.type = .note,
|
||||||
|
.arg_index = input_format_context.index,
|
||||||
|
.arg_span = input_format_context.value.argSpan(input_format_context.arg),
|
||||||
|
};
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.writeAll("the input format was specified here");
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (print_output_format_source_note) {
|
||||||
|
switch (output_format_source) {
|
||||||
|
.inferred_from_input_filename, .unable_to_infer_from_input_filename => {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .type = .note, .arg_index = input_filename_arg_i };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
if (output_format_source == .inferred_from_input_filename) {
|
||||||
|
try msg_writer.writeAll("the output format was inferred from the input filename");
|
||||||
|
} else {
|
||||||
|
try msg_writer.writeAll("the output format was unable to be inferred from the input filename, so the default was used");
|
||||||
|
}
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
},
|
||||||
|
.inferred_from_output_filename, .unable_to_infer_from_output_filename => {
|
||||||
|
var err_details: Diagnostics.ErrorDetails = switch (output_filename_context) {
|
||||||
|
.positional => |i| .{ .type = .note, .arg_index = i },
|
||||||
|
.arg => |ctx| .{ .type = .note, .arg_index = ctx.index, .arg_span = ctx.value.argSpan(ctx.arg) },
|
||||||
|
.unspecified => unreachable,
|
||||||
|
};
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
if (output_format_source == .inferred_from_output_filename) {
|
||||||
|
try msg_writer.writeAll("the output format was inferred from the output filename");
|
||||||
|
} else {
|
||||||
|
try msg_writer.writeAll("the output format was unable to be inferred from the output filename, so the default was used");
|
||||||
|
}
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
},
|
||||||
|
.output_format_arg => {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{
|
||||||
|
.type = .note,
|
||||||
|
.arg_index = output_format_context.index,
|
||||||
|
.arg_span = output_format_context.value.argSpan(output_format_context.arg),
|
||||||
|
};
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.writeAll("the output format was specified here");
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
},
|
||||||
|
.inferred_from_preprocess_only => {
|
||||||
|
var err_details = Diagnostics.ErrorDetails{ .type = .note, .arg_index = preprocess_only_context.index };
|
||||||
|
var msg_writer = err_details.msg.writer(allocator);
|
||||||
|
try msg_writer.print("the output format was inferred from the usage of the {s}{s} option", .{
|
||||||
|
preprocess_only_context.arg.prefixSlice(),
|
||||||
|
preprocess_only_context.arg.optionWithoutPrefix(preprocess_only_context.option_len),
|
||||||
|
});
|
||||||
|
try diagnostics.append(err_details);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diagnostics.hasError()) {
|
if (diagnostics.hasError()) {
|
||||||
return error.ParseError;
|
return error.ParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implied settings from input/output formats
|
||||||
|
if (options.output_format == .rcpp) options.preprocess = .only;
|
||||||
|
if (options.input_format == .res) options.output_format = .coff;
|
||||||
|
if (options.input_format == .rcpp) options.preprocess = .no;
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn filepathWithExtension(allocator: Allocator, path: []const u8, ext: []const u8) ![]const u8 {
|
||||||
|
var buf = std.ArrayList(u8).init(allocator);
|
||||||
|
errdefer buf.deinit();
|
||||||
|
if (std.fs.path.dirname(path)) |dirname| {
|
||||||
|
var end_pos = dirname.len;
|
||||||
|
// We want to ensure that we write a path separator at the end, so if the dirname
|
||||||
|
// doesn't end with a path sep then include the char after the dirname
|
||||||
|
// which must be a path sep.
|
||||||
|
if (!std.fs.path.isSep(dirname[dirname.len - 1])) end_pos += 1;
|
||||||
|
try buf.appendSlice(path[0..end_pos]);
|
||||||
|
}
|
||||||
|
try buf.appendSlice(std.fs.path.stem(path));
|
||||||
|
try buf.appendSlice(ext);
|
||||||
|
return try buf.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn isSupportedInputExtension(ext: []const u8) bool {
|
pub fn isSupportedInputExtension(ext: []const u8) bool {
|
||||||
if (std.ascii.eqlIgnoreCase(ext, ".rc")) return true;
|
if (std.ascii.eqlIgnoreCase(ext, ".rc")) return true;
|
||||||
|
if (std.ascii.eqlIgnoreCase(ext, ".res")) return true;
|
||||||
if (std.ascii.eqlIgnoreCase(ext, ".rcpp")) return true;
|
if (std.ascii.eqlIgnoreCase(ext, ".rcpp")) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isSupportedTransformation(input: Options.InputFormat, output: Options.OutputFormat) bool {
|
||||||
|
return switch (input) {
|
||||||
|
.rc => switch (output) {
|
||||||
|
.res => true,
|
||||||
|
.coff => true,
|
||||||
|
.rcpp => true,
|
||||||
|
},
|
||||||
|
.res => switch (output) {
|
||||||
|
.res => false,
|
||||||
|
.coff => true,
|
||||||
|
.rcpp => false,
|
||||||
|
},
|
||||||
|
.rcpp => switch (output) {
|
||||||
|
.res => true,
|
||||||
|
.coff => true,
|
||||||
|
.rcpp => false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the str is a valid C identifier for use in a #define/#undef macro
|
/// Returns true if the str is a valid C identifier for use in a #define/#undef macro
|
||||||
pub fn isValidIdentifier(str: []const u8) bool {
|
pub fn isValidIdentifier(str: []const u8) bool {
|
||||||
for (str, 0..) |c, i| switch (c) {
|
for (str, 0..) |c, i| switch (c) {
|
||||||
|
|
@ -1278,17 +1620,6 @@ test "parse errors: basic" {
|
||||||
\\
|
\\
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
try testParseError(&.{"/some/absolute/path/parsed/as/an/option.rc"},
|
|
||||||
\\<cli>: error: the /s option is unsupported
|
|
||||||
\\ ... /some/absolute/path/parsed/as/an/option.rc
|
|
||||||
\\ ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
\\<cli>: error: missing input filename
|
|
||||||
\\
|
|
||||||
\\<cli>: note: if this argument was intended to be the input filename, then -- should be specified in front of it to exclude it from option parsing
|
|
||||||
\\ ... /some/absolute/path/parsed/as/an/option.rc
|
|
||||||
\\ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
\\
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "inferred absolute filepaths" {
|
test "inferred absolute filepaths" {
|
||||||
|
|
@ -1349,8 +1680,8 @@ test "parse: options" {
|
||||||
defer options.deinit();
|
defer options.deinit();
|
||||||
|
|
||||||
try std.testing.expectEqual(true, options.verbose);
|
try std.testing.expectEqual(true, options.verbose);
|
||||||
try std.testing.expectEqualStrings("foo.rc", options.input_filename);
|
try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
|
||||||
try std.testing.expectEqualStrings("foo.res", options.output_filename);
|
try std.testing.expectEqualStrings("foo.res", options.output_source.filename);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
var options = try testParse(&.{ "/vx", "foo.rc" });
|
var options = try testParse(&.{ "/vx", "foo.rc" });
|
||||||
|
|
@ -1358,8 +1689,8 @@ test "parse: options" {
|
||||||
|
|
||||||
try std.testing.expectEqual(true, options.verbose);
|
try std.testing.expectEqual(true, options.verbose);
|
||||||
try std.testing.expectEqual(true, options.ignore_include_env_var);
|
try std.testing.expectEqual(true, options.ignore_include_env_var);
|
||||||
try std.testing.expectEqualStrings("foo.rc", options.input_filename);
|
try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
|
||||||
try std.testing.expectEqualStrings("foo.res", options.output_filename);
|
try std.testing.expectEqualStrings("foo.res", options.output_source.filename);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
var options = try testParse(&.{ "/xv", "foo.rc" });
|
var options = try testParse(&.{ "/xv", "foo.rc" });
|
||||||
|
|
@ -1367,8 +1698,8 @@ test "parse: options" {
|
||||||
|
|
||||||
try std.testing.expectEqual(true, options.verbose);
|
try std.testing.expectEqual(true, options.verbose);
|
||||||
try std.testing.expectEqual(true, options.ignore_include_env_var);
|
try std.testing.expectEqual(true, options.ignore_include_env_var);
|
||||||
try std.testing.expectEqualStrings("foo.rc", options.input_filename);
|
try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
|
||||||
try std.testing.expectEqualStrings("foo.res", options.output_filename);
|
try std.testing.expectEqualStrings("foo.res", options.output_source.filename);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
var options = try testParse(&.{ "/xvFObar.res", "foo.rc" });
|
var options = try testParse(&.{ "/xvFObar.res", "foo.rc" });
|
||||||
|
|
@ -1376,8 +1707,8 @@ test "parse: options" {
|
||||||
|
|
||||||
try std.testing.expectEqual(true, options.verbose);
|
try std.testing.expectEqual(true, options.verbose);
|
||||||
try std.testing.expectEqual(true, options.ignore_include_env_var);
|
try std.testing.expectEqual(true, options.ignore_include_env_var);
|
||||||
try std.testing.expectEqualStrings("foo.rc", options.input_filename);
|
try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
|
||||||
try std.testing.expectEqualStrings("bar.res", options.output_filename);
|
try std.testing.expectEqualStrings("bar.res", options.output_source.filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1541,24 +1872,208 @@ test "parse: unsupported LCX/LCE-related options" {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "parse: output filename specified twice" {
|
||||||
|
try testParseError(&.{ "/fo", "foo.res", "foo.rc", "foo.res" },
|
||||||
|
\\<cli>: error: output filename already specified
|
||||||
|
\\ ... foo.res
|
||||||
|
\\ ^~~~~~~
|
||||||
|
\\<cli>: note: output filename previously specified here
|
||||||
|
\\ ... /fo foo.res ...
|
||||||
|
\\ ~~~~^~~~~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse: input and output formats" {
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "/:output-format", "rcpp", "foo.res" },
|
||||||
|
\\<cli>: error: input format 'res' cannot be converted to output format 'rcpp'
|
||||||
|
\\
|
||||||
|
\\<cli>: note: the input format was inferred from the input filename
|
||||||
|
\\ ... foo.res
|
||||||
|
\\ ^~~~~~~
|
||||||
|
\\<cli>: note: the output format was specified here
|
||||||
|
\\ ... /:output-format rcpp ...
|
||||||
|
\\ ~~~~~~~~~~~~~~~~^~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "foo.res", "foo.rcpp" },
|
||||||
|
\\<cli>: error: input format 'res' cannot be converted to output format 'rcpp'
|
||||||
|
\\
|
||||||
|
\\<cli>: note: the input format was inferred from the input filename
|
||||||
|
\\ ... foo.res ...
|
||||||
|
\\ ^~~~~~~
|
||||||
|
\\<cli>: note: the output format was inferred from the output filename
|
||||||
|
\\ ... foo.rcpp
|
||||||
|
\\ ^~~~~~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "/:input-format", "res", "foo" },
|
||||||
|
\\<cli>: error: input format 'res' cannot be converted to output format 'res'
|
||||||
|
\\
|
||||||
|
\\<cli>: note: the input format was specified here
|
||||||
|
\\ ... /:input-format res ...
|
||||||
|
\\ ~~~~~~~~~~~~~~~^~~
|
||||||
|
\\<cli>: note: the output format was unable to be inferred from the input filename, so the default was used
|
||||||
|
\\ ... foo
|
||||||
|
\\ ^~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "/p", "/:input-format", "res", "foo" },
|
||||||
|
\\<cli>: error: input format 'res' cannot be converted to output format 'res'
|
||||||
|
\\
|
||||||
|
\\<cli>: error: the /p option cannot be used with output format 'res'
|
||||||
|
\\ ... /p ...
|
||||||
|
\\ ^~
|
||||||
|
\\<cli>: note: the input format was specified here
|
||||||
|
\\ ... /:input-format res ...
|
||||||
|
\\ ~~~~~~~~~~~~~~~^~~
|
||||||
|
\\<cli>: note: the output format was unable to be inferred from the input filename, so the default was used
|
||||||
|
\\ ... foo
|
||||||
|
\\ ^~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "/:output-format", "coff", "/p", "foo.rc" },
|
||||||
|
\\<cli>: error: the /p option cannot be used with output format 'coff'
|
||||||
|
\\ ... /p ...
|
||||||
|
\\ ^~
|
||||||
|
\\<cli>: note: the output format was specified here
|
||||||
|
\\ ... /:output-format coff ...
|
||||||
|
\\ ~~~~~~~~~~~~~~~~^~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "/fo", "foo.res", "/p", "foo.rc" },
|
||||||
|
\\<cli>: error: the /p option cannot be used with output format 'res'
|
||||||
|
\\ ... /p ...
|
||||||
|
\\ ^~
|
||||||
|
\\<cli>: note: the output format was inferred from the output filename
|
||||||
|
\\ ... /fo foo.res ...
|
||||||
|
\\ ~~~~^~~~~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
try testParseError(&.{ "/p", "foo.rc", "foo.o" },
|
||||||
|
\\<cli>: error: the /p option cannot be used with output format 'coff'
|
||||||
|
\\ ... /p ...
|
||||||
|
\\ ^~
|
||||||
|
\\<cli>: note: the output format was inferred from the output filename
|
||||||
|
\\ ... foo.o
|
||||||
|
\\ ^~~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParse(&.{"foo.rc"});
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.rc, options.input_format);
|
||||||
|
try std.testing.expectEqual(.res, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParse(&.{"foo.rcpp"});
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.no, options.preprocess);
|
||||||
|
try std.testing.expectEqual(.rcpp, options.input_format);
|
||||||
|
try std.testing.expectEqual(.res, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParse(&.{ "foo.rc", "foo.rcpp" });
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.only, options.preprocess);
|
||||||
|
try std.testing.expectEqual(.rc, options.input_format);
|
||||||
|
try std.testing.expectEqual(.rcpp, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParse(&.{ "foo.rc", "foo.obj" });
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.rc, options.input_format);
|
||||||
|
try std.testing.expectEqual(.coff, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParse(&.{ "/fo", "foo.o", "foo.rc" });
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.rc, options.input_format);
|
||||||
|
try std.testing.expectEqual(.coff, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParse(&.{"foo.res"});
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.res, options.input_format);
|
||||||
|
try std.testing.expectEqual(.coff, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParseWarning(&.{ "/:depfile", "foo.json", "foo.rc", "foo.rcpp" },
|
||||||
|
\\<cli>: warning: the /:depfile option was ignored because the output format is 'rcpp'
|
||||||
|
\\ ... /:depfile foo.json ...
|
||||||
|
\\ ~~~~~~~~~~^~~~~~~~
|
||||||
|
\\<cli>: note: the output format was inferred from the output filename
|
||||||
|
\\ ... foo.rcpp
|
||||||
|
\\ ^~~~~~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.rc, options.input_format);
|
||||||
|
try std.testing.expectEqual(.rcpp, options.output_format);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var options = try testParseWarning(&.{ "/:depfile", "foo.json", "foo.res", "foo.o" },
|
||||||
|
\\<cli>: warning: the /:depfile option was ignored because the input format is 'res'
|
||||||
|
\\ ... /:depfile foo.json ...
|
||||||
|
\\ ~~~~~~~~~~^~~~~~~~
|
||||||
|
\\<cli>: note: the input format was inferred from the input filename
|
||||||
|
\\ ... foo.res ...
|
||||||
|
\\ ^~~~~~~
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
defer options.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.res, options.input_format);
|
||||||
|
try std.testing.expectEqual(.coff, options.output_format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "maybeAppendRC" {
|
test "maybeAppendRC" {
|
||||||
var tmp = std.testing.tmpDir(.{});
|
var tmp = std.testing.tmpDir(.{});
|
||||||
defer tmp.cleanup();
|
defer tmp.cleanup();
|
||||||
|
|
||||||
var options = try testParse(&.{"foo"});
|
var options = try testParse(&.{"foo"});
|
||||||
defer options.deinit();
|
defer options.deinit();
|
||||||
try std.testing.expectEqualStrings("foo", options.input_filename);
|
try std.testing.expectEqualStrings("foo", options.input_source.filename);
|
||||||
|
|
||||||
// Create the file so that it's found. In this scenario, .rc should not get
|
// Create the file so that it's found. In this scenario, .rc should not get
|
||||||
// appended.
|
// appended.
|
||||||
var file = try tmp.dir.createFile("foo", .{});
|
var file = try tmp.dir.createFile("foo", .{});
|
||||||
file.close();
|
file.close();
|
||||||
try options.maybeAppendRC(tmp.dir);
|
try options.maybeAppendRC(tmp.dir);
|
||||||
try std.testing.expectEqualStrings("foo", options.input_filename);
|
try std.testing.expectEqualStrings("foo", options.input_source.filename);
|
||||||
|
|
||||||
// Now delete the file and try again. Since the verbatim name is no longer found
|
// Now delete the file and try again. But this time change the input format
|
||||||
// and the input filename does not have an extension, .rc should get appended.
|
// to non-rc.
|
||||||
try tmp.dir.deleteFile("foo");
|
try tmp.dir.deleteFile("foo");
|
||||||
|
options.input_format = .res;
|
||||||
try options.maybeAppendRC(tmp.dir);
|
try options.maybeAppendRC(tmp.dir);
|
||||||
try std.testing.expectEqualStrings("foo.rc", options.input_filename);
|
try std.testing.expectEqualStrings("foo", options.input_source.filename);
|
||||||
|
|
||||||
|
// Finally, reset the input format to rc. Since the verbatim name is no longer found
|
||||||
|
// and the input filename does not have an extension, .rc should get appended.
|
||||||
|
options.input_format = .rc;
|
||||||
|
try options.maybeAppendRC(tmp.dir);
|
||||||
|
try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1125
lib/compiler/resinator/cvtres.zig
Normal file
1125
lib/compiler/resinator/cvtres.zig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,7 +7,10 @@ const Diagnostics = @import("errors.zig").Diagnostics;
|
||||||
const cli = @import("cli.zig");
|
const cli = @import("cli.zig");
|
||||||
const preprocess = @import("preprocess.zig");
|
const preprocess = @import("preprocess.zig");
|
||||||
const renderErrorMessage = @import("utils.zig").renderErrorMessage;
|
const renderErrorMessage = @import("utils.zig").renderErrorMessage;
|
||||||
|
const openFileNotDir = @import("utils.zig").openFileNotDir;
|
||||||
|
const cvtres = @import("cvtres.zig");
|
||||||
const hasDisjointCodePage = @import("disjoint_code_page.zig").hasDisjointCodePage;
|
const hasDisjointCodePage = @import("disjoint_code_page.zig").hasDisjointCodePage;
|
||||||
|
const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType;
|
||||||
const aro = @import("aro");
|
const aro = @import("aro");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
|
@ -135,7 +138,10 @@ pub fn main() !void {
|
||||||
|
|
||||||
try argv.append("arocc"); // dummy command name
|
try argv.append("arocc"); // dummy command name
|
||||||
try preprocess.appendAroArgs(aro_arena, &argv, options, include_paths);
|
try preprocess.appendAroArgs(aro_arena, &argv, options, include_paths);
|
||||||
try argv.append(options.input_filename);
|
try argv.append(switch (options.input_source) {
|
||||||
|
.stdio => "-",
|
||||||
|
.filename => |filename| filename,
|
||||||
|
});
|
||||||
|
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n");
|
try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n");
|
||||||
|
|
@ -164,121 +170,333 @@ pub fn main() !void {
|
||||||
|
|
||||||
break :full_input try preprocessed_buf.toOwnedSlice();
|
break :full_input try preprocessed_buf.toOwnedSlice();
|
||||||
} else {
|
} else {
|
||||||
break :full_input std.fs.cwd().readFileAlloc(allocator, options.input_filename, std.math.maxInt(usize)) catch |err| {
|
switch (options.input_source) {
|
||||||
try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ options.input_filename, @errorName(err) });
|
.stdio => |file| {
|
||||||
std.process.exit(1);
|
break :full_input file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
|
||||||
};
|
try error_handler.emitMessage(allocator, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.filename => |input_filename| {
|
||||||
|
break :full_input std.fs.cwd().readFileAlloc(allocator, input_filename, std.math.maxInt(usize)) catch |err| {
|
||||||
|
try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
defer allocator.free(full_input);
|
defer allocator.free(full_input);
|
||||||
|
|
||||||
if (options.preprocess == .only) {
|
if (options.preprocess == .only) {
|
||||||
try std.fs.cwd().writeFile(.{ .sub_path = options.output_filename, .data = full_input });
|
switch (options.output_source) {
|
||||||
|
.stdio => |output_file| {
|
||||||
|
try output_file.writeAll(full_input);
|
||||||
|
},
|
||||||
|
.filename => |output_filename| {
|
||||||
|
try std.fs.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input });
|
||||||
|
},
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: We still want to run this when no-preprocess is set because:
|
var resources = resources: {
|
||||||
// 1. We want to print accurate line numbers after removing multiline comments
|
const need_intermediate_res = options.output_format == .coff and options.input_format != .res;
|
||||||
// 2. We want to be able to handle an already-preprocessed input with #line commands in it
|
var res_stream = if (need_intermediate_res)
|
||||||
var mapping_results = parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_filename }) catch |err| switch (err) {
|
IoStream{
|
||||||
error.InvalidLineCommand => {
|
.name = "<in-memory intermediate res>",
|
||||||
// TODO: Maybe output the invalid line command
|
.intermediate = true,
|
||||||
try error_handler.emitMessage(allocator, .err, "invalid line command in the preprocessed source", .{});
|
.source = .{ .memory = .empty },
|
||||||
if (options.preprocess == .no) {
|
|
||||||
try error_handler.emitMessage(allocator, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
|
|
||||||
} else {
|
|
||||||
try error_handler.emitMessage(allocator, .note, "this is likely to be a bug, please report it", .{});
|
|
||||||
}
|
}
|
||||||
std.process.exit(1);
|
else if (options.input_format == .res)
|
||||||
},
|
IoStream.fromIoSource(options.input_source, .input) catch |err| {
|
||||||
error.LineNumberOverflow => {
|
try error_handler.emitMessage(allocator, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
|
||||||
// TODO: Better error message
|
std.process.exit(1);
|
||||||
try error_handler.emitMessage(allocator, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
|
}
|
||||||
std.process.exit(1);
|
else
|
||||||
},
|
IoStream.fromIoSource(options.output_source, .output) catch |err| {
|
||||||
error.OutOfMemory => |e| return e,
|
try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
|
||||||
};
|
std.process.exit(1);
|
||||||
defer mapping_results.mappings.deinit(allocator);
|
};
|
||||||
|
defer res_stream.deinit(allocator);
|
||||||
|
|
||||||
const default_code_page = options.default_code_page orelse .windows1252;
|
const res_data = res_data: {
|
||||||
const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page);
|
if (options.input_format != .res) {
|
||||||
|
// Note: We still want to run this when no-preprocess is set because:
|
||||||
|
// 1. We want to print accurate line numbers after removing multiline comments
|
||||||
|
// 2. We want to be able to handle an already-preprocessed input with #line commands in it
|
||||||
|
var mapping_results = parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
|
||||||
|
error.InvalidLineCommand => {
|
||||||
|
// TODO: Maybe output the invalid line command
|
||||||
|
try error_handler.emitMessage(allocator, .err, "invalid line command in the preprocessed source", .{});
|
||||||
|
if (options.preprocess == .no) {
|
||||||
|
try error_handler.emitMessage(allocator, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
|
||||||
|
} else {
|
||||||
|
try error_handler.emitMessage(allocator, .note, "this is likely to be a bug, please report it", .{});
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
},
|
||||||
|
error.LineNumberOverflow => {
|
||||||
|
// TODO: Better error message
|
||||||
|
try error_handler.emitMessage(allocator, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
|
||||||
|
std.process.exit(1);
|
||||||
|
},
|
||||||
|
error.OutOfMemory => |e| return e,
|
||||||
|
};
|
||||||
|
defer mapping_results.mappings.deinit(allocator);
|
||||||
|
|
||||||
const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
|
const default_code_page = options.default_code_page orelse .windows1252;
|
||||||
|
const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page);
|
||||||
|
|
||||||
var output_file = std.fs.cwd().createFile(options.output_filename, .{}) catch |err| {
|
const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
|
||||||
try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_filename, @errorName(err) });
|
|
||||||
std.process.exit(1);
|
|
||||||
};
|
|
||||||
var output_file_closed = false;
|
|
||||||
defer if (!output_file_closed) output_file.close();
|
|
||||||
|
|
||||||
var diagnostics = Diagnostics.init(allocator);
|
var diagnostics = Diagnostics.init(allocator);
|
||||||
defer diagnostics.deinit();
|
defer diagnostics.deinit();
|
||||||
|
|
||||||
var output_buffered_stream = std.io.bufferedWriter(output_file.writer());
|
const res_stream_writer = res_stream.source.writer(allocator);
|
||||||
|
var output_buffered_stream = std.io.bufferedWriter(res_stream_writer);
|
||||||
|
|
||||||
compile(allocator, final_input, output_buffered_stream.writer(), .{
|
compile(allocator, final_input, output_buffered_stream.writer(), .{
|
||||||
.cwd = std.fs.cwd(),
|
.cwd = std.fs.cwd(),
|
||||||
.diagnostics = &diagnostics,
|
.diagnostics = &diagnostics,
|
||||||
.source_mappings = &mapping_results.mappings,
|
.source_mappings = &mapping_results.mappings,
|
||||||
.dependencies_list = maybe_dependencies_list,
|
.dependencies_list = maybe_dependencies_list,
|
||||||
.ignore_include_env_var = options.ignore_include_env_var,
|
.ignore_include_env_var = options.ignore_include_env_var,
|
||||||
.extra_include_paths = options.extra_include_paths.items,
|
.extra_include_paths = options.extra_include_paths.items,
|
||||||
.system_include_paths = include_paths,
|
.system_include_paths = include_paths,
|
||||||
.default_language_id = options.default_language_id,
|
.default_language_id = options.default_language_id,
|
||||||
.default_code_page = default_code_page,
|
.default_code_page = default_code_page,
|
||||||
.disjoint_code_page = has_disjoint_code_page,
|
.disjoint_code_page = has_disjoint_code_page,
|
||||||
.verbose = options.verbose,
|
.verbose = options.verbose,
|
||||||
.null_terminate_string_table_strings = options.null_terminate_string_table_strings,
|
.null_terminate_string_table_strings = options.null_terminate_string_table_strings,
|
||||||
.max_string_literal_codepoints = options.max_string_literal_codepoints,
|
.max_string_literal_codepoints = options.max_string_literal_codepoints,
|
||||||
.silent_duplicate_control_ids = options.silent_duplicate_control_ids,
|
.silent_duplicate_control_ids = options.silent_duplicate_control_ids,
|
||||||
.warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
|
.warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
|
||||||
}) catch |err| switch (err) {
|
}) catch |err| switch (err) {
|
||||||
error.ParseError, error.CompileError => {
|
error.ParseError, error.CompileError => {
|
||||||
try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
|
try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
|
||||||
// Delete the output file on error
|
// Delete the output file on error
|
||||||
output_file.close();
|
res_stream.cleanupAfterError();
|
||||||
output_file_closed = true;
|
std.process.exit(1);
|
||||||
// Failing to delete is not really a big deal, so swallow any errors
|
},
|
||||||
std.fs.cwd().deleteFile(options.output_filename) catch {};
|
else => |e| return e,
|
||||||
std.process.exit(1);
|
};
|
||||||
},
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
|
||||||
|
|
||||||
try output_buffered_stream.flush();
|
try output_buffered_stream.flush();
|
||||||
|
|
||||||
// print any warnings/notes
|
// print any warnings/notes
|
||||||
if (!zig_integration) {
|
if (!zig_integration) {
|
||||||
diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
|
diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the depfile
|
// write the depfile
|
||||||
if (options.depfile_path) |depfile_path| {
|
if (options.depfile_path) |depfile_path| {
|
||||||
var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
|
var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
|
||||||
try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
|
try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
defer depfile.close();
|
||||||
|
|
||||||
|
const depfile_writer = depfile.writer();
|
||||||
|
var depfile_buffered_writer = std.io.bufferedWriter(depfile_writer);
|
||||||
|
switch (options.depfile_fmt) {
|
||||||
|
.json => {
|
||||||
|
var write_stream = std.json.writeStream(depfile_buffered_writer.writer(), .{ .whitespace = .indent_2 });
|
||||||
|
defer write_stream.deinit();
|
||||||
|
|
||||||
|
try write_stream.beginArray();
|
||||||
|
for (dependencies_list.items) |dep_path| {
|
||||||
|
try write_stream.write(dep_path);
|
||||||
|
}
|
||||||
|
try write_stream.endArray();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
try depfile_buffered_writer.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.output_format != .coff) return;
|
||||||
|
|
||||||
|
break :res_data res_stream.source.readAll(allocator) catch |err| {
|
||||||
|
try error_handler.emitMessage(allocator, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// No need to keep the res_data around after parsing the resources from it
|
||||||
|
defer res_data.deinit(allocator);
|
||||||
|
|
||||||
|
std.debug.assert(options.output_format == .coff);
|
||||||
|
|
||||||
|
// TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs
|
||||||
|
var fbs = std.io.fixedBufferStream(res_data.bytes);
|
||||||
|
break :resources cvtres.parseRes(allocator, fbs.reader(), .{ .max_size = res_data.bytes.len }) catch |err| {
|
||||||
|
// TODO: Better errors
|
||||||
|
try error_handler.emitMessage(allocator, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
};
|
};
|
||||||
defer depfile.close();
|
};
|
||||||
|
defer resources.deinit();
|
||||||
|
|
||||||
const depfile_writer = depfile.writer();
|
var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| {
|
||||||
var depfile_buffered_writer = std.io.bufferedWriter(depfile_writer);
|
try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
|
||||||
switch (options.depfile_fmt) {
|
std.process.exit(1);
|
||||||
.json => {
|
};
|
||||||
var write_stream = std.json.writeStream(depfile_buffered_writer.writer(), .{ .whitespace = .indent_2 });
|
defer coff_stream.deinit(allocator);
|
||||||
defer write_stream.deinit();
|
|
||||||
|
|
||||||
try write_stream.beginArray();
|
var coff_output_buffered_stream = std.io.bufferedWriter(coff_stream.source.writer(allocator));
|
||||||
for (dependencies_list.items) |dep_path| {
|
|
||||||
try write_stream.write(dep_path);
|
var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
|
||||||
}
|
cvtres.writeCoff(allocator, coff_output_buffered_stream.writer(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
|
||||||
try write_stream.endArray();
|
switch (err) {
|
||||||
|
error.DuplicateResource => {
|
||||||
|
const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
|
||||||
|
try error_handler.emitMessage(allocator, .err, "duplicate resource [id: {}, type: {}, language: {}]", .{
|
||||||
|
duplicate_resource.name_value,
|
||||||
|
fmtResourceType(duplicate_resource.type_value),
|
||||||
|
duplicate_resource.language,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error.ResourceDataTooLong => {
|
||||||
|
const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
|
||||||
|
try error_handler.emitMessage(allocator, .err, "resource has a data length that is too large to be written into a coff section", .{});
|
||||||
|
try error_handler.emitMessage(allocator, .note, "the resource with the invalid size is [id: {}, type: {}, language: {}]", .{
|
||||||
|
overflow_resource.name_value,
|
||||||
|
fmtResourceType(overflow_resource.type_value),
|
||||||
|
overflow_resource.language,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error.TotalResourceDataTooLong => {
|
||||||
|
const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
|
||||||
|
try error_handler.emitMessage(allocator, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
|
||||||
|
try error_handler.emitMessage(allocator, .note, "size overflow occurred when attempting to write this resource: [id: {}, type: {}, language: {}]", .{
|
||||||
|
overflow_resource.name_value,
|
||||||
|
fmtResourceType(overflow_resource.type_value),
|
||||||
|
overflow_resource.language,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
try error_handler.emitMessage(allocator, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
try depfile_buffered_writer.flush();
|
// Delete the output file on error
|
||||||
}
|
coff_stream.cleanupAfterError();
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
try coff_output_buffered_stream.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IoStream = struct {
|
||||||
|
name: []const u8,
|
||||||
|
intermediate: bool,
|
||||||
|
source: Source,
|
||||||
|
|
||||||
|
pub const IoDirection = enum { input, output };
|
||||||
|
|
||||||
|
pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !IoStream {
|
||||||
|
return .{
|
||||||
|
.name = switch (source) {
|
||||||
|
.filename => |filename| filename,
|
||||||
|
.stdio => switch (io) {
|
||||||
|
.input => "<stdin>",
|
||||||
|
.output => "<stdout>",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.intermediate = false,
|
||||||
|
.source = try Source.fromIoSource(source, io),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *IoStream, allocator: std.mem.Allocator) void {
|
||||||
|
self.source.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanupAfterError(self: *IoStream) void {
|
||||||
|
switch (self.source) {
|
||||||
|
.file => |file| {
|
||||||
|
// Delete the output file on error
|
||||||
|
file.close();
|
||||||
|
// Failing to delete is not really a big deal, so swallow any errors
|
||||||
|
std.fs.cwd().deleteFile(self.name) catch {};
|
||||||
|
},
|
||||||
|
.stdio, .memory, .closed => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Source = union(enum) {
|
||||||
|
file: std.fs.File,
|
||||||
|
stdio: std.fs.File,
|
||||||
|
memory: std.ArrayListUnmanaged(u8),
|
||||||
|
/// The source has been closed and any usage of the Source in this state is illegal (except deinit).
|
||||||
|
closed: void,
|
||||||
|
|
||||||
|
pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !Source {
|
||||||
|
switch (source) {
|
||||||
|
.filename => |filename| return .{
|
||||||
|
.file = switch (io) {
|
||||||
|
.input => try openFileNotDir(std.fs.cwd(), filename, .{}),
|
||||||
|
.output => try std.fs.cwd().createFile(filename, .{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.stdio => |file| return .{ .stdio = file },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Source, allocator: std.mem.Allocator) void {
|
||||||
|
switch (self.*) {
|
||||||
|
.file => |file| file.close(),
|
||||||
|
.stdio => {},
|
||||||
|
.memory => |*list| list.deinit(allocator),
|
||||||
|
.closed => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Data = struct {
|
||||||
|
bytes: []const u8,
|
||||||
|
needs_free: bool,
|
||||||
|
|
||||||
|
pub fn deinit(self: Data, allocator: std.mem.Allocator) void {
|
||||||
|
if (self.needs_free) {
|
||||||
|
allocator.free(self.bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn readAll(self: Source, allocator: std.mem.Allocator) !Data {
|
||||||
|
return switch (self) {
|
||||||
|
inline .file, .stdio => |file| .{
|
||||||
|
.bytes = try file.readToEndAlloc(allocator, std.math.maxInt(usize)),
|
||||||
|
.needs_free = true,
|
||||||
|
},
|
||||||
|
.memory => |list| .{ .bytes = list.items, .needs_free = false },
|
||||||
|
.closed => unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const WriterContext = struct {
|
||||||
|
self: *Source,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
};
|
||||||
|
pub const WriteError = std.mem.Allocator.Error || std.fs.File.WriteError;
|
||||||
|
pub const Writer = std.io.Writer(WriterContext, WriteError, write);
|
||||||
|
|
||||||
|
pub fn write(ctx: WriterContext, bytes: []const u8) WriteError!usize {
|
||||||
|
switch (ctx.self.*) {
|
||||||
|
inline .file, .stdio => |file| return file.write(bytes),
|
||||||
|
.memory => |*list| {
|
||||||
|
try list.appendSlice(ctx.allocator, bytes);
|
||||||
|
return bytes.len;
|
||||||
|
},
|
||||||
|
.closed => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writer(self: *Source, allocator: std.mem.Allocator) Writer {
|
||||||
|
return .{ .context = .{ .self = self, .allocator = allocator } };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8) ![]const []const u8 {
|
fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8) ![]const []const u8 {
|
||||||
var includes = auto_includes_option;
|
var includes = auto_includes_option;
|
||||||
if (builtin.target.os.tag != .windows) {
|
if (builtin.target.os.tag != .windows) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue