Skip to content

Commit

Permalink
First work on /:depfile CLI option
Browse files Browse the repository at this point in the history
Will be needed for integrating with the cache system of Zig. Should probably be expanded to allow different formats (Makefile, NMake), but the existing standard depfile formats are quite a bit of a mess in terms of how things are escaped/what's allowed/etc.
  • Loading branch information
squeek502 committed Mar 2, 2024
1 parent 52c101c commit b95fe10
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 18 deletions.
42 changes: 42 additions & 0 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub const usage_string_after_command_name =
\\ msvc Use MSVC include paths (must be present on the system)
\\ gnu Use MinGW include paths (requires Zig as the preprocessor)
\\ none Do not use any autodetected include paths
\\ /:depfile <path> Output a file containing a list of all the files that the
\\ .rc includes or otherwise depends on.
\\ /:depfile-fmt <value> Output format of the depfile, if /:depfile is set.
\\ json (default) A top-level JSON array of paths
\\
\\Note: For compatibility reasons, all custom options start with :
\\
Expand Down Expand Up @@ -140,8 +144,11 @@ pub const Options = struct {
debug: bool = false,
print_help_and_exit: bool = false,
auto_includes: AutoIncludes = .any,
depfile_path: ?[]const u8 = null,
depfile_fmt: DepfileFormat = .json,

pub const AutoIncludes = enum { any, msvc, gnu, none };
pub const DepfileFormat = enum { json };
pub const Preprocess = enum { no, yes, only };
pub const SymbolAction = enum { define, undefine };
pub const SymbolValue = union(SymbolAction) {
Expand Down Expand Up @@ -230,6 +237,9 @@ pub const Options = struct {
entry.value_ptr.deinit(self.allocator);
}
self.symbols.deinit(self.allocator);
if (self.depfile_path) |depfile_path| {
self.allocator.free(depfile_path);
}
}

pub fn dumpVerbose(self: *const Options, writer: anytype) !void {
Expand Down Expand Up @@ -424,6 +434,24 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
if (std.ascii.startsWithIgnoreCase(arg_name, ":no-preprocess")) {
options.preprocess = .no;
arg.name_offset += ":no-preprocess".len;
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":depfile-fmt")) {
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 msg_writer = err_details.msg.writer(allocator);
try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":depfile-fmt".len) });
try diagnostics.append(err_details);
arg_i += 1;
break :next_arg;
};
options.depfile_fmt = std.meta.stringToEnum(Options.DepfileFormat, 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 depfile format setting: {s} ", .{value.slice});
try diagnostics.append(err_details);
break :blk options.depfile_fmt;
};
arg_i += value.index_increment;
continue :next_arg;
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":auto-includes")) {
const value = arg.value(":auto-includes".len, arg_i, args) catch {
var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
Expand All @@ -442,6 +470,20 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
};
arg_i += value.index_increment;
continue :next_arg;
} else if (std.ascii.startsWithIgnoreCase(arg_name, ":depfile")) {
const value = arg.value(":depfile".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(":depfile".len) });
try diagnostics.append(err_details);
arg_i += 1;
break :next_arg;
};
const path = try allocator.dupe(u8, value.slice);
errdefer allocator.free(path);
options.depfile_path = path;
arg_i += value.index_increment;
continue :next_arg;
} else if (std.ascii.startsWithIgnoreCase(arg_name, "nologo")) {
// No-op, we don't display any 'logo' to suppress
arg.name_offset += "nologo".len;
Expand Down
54 changes: 36 additions & 18 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ pub fn main() !void {
try stdout_writer.writeByte('\n');
}

var dependencies_list = std.ArrayList([]const u8).init(allocator);
defer {
for (dependencies_list.items) |item| {
allocator.free(item);
}
dependencies_list.deinit();
}
const maybe_dependencies_list: ?*std.ArrayList([]const u8) = if (options.depfile_path != null) &dependencies_list else null;

const full_input = full_input: {
if (options.preprocess != .no) {
var preprocessed_buf = std.ArrayList(u8).init(allocator);
Expand Down Expand Up @@ -112,7 +121,7 @@ pub fn main() !void {
try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
}

preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items) catch |err| switch (err) {
preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items, maybe_dependencies_list) catch |err| switch (err) {
error.GeneratedSourceError => {
// extra newline to separate this line from the aro errors
try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during preprocessor setup (this is always a bug):\n", .{});
Expand Down Expand Up @@ -154,14 +163,6 @@ pub fn main() !void {
var mapping_results = try parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_filename });
defer mapping_results.mappings.deinit(allocator);

// TODO: Need to test to make sure that the parsing of the #line directives match
// the initial_filename given to parseAndRemoveLineCommands.
// They *should* match, as the clang preprocessor inserts whatever filename
// you give it into the #line directives (e.g. `.\./rCDaTA.rC` for a file called
// `rcdata.rc` will get a `#line 1 ".\\./rCDaTA.rC"` directive), but there still
// may be a mismatch in how the line directive strings are parsed versus
// how they are escaped/written by the preprocessor.

const final_input = removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings) catch |err| switch (err) {
error.InvalidSourceMappingCollapse => {
try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during comment removal; this is a known bug", .{});
Expand All @@ -180,14 +181,6 @@ pub fn main() !void {
var diagnostics = Diagnostics.init(allocator);
defer diagnostics.deinit();

var dependencies_list = std.ArrayList([]const u8).init(allocator);
defer {
for (dependencies_list.items) |item| {
allocator.free(item);
}
dependencies_list.deinit();
}

if (options.debug) {
std.debug.print("after preprocessor:\n------------------\n{s}\n------------------\n", .{final_input});
std.debug.print("\nmappings:\n", .{});
Expand Down Expand Up @@ -223,7 +216,7 @@ pub fn main() !void {
.cwd = std.fs.cwd(),
.diagnostics = &diagnostics,
.source_mappings = &mapping_results.mappings,
.dependencies_list = if (options.debug) &dependencies_list else null,
.dependencies_list = maybe_dependencies_list,
.ignore_include_env_var = options.ignore_include_env_var,
.extra_include_paths = options.extra_include_paths.items,
.default_language_id = options.default_language_id,
Expand Down Expand Up @@ -258,6 +251,31 @@ pub fn main() !void {

// print any warnings/notes
diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);

// write the depfile
if (options.depfile_path) |depfile_path| {
var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
try renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
std.os.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();
}
}

fn getIncludePaths(allocator: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes) ![]const []const u8 {
Expand Down
11 changes: 11 additions & 0 deletions src/preprocess.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub fn preprocess(
writer: anytype,
/// Expects argv[0] to be the command name
argv: []const []const u8,
maybe_dependencies_list: ?*std.ArrayList([]const u8),
) PreprocessError!void {
try comp.addDefaultPragmaHandlers();

Expand Down Expand Up @@ -59,6 +60,16 @@ pub fn preprocess(
if (hasAnyErrors(comp)) return error.PreprocessError;

try pp.prettyPrintTokens(writer);

if (maybe_dependencies_list) |dependencies_list| {
for (comp.sources.values()) |comp_source| {
if (comp_source.id == builtin_macros.id or comp_source.id == user_macros.id) continue;
if (comp_source.id == .unused or comp_source.id == .generated) continue;
const duped_path = try dependencies_list.allocator.dupe(u8, comp_source.path);
errdefer dependencies_list.allocator.free(duped_path);
try dependencies_list.append(duped_path);
}
}
}

fn hasAnyErrors(comp: *aro.Compilation) bool {
Expand Down

0 comments on commit b95fe10

Please sign in to comment.