feature. See also
. The project being documented here (as the example) is the Zig library itself.
main.main
pub fn main(init: std.process.Init.Minimal) !void
File
Code
pub fn main(init: std.process.Init.Minimal) !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer std.debug.assert(debug_allocator.deinit() == .ok);
const gpa = debug_allocator.allocator();
var environ_map = try init.environ.createMap(gpa);
defer environ_map.deinit();
var threaded: std.Io.Threaded = .init(gpa, .{
.environ = init.environ,
.argv0 = .init(init.args),
});
defer threaded.deinit();
const io = threaded.io();
var arena_state = std.heap.ArenaAllocator.init(gpa);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try init.args.toSlice(arena);
if (args.len < 2) {
const stderr = try io.lockStderr(&.{}, null);
try renderErrorMessage(stderr.terminal(), .err, "expected zig lib dir as first argument", .{});
std.process.exit(1);
}
const zig_lib_dir = std.mem.cutPrefix(u8, args[1], "--zig-lib=") orelse @panic("bad --zig-lib= arg");
var cli_args = args[2..];
var zig_integration = false;
if (cli_args.len > 0 and std.mem.eql(u8, cli_args[0], "--zig-integration")) {
zig_integration = true;
cli_args = args[3..];
}
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
const stdout = &stdout_writer.interface;
var error_handler: ErrorHandler = switch (zig_integration) {
true => .{
.server = .{
.out = stdout,
.in = undefined,
},
},
false => .stderr,
};
var options = options: {
var cli_diagnostics = cli.Diagnostics.init(gpa);
defer cli_diagnostics.deinit();
var options = cli.parse(gpa, io, cli_args, &cli_diagnostics) catch |err| switch (err) {
error.ParseError => {
try error_handler.emitCliDiagnostics(gpa, io, cli_args, &cli_diagnostics);
std.process.exit(1);
},
else => |e| return e,
};
try options.maybeAppendRC(io, Io.Dir.cwd());
if (!zig_integration) {
try cli_diagnostics.renderToStderr(io, cli_args);
// so that there is a clear separation between the cli diagnostics and whatever
// gets printed after
if (cli_diagnostics.errors.items.len > 0) {
const stderr = try io.lockStderr(&.{}, null);
defer io.unlockStderr();
try stderr.file_writer.interface.writeByte('\n');
}
}
break :options options;
};
defer options.deinit();
if (options.print_help_and_exit) {
try cli.writeUsage(stdout, "zig rc");
try stdout.flush();
return;
}
options.verbose = false;
if (options.verbose) {
try options.dumpVerbose(stdout);
try stdout.writeByte('\n');
try stdout.flush();
}
var dependencies = Dependencies.init(gpa);
defer dependencies.deinit();
const maybe_dependencies: ?*Dependencies = if (options.depfile_path != null) &dependencies else null;
var include_paths = LazyIncludePaths{
.arena = arena,
.io = io,
.auto_includes_option = options.auto_includes,
.zig_lib_dir = zig_lib_dir,
.target_machine_type = options.coff_options.target,
};
const full_input = full_input: {
if (options.input_format == .rc and options.preprocess != .no) {
var preprocessed_buf: std.Io.Writer.Allocating = .init(gpa);
errdefer preprocessed_buf.deinit();
// so we can use a scoped arena for everything else.
var aro_arena_state = std.heap.ArenaAllocator.init(gpa);
defer aro_arena_state.deinit();
const aro_arena = aro_arena_state.allocator();
var stderr_buf: [512]u8 = undefined;
var diagnostics: aro.Diagnostics = .{ .output = output: {
if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } };
const stderr = try io.lockStderr(&stderr_buf, null);
break :output .{ .to_writer = stderr.terminal() };
} };
defer {
diagnostics.deinit();
if (!zig_integration) io.unlockStderr();
}
var comp = try aro.Compilation.init(.{
.gpa = aro_arena,
.arena = aro_arena,
.io = io,
.diagnostics = &diagnostics,
.environ_map = &environ_map,
});
defer comp.deinit();
var argv: std.ArrayList([]const u8) = .empty;
defer argv.deinit(aro_arena);
try argv.append(aro_arena, "arocc");
const resolved_include_paths = try include_paths.get(&error_handler, &environ_map);
try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths, environ_map.get("INCLUDE"));
try argv.append(aro_arena, switch (options.input_source) {
.stdio => "-",
.filename => |filename| filename,
});
if (options.verbose) {
try stdout.writeAll("Preprocessor: arocc (built-in)\n");
for (argv.items[0 .. argv.items.len - 1]) |arg| {
try stdout.print("{s} ", .{arg});
}
try stdout.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
try stdout.flush();
}
preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies) catch |err| switch (err) {
error.GeneratedSourceError => {
try error_handler.emitAroDiagnostics(gpa, "failed during preprocessor setup (this is always a bug)", &comp);
std.process.exit(1);
},
error.ArgError, error.PreprocessError => {
try error_handler.emitAroDiagnostics(gpa, "failed during preprocessing", &comp);
std.process.exit(1);
},
error.FileTooBig => {
try error_handler.emitMessage(gpa, io, .err, "failed during preprocessing: maximum file size exceeded", .{});
std.process.exit(1);
},
error.WriteFailed => {
try error_handler.emitMessage(gpa, io, .err, "failed during preprocessing: error writing the preprocessed output", .{});
std.process.exit(1);
},
error.OutOfMemory => |e| return e,
};
break :full_input try preprocessed_buf.toOwnedSlice();
} else {
switch (options.input_source) {
.stdio => |file| {
var file_reader = file.reader(io, &.{});
break :full_input file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to read input from stdin: {t}", .{file_reader.err orelse err});
std.process.exit(1);
};
},
.filename => |input_filename| {
break :full_input Io.Dir.cwd().readFileAlloc(io, input_filename, gpa, .unlimited) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to read input file path '{s}': {t}", .{ input_filename, err });
std.process.exit(1);
};
},
}
}
};
defer gpa.free(full_input);
if (options.preprocess == .only) {
switch (options.output_source) {
.stdio => |output_file| {
try output_file.writeStreamingAll(io, full_input);
},
.filename => |output_filename| {
try Io.Dir.cwd().writeFile(io, .{ .sub_path = output_filename, .data = full_input });
},
}
return;
}
var resources = resources: {
const need_intermediate_res = options.output_format == .coff and options.input_format != .res;
var res_stream = if (need_intermediate_res)
IoStream{
.name = "<in-memory intermediate res>",
.intermediate = true,
.source = .{ .memory = .empty },
}
else if (options.input_format == .res)
IoStream.fromIoSource(io, options.input_source, .input) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
std.process.exit(1);
}
else
IoStream.fromIoSource(io, options.output_source, .output) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
std.process.exit(1);
};
defer res_stream.deinit(gpa, io);
const res_data = res_data: {
if (options.input_format != .res) {
// 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(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
error.InvalidLineCommand => {
try error_handler.emitMessage(gpa, io, .err, "invalid line command in the preprocessed source", .{});
if (options.preprocess == .no) {
try error_handler.emitMessage(gpa, io, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
} else {
try error_handler.emitMessage(gpa, io, .note, "this is likely to be a bug, please report it", .{});
}
std.process.exit(1);
},
error.LineNumberOverflow => {
try error_handler.emitMessage(gpa, io, .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(gpa);
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);
const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
var diagnostics = Diagnostics.init(gpa);
defer diagnostics.deinit();
var output_buffer: [4096]u8 = undefined;
var res_stream_writer = res_stream.source.writer(gpa, io, &output_buffer);
defer res_stream_writer.deinit(&res_stream.source);
const output_buffered_stream = res_stream_writer.interface();
compile(gpa, io, final_input, output_buffered_stream, .{
.cwd = Io.Dir.cwd(),
.diagnostics = &diagnostics,
.source_mappings = &mapping_results.mappings,
.dependencies = maybe_dependencies,
.ignore_include_env_var = options.ignore_include_env_var,
.extra_include_paths = options.extra_include_paths.items,
.system_include_paths = try include_paths.get(&error_handler, &environ_map),
.default_language_id = options.default_language_id,
.default_code_page = default_code_page,
.disjoint_code_page = has_disjoint_code_page,
.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,
.include_env_value = environ_map.get("INCLUDE"),
}) catch |err| switch (err) {
error.ParseError, error.CompileError => {
try error_handler.emitDiagnostics(gpa, io, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings);
res_stream.cleanupAfterError(io);
std.process.exit(1);
},
else => |e| return e,
};
try output_buffered_stream.flush();
if (!zig_integration) {
try diagnostics.renderToStderr(io, Io.Dir.cwd(), final_input, mapping_results.mappings);
}
if (options.depfile_path) |depfile_path| {
var depfile = Io.Dir.cwd().createFile(io, depfile_path, .{}) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
std.process.exit(1);
};
defer depfile.close(io);
var depfile_buffer: [1024]u8 = undefined;
var depfile_writer = depfile.writer(io, &depfile_buffer);
switch (options.depfile_fmt) {
.json => {
var write_stream: std.json.Stringify = .{
.writer = &depfile_writer.interface,
.options = .{ .whitespace = .indent_2 },
};
try write_stream.beginArray();
for (dependencies.list.items) |dep_path| {
try write_stream.write(dep_path);
}
try write_stream.endArray();
},
}
try depfile_writer.interface.flush();
}
}
if (options.output_format != .coff) return;
break :res_data res_stream.source.readAll(gpa, io) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
std.process.exit(1);
};
};
defer res_data.deinit(gpa);
std.debug.assert(options.output_format == .coff);
var res_reader: std.Io.Reader = .fixed(res_data.bytes);
break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
std.process.exit(1);
};
};
defer resources.deinit();
var coff_stream = IoStream.fromIoSource(io, options.output_source, .output) catch |err| {
try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
std.process.exit(1);
};
defer coff_stream.deinit(gpa, io);
var coff_output_buffer: [4096]u8 = undefined;
var coff_output_buffered_stream = coff_stream.source.writer(gpa, io, &coff_output_buffer);
var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
switch (err) {
error.DuplicateResource => {
const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
try error_handler.emitMessage(gpa, io, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{
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(gpa, io, .err, "resource has a data length that is too large to be written into a coff section", .{});
try error_handler.emitMessage(gpa, io, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
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(gpa, io, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
try error_handler.emitMessage(gpa, io, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
overflow_resource.name_value,
fmtResourceType(overflow_resource.type_value),
overflow_resource.language,
});
},
else => {
try error_handler.emitMessage(gpa, io, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
},
}
coff_stream.cleanupAfterError(io);
std.process.exit(1);
};
try coff_output_buffered_stream.interface().flush();
}