mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 22:33:08 +00:00
This moves .rc/.manifest compilation out of the main Zig binary, contributing towards #19063 Also: - Make resinator use Aro as its preprocessor instead of clang - Sync resinator with upstream
299 lines
13 KiB
Zig
299 lines
13 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const removeComments = @import("comments.zig").removeComments;
|
|
const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands;
|
|
const compile = @import("compile.zig").compile;
|
|
const Diagnostics = @import("errors.zig").Diagnostics;
|
|
const cli = @import("cli.zig");
|
|
const preprocess = @import("preprocess.zig");
|
|
const renderErrorMessage = @import("utils.zig").renderErrorMessage;
|
|
const aro = @import("aro");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer std.debug.assert(gpa.deinit() == .ok);
|
|
const allocator = gpa.allocator();
|
|
|
|
const stderr = std.io.getStdErr();
|
|
const stderr_config = std.io.tty.detectConfig(stderr);
|
|
|
|
const args = try std.process.argsAlloc(allocator);
|
|
defer std.process.argsFree(allocator, args);
|
|
|
|
if (args.len < 2) {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "expected zig lib dir as first argument", .{});
|
|
std.os.exit(1);
|
|
}
|
|
const zig_lib_dir = args[1];
|
|
|
|
var options = options: {
|
|
var cli_diagnostics = cli.Diagnostics.init(allocator);
|
|
defer cli_diagnostics.deinit();
|
|
var options = cli.parse(allocator, args[2..], &cli_diagnostics) catch |err| switch (err) {
|
|
error.ParseError => {
|
|
cli_diagnostics.renderToStdErr(args, stderr_config);
|
|
std.os.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
try options.maybeAppendRC(std.fs.cwd());
|
|
|
|
// print any warnings/notes
|
|
cli_diagnostics.renderToStdErr(args, stderr_config);
|
|
// If there was something printed, then add an extra newline separator
|
|
// so that there is a clear separation between the cli diagnostics and whatever
|
|
// gets printed after
|
|
if (cli_diagnostics.errors.items.len > 0) {
|
|
try stderr.writeAll("\n");
|
|
}
|
|
break :options options;
|
|
};
|
|
defer options.deinit();
|
|
|
|
if (options.print_help_and_exit) {
|
|
try cli.writeUsage(stderr.writer(), "zig rc");
|
|
return;
|
|
}
|
|
|
|
const stdout_writer = std.io.getStdOut().writer();
|
|
if (options.verbose) {
|
|
try options.dumpVerbose(stdout_writer);
|
|
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);
|
|
errdefer preprocessed_buf.deinit();
|
|
|
|
// We're going to throw away everything except the final preprocessed output anyway,
|
|
// so we can use a scoped arena for everything else.
|
|
var aro_arena_state = std.heap.ArenaAllocator.init(allocator);
|
|
defer aro_arena_state.deinit();
|
|
const aro_arena = aro_arena_state.allocator();
|
|
|
|
const include_paths = getIncludePaths(aro_arena, options.auto_includes, zig_lib_dir) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => |e| {
|
|
switch (e) {
|
|
error.MsvcIncludesNotFound => {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "MSVC include paths could not be automatically detected", .{});
|
|
},
|
|
error.MingwIncludesNotFound => {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "MinGW include paths could not be automatically detected", .{});
|
|
},
|
|
}
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .note, "to disable auto includes, use the option /:auto-includes none", .{});
|
|
std.os.exit(1);
|
|
},
|
|
};
|
|
|
|
var comp = aro.Compilation.init(aro_arena);
|
|
defer comp.deinit();
|
|
|
|
var argv = std.ArrayList([]const u8).init(comp.gpa);
|
|
defer argv.deinit();
|
|
|
|
try argv.append("arocc"); // dummy command name
|
|
try preprocess.appendAroArgs(aro_arena, &argv, options, include_paths);
|
|
try argv.append(options.input_filename);
|
|
|
|
if (options.verbose) {
|
|
try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n");
|
|
for (argv.items[0 .. argv.items.len - 1]) |arg| {
|
|
try stdout_writer.print("{s} ", .{arg});
|
|
}
|
|
try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
|
|
}
|
|
|
|
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", .{});
|
|
aro.Diagnostics.render(&comp, stderr_config);
|
|
std.os.exit(1);
|
|
},
|
|
// ArgError can occur if e.g. the .rc file is not found
|
|
error.ArgError, error.PreprocessError => {
|
|
// extra newline to separate this line from the aro errors
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during preprocessing:\n", .{});
|
|
aro.Diagnostics.render(&comp, stderr_config);
|
|
std.os.exit(1);
|
|
},
|
|
error.StreamTooLong => {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during preprocessing: maximum file size exceeded", .{});
|
|
std.os.exit(1);
|
|
},
|
|
error.OutOfMemory => |e| return e,
|
|
};
|
|
|
|
break :full_input try preprocessed_buf.toOwnedSlice();
|
|
} else {
|
|
break :full_input std.fs.cwd().readFileAlloc(allocator, options.input_filename, std.math.maxInt(usize)) catch |err| {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to read input file path '{s}': {s}", .{ options.input_filename, @errorName(err) });
|
|
std.os.exit(1);
|
|
};
|
|
}
|
|
};
|
|
defer allocator.free(full_input);
|
|
|
|
if (options.preprocess == .only) {
|
|
try std.fs.cwd().writeFile(options.output_filename, full_input);
|
|
return;
|
|
}
|
|
|
|
// 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 = try parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_filename });
|
|
defer mapping_results.mappings.deinit(allocator);
|
|
|
|
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", .{});
|
|
std.os.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
var output_file = std.fs.cwd().createFile(options.output_filename, .{}) catch |err| {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to create output file '{s}': {s}", .{ options.output_filename, @errorName(err) });
|
|
std.os.exit(1);
|
|
};
|
|
var output_file_closed = false;
|
|
defer if (!output_file_closed) output_file.close();
|
|
|
|
var diagnostics = Diagnostics.init(allocator);
|
|
defer diagnostics.deinit();
|
|
|
|
var output_buffered_stream = std.io.bufferedWriter(output_file.writer());
|
|
|
|
compile(allocator, final_input, output_buffered_stream.writer(), .{
|
|
.cwd = std.fs.cwd(),
|
|
.diagnostics = &diagnostics,
|
|
.source_mappings = &mapping_results.mappings,
|
|
.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,
|
|
.default_code_page = options.default_code_page orelse .windows1252,
|
|
.verbose = options.verbose,
|
|
.null_terminate_string_table_strings = options.null_terminate_string_table_strings,
|
|
.max_string_literal_codepoints = options.max_string_literal_codepoints,
|
|
.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,
|
|
}) catch |err| switch (err) {
|
|
error.ParseError, error.CompileError => {
|
|
diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
|
|
// Delete the output file on error
|
|
output_file.close();
|
|
output_file_closed = true;
|
|
// Failing to delete is not really a big deal, so swallow any errors
|
|
std.fs.cwd().deleteFile(options.output_filename) catch {};
|
|
std.os.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
try output_buffered_stream.flush();
|
|
|
|
// 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(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8) ![]const []const u8 {
|
|
var includes = auto_includes_option;
|
|
if (builtin.target.os.tag != .windows) {
|
|
switch (includes) {
|
|
// MSVC can't be found when the host isn't Windows, so short-circuit.
|
|
.msvc => return error.MsvcIncludesNotFound,
|
|
// Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts.
|
|
.any => includes = .gnu,
|
|
.none, .gnu => {},
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
switch (includes) {
|
|
.none => return &[_][]const u8{},
|
|
.any, .msvc => {
|
|
// MSVC is only detectable on Windows targets. This unreachable is to signify
|
|
// that .any and .msvc should be dealt with on non-Windows targets before this point,
|
|
// since getting MSVC include paths uses Windows-only APIs.
|
|
if (builtin.target.os.tag != .windows) unreachable;
|
|
|
|
const target_query: std.Target.Query = .{
|
|
.os_tag = .windows,
|
|
.abi = .msvc,
|
|
};
|
|
const target = std.zig.resolveTargetQueryOrFatal(target_query);
|
|
const is_native_abi = target_query.isNativeAbi();
|
|
const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, target, is_native_abi, true, null) catch {
|
|
if (includes == .any) {
|
|
// fall back to mingw
|
|
includes = .gnu;
|
|
continue;
|
|
}
|
|
return error.MsvcIncludesNotFound;
|
|
};
|
|
if (detected_libc.libc_include_dir_list.len == 0) {
|
|
if (includes == .any) {
|
|
// fall back to mingw
|
|
includes = .gnu;
|
|
continue;
|
|
}
|
|
return error.MsvcIncludesNotFound;
|
|
}
|
|
return detected_libc.libc_include_dir_list;
|
|
},
|
|
.gnu => {
|
|
const target_query: std.Target.Query = .{
|
|
.os_tag = .windows,
|
|
.abi = .gnu,
|
|
};
|
|
const target = std.zig.resolveTargetQueryOrFatal(target_query);
|
|
const is_native_abi = target_query.isNativeAbi();
|
|
const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, target, is_native_abi, true, null) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => return error.MingwIncludesNotFound,
|
|
};
|
|
return detected_libc.libc_include_dir_list;
|
|
},
|
|
}
|
|
}
|
|
}
|