zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

main.zig (41392B) - Raw


      1 const std = @import("std");
      2 const builtin = @import("builtin");
      3 const removeComments = @import("comments.zig").removeComments;
      4 const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands;
      5 const compile = @import("compile.zig").compile;
      6 const Diagnostics = @import("errors.zig").Diagnostics;
      7 const cli = @import("cli.zig");
      8 const preprocess = @import("preprocess.zig");
      9 const renderErrorMessage = @import("utils.zig").renderErrorMessage;
     10 const openFileNotDir = @import("utils.zig").openFileNotDir;
     11 const cvtres = @import("cvtres.zig");
     12 const hasDisjointCodePage = @import("disjoint_code_page.zig").hasDisjointCodePage;
     13 const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType;
     14 const aro = @import("aro");
     15 
     16 var stdout_buffer: [1024]u8 = undefined;
     17 
     18 pub fn main() !void {
     19     var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
     20     defer std.debug.assert(gpa.deinit() == .ok);
     21     const allocator = gpa.allocator();
     22 
     23     var arena_state = std.heap.ArenaAllocator.init(allocator);
     24     defer arena_state.deinit();
     25     const arena = arena_state.allocator();
     26 
     27     const stderr = std.fs.File.stderr();
     28     const stderr_config = std.io.tty.detectConfig(stderr);
     29 
     30     const args = try std.process.argsAlloc(allocator);
     31     defer std.process.argsFree(allocator, args);
     32 
     33     if (args.len < 2) {
     34         try renderErrorMessage(std.debug.lockStderrWriter(&.{}), stderr_config, .err, "expected zig lib dir as first argument", .{});
     35         std.process.exit(1);
     36     }
     37     const zig_lib_dir = args[1];
     38     var cli_args = args[2..];
     39 
     40     var zig_integration = false;
     41     if (cli_args.len > 0 and std.mem.eql(u8, cli_args[0], "--zig-integration")) {
     42         zig_integration = true;
     43         cli_args = args[3..];
     44     }
     45 
     46     var stdout_writer2 = std.fs.File.stdout().writer(&stdout_buffer);
     47     var error_handler: ErrorHandler = switch (zig_integration) {
     48         true => .{
     49             .server = .{
     50                 .out = &stdout_writer2.interface,
     51                 .in = undefined, // won't be receiving messages
     52             },
     53         },
     54         false => .{
     55             .tty = stderr_config,
     56         },
     57     };
     58 
     59     var options = options: {
     60         var cli_diagnostics = cli.Diagnostics.init(allocator);
     61         defer cli_diagnostics.deinit();
     62         var options = cli.parse(allocator, cli_args, &cli_diagnostics) catch |err| switch (err) {
     63             error.ParseError => {
     64                 try error_handler.emitCliDiagnostics(allocator, cli_args, &cli_diagnostics);
     65                 std.process.exit(1);
     66             },
     67             else => |e| return e,
     68         };
     69         try options.maybeAppendRC(std.fs.cwd());
     70 
     71         if (!zig_integration) {
     72             // print any warnings/notes
     73             cli_diagnostics.renderToStdErr(cli_args, stderr_config);
     74             // If there was something printed, then add an extra newline separator
     75             // so that there is a clear separation between the cli diagnostics and whatever
     76             // gets printed after
     77             if (cli_diagnostics.errors.items.len > 0) {
     78                 try stderr.writeAll("\n");
     79             }
     80         }
     81         break :options options;
     82     };
     83     defer options.deinit();
     84 
     85     if (options.print_help_and_exit) {
     86         const stdout = std.fs.File.stdout();
     87         try cli.writeUsage(stdout.deprecatedWriter(), "zig rc");
     88         return;
     89     }
     90 
     91     // Don't allow verbose when integrating with Zig via stdout
     92     options.verbose = false;
     93 
     94     const stdout_writer = std.fs.File.stdout().deprecatedWriter();
     95     if (options.verbose) {
     96         try options.dumpVerbose(stdout_writer);
     97         try stdout_writer.writeByte('\n');
     98     }
     99 
    100     var dependencies_list = std.array_list.Managed([]const u8).init(allocator);
    101     defer {
    102         for (dependencies_list.items) |item| {
    103             allocator.free(item);
    104         }
    105         dependencies_list.deinit();
    106     }
    107     const maybe_dependencies_list: ?*std.array_list.Managed([]const u8) = if (options.depfile_path != null) &dependencies_list else null;
    108 
    109     var include_paths = LazyIncludePaths{
    110         .arena = arena,
    111         .auto_includes_option = options.auto_includes,
    112         .zig_lib_dir = zig_lib_dir,
    113         .target_machine_type = options.coff_options.target,
    114     };
    115 
    116     const full_input = full_input: {
    117         if (options.input_format == .rc and options.preprocess != .no) {
    118             var preprocessed_buf = std.array_list.Managed(u8).init(allocator);
    119             errdefer preprocessed_buf.deinit();
    120 
    121             // We're going to throw away everything except the final preprocessed output anyway,
    122             // so we can use a scoped arena for everything else.
    123             var aro_arena_state = std.heap.ArenaAllocator.init(allocator);
    124             defer aro_arena_state.deinit();
    125             const aro_arena = aro_arena_state.allocator();
    126 
    127             var comp = aro.Compilation.init(aro_arena, std.fs.cwd());
    128             defer comp.deinit();
    129 
    130             var argv = std.array_list.Managed([]const u8).init(comp.gpa);
    131             defer argv.deinit();
    132 
    133             try argv.append("arocc"); // dummy command name
    134             const resolved_include_paths = try include_paths.get(&error_handler);
    135             try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths);
    136             try argv.append(switch (options.input_source) {
    137                 .stdio => "-",
    138                 .filename => |filename| filename,
    139             });
    140 
    141             if (options.verbose) {
    142                 try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n");
    143                 for (argv.items[0 .. argv.items.len - 1]) |arg| {
    144                     try stdout_writer.print("{s} ", .{arg});
    145                 }
    146                 try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
    147             }
    148 
    149             preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items, maybe_dependencies_list) catch |err| switch (err) {
    150                 error.GeneratedSourceError => {
    151                     try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug):", &comp);
    152                     std.process.exit(1);
    153                 },
    154                 // ArgError can occur if e.g. the .rc file is not found
    155                 error.ArgError, error.PreprocessError => {
    156                     try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing:", &comp);
    157                     std.process.exit(1);
    158                 },
    159                 error.StreamTooLong => {
    160                     try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{});
    161                     std.process.exit(1);
    162                 },
    163                 error.OutOfMemory => |e| return e,
    164             };
    165 
    166             break :full_input try preprocessed_buf.toOwnedSlice();
    167         } else {
    168             switch (options.input_source) {
    169                 .stdio => |file| {
    170                     break :full_input file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
    171                         try error_handler.emitMessage(allocator, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
    172                         std.process.exit(1);
    173                     };
    174                 },
    175                 .filename => |input_filename| {
    176                     break :full_input std.fs.cwd().readFileAlloc(allocator, input_filename, std.math.maxInt(usize)) catch |err| {
    177                         try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
    178                         std.process.exit(1);
    179                     };
    180                 },
    181             }
    182         }
    183     };
    184     defer allocator.free(full_input);
    185 
    186     if (options.preprocess == .only) {
    187         switch (options.output_source) {
    188             .stdio => |output_file| {
    189                 try output_file.writeAll(full_input);
    190             },
    191             .filename => |output_filename| {
    192                 try std.fs.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input });
    193             },
    194         }
    195         return;
    196     }
    197 
    198     var resources = resources: {
    199         const need_intermediate_res = options.output_format == .coff and options.input_format != .res;
    200         var res_stream = if (need_intermediate_res)
    201             IoStream{
    202                 .name = "<in-memory intermediate res>",
    203                 .intermediate = true,
    204                 .source = .{ .memory = .empty },
    205             }
    206         else if (options.input_format == .res)
    207             IoStream.fromIoSource(options.input_source, .input) catch |err| {
    208                 try error_handler.emitMessage(allocator, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
    209                 std.process.exit(1);
    210             }
    211         else
    212             IoStream.fromIoSource(options.output_source, .output) catch |err| {
    213                 try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
    214                 std.process.exit(1);
    215             };
    216         defer res_stream.deinit(allocator);
    217 
    218         const res_data = res_data: {
    219             if (options.input_format != .res) {
    220                 // Note: We still want to run this when no-preprocess is set because:
    221                 //   1. We want to print accurate line numbers after removing multiline comments
    222                 //   2. We want to be able to handle an already-preprocessed input with #line commands in it
    223                 var mapping_results = parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
    224                     error.InvalidLineCommand => {
    225                         // TODO: Maybe output the invalid line command
    226                         try error_handler.emitMessage(allocator, .err, "invalid line command in the preprocessed source", .{});
    227                         if (options.preprocess == .no) {
    228                             try error_handler.emitMessage(allocator, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
    229                         } else {
    230                             try error_handler.emitMessage(allocator, .note, "this is likely to be a bug, please report it", .{});
    231                         }
    232                         std.process.exit(1);
    233                     },
    234                     error.LineNumberOverflow => {
    235                         // TODO: Better error message
    236                         try error_handler.emitMessage(allocator, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
    237                         std.process.exit(1);
    238                     },
    239                     error.OutOfMemory => |e| return e,
    240                 };
    241                 defer mapping_results.mappings.deinit(allocator);
    242 
    243                 const default_code_page = options.default_code_page orelse .windows1252;
    244                 const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page);
    245 
    246                 const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
    247 
    248                 var diagnostics = Diagnostics.init(allocator);
    249                 defer diagnostics.deinit();
    250 
    251                 var output_buffer: [4096]u8 = undefined;
    252                 var res_stream_writer = res_stream.source.writer(allocator).adaptToNewApi(&output_buffer);
    253                 const output_buffered_stream = &res_stream_writer.new_interface;
    254 
    255                 compile(allocator, final_input, output_buffered_stream, .{
    256                     .cwd = std.fs.cwd(),
    257                     .diagnostics = &diagnostics,
    258                     .source_mappings = &mapping_results.mappings,
    259                     .dependencies_list = maybe_dependencies_list,
    260                     .ignore_include_env_var = options.ignore_include_env_var,
    261                     .extra_include_paths = options.extra_include_paths.items,
    262                     .system_include_paths = try include_paths.get(&error_handler),
    263                     .default_language_id = options.default_language_id,
    264                     .default_code_page = default_code_page,
    265                     .disjoint_code_page = has_disjoint_code_page,
    266                     .verbose = options.verbose,
    267                     .null_terminate_string_table_strings = options.null_terminate_string_table_strings,
    268                     .max_string_literal_codepoints = options.max_string_literal_codepoints,
    269                     .silent_duplicate_control_ids = options.silent_duplicate_control_ids,
    270                     .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
    271                 }) catch |err| switch (err) {
    272                     error.ParseError, error.CompileError => {
    273                         try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
    274                         // Delete the output file on error
    275                         res_stream.cleanupAfterError();
    276                         std.process.exit(1);
    277                     },
    278                     else => |e| return e,
    279                 };
    280 
    281                 try output_buffered_stream.flush();
    282 
    283                 // print any warnings/notes
    284                 if (!zig_integration) {
    285                     diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
    286                 }
    287 
    288                 // write the depfile
    289                 if (options.depfile_path) |depfile_path| {
    290                     var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
    291                         try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
    292                         std.process.exit(1);
    293                     };
    294                     defer depfile.close();
    295 
    296                     var depfile_buffer: [1024]u8 = undefined;
    297                     var depfile_writer = depfile.writer(&depfile_buffer);
    298                     switch (options.depfile_fmt) {
    299                         .json => {
    300                             var write_stream: std.json.Stringify = .{
    301                                 .writer = &depfile_writer.interface,
    302                                 .options = .{ .whitespace = .indent_2 },
    303                             };
    304 
    305                             try write_stream.beginArray();
    306                             for (dependencies_list.items) |dep_path| {
    307                                 try write_stream.write(dep_path);
    308                             }
    309                             try write_stream.endArray();
    310                         },
    311                     }
    312                     try depfile_writer.interface.flush();
    313                 }
    314             }
    315 
    316             if (options.output_format != .coff) return;
    317 
    318             break :res_data res_stream.source.readAll(allocator) catch |err| {
    319                 try error_handler.emitMessage(allocator, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
    320                 std.process.exit(1);
    321             };
    322         };
    323         // No need to keep the res_data around after parsing the resources from it
    324         defer res_data.deinit(allocator);
    325 
    326         std.debug.assert(options.output_format == .coff);
    327 
    328         // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs
    329         var res_reader: std.Io.Reader = .fixed(res_data.bytes);
    330         break :resources cvtres.parseRes(allocator, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
    331             // TODO: Better errors
    332             try error_handler.emitMessage(allocator, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
    333             std.process.exit(1);
    334         };
    335     };
    336     defer resources.deinit();
    337 
    338     var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| {
    339         try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
    340         std.process.exit(1);
    341     };
    342     defer coff_stream.deinit(allocator);
    343 
    344     var coff_output_buffer: [4096]u8 = undefined;
    345     var coff_output_buffered_stream = coff_stream.source.writer(allocator).adaptToNewApi(&coff_output_buffer);
    346 
    347     var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
    348     cvtres.writeCoff(allocator, &coff_output_buffered_stream.new_interface, resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
    349         switch (err) {
    350             error.DuplicateResource => {
    351                 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
    352                 try error_handler.emitMessage(allocator, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{
    353                     duplicate_resource.name_value,
    354                     fmtResourceType(duplicate_resource.type_value),
    355                     duplicate_resource.language,
    356                 });
    357             },
    358             error.ResourceDataTooLong => {
    359                 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
    360                 try error_handler.emitMessage(allocator, .err, "resource has a data length that is too large to be written into a coff section", .{});
    361                 try error_handler.emitMessage(allocator, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
    362                     overflow_resource.name_value,
    363                     fmtResourceType(overflow_resource.type_value),
    364                     overflow_resource.language,
    365                 });
    366             },
    367             error.TotalResourceDataTooLong => {
    368                 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
    369                 try error_handler.emitMessage(allocator, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
    370                 try error_handler.emitMessage(allocator, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
    371                     overflow_resource.name_value,
    372                     fmtResourceType(overflow_resource.type_value),
    373                     overflow_resource.language,
    374                 });
    375             },
    376             else => {
    377                 try error_handler.emitMessage(allocator, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
    378             },
    379         }
    380         // Delete the output file on error
    381         coff_stream.cleanupAfterError();
    382         std.process.exit(1);
    383     };
    384 
    385     try coff_output_buffered_stream.new_interface.flush();
    386 }
    387 
    388 const IoStream = struct {
    389     name: []const u8,
    390     intermediate: bool,
    391     source: Source,
    392 
    393     pub const IoDirection = enum { input, output };
    394 
    395     pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !IoStream {
    396         return .{
    397             .name = switch (source) {
    398                 .filename => |filename| filename,
    399                 .stdio => switch (io) {
    400                     .input => "<stdin>",
    401                     .output => "<stdout>",
    402                 },
    403             },
    404             .intermediate = false,
    405             .source = try Source.fromIoSource(source, io),
    406         };
    407     }
    408 
    409     pub fn deinit(self: *IoStream, allocator: std.mem.Allocator) void {
    410         self.source.deinit(allocator);
    411     }
    412 
    413     pub fn cleanupAfterError(self: *IoStream) void {
    414         switch (self.source) {
    415             .file => |file| {
    416                 // Delete the output file on error
    417                 file.close();
    418                 // Failing to delete is not really a big deal, so swallow any errors
    419                 std.fs.cwd().deleteFile(self.name) catch {};
    420             },
    421             .stdio, .memory, .closed => return,
    422         }
    423     }
    424 
    425     pub const Source = union(enum) {
    426         file: std.fs.File,
    427         stdio: std.fs.File,
    428         memory: std.ArrayListUnmanaged(u8),
    429         /// The source has been closed and any usage of the Source in this state is illegal (except deinit).
    430         closed: void,
    431 
    432         pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !Source {
    433             switch (source) {
    434                 .filename => |filename| return .{
    435                     .file = switch (io) {
    436                         .input => try openFileNotDir(std.fs.cwd(), filename, .{}),
    437                         .output => try std.fs.cwd().createFile(filename, .{}),
    438                     },
    439                 },
    440                 .stdio => |file| return .{ .stdio = file },
    441             }
    442         }
    443 
    444         pub fn deinit(self: *Source, allocator: std.mem.Allocator) void {
    445             switch (self.*) {
    446                 .file => |file| file.close(),
    447                 .stdio => {},
    448                 .memory => |*list| list.deinit(allocator),
    449                 .closed => {},
    450             }
    451         }
    452 
    453         pub const Data = struct {
    454             bytes: []const u8,
    455             needs_free: bool,
    456 
    457             pub fn deinit(self: Data, allocator: std.mem.Allocator) void {
    458                 if (self.needs_free) {
    459                     allocator.free(self.bytes);
    460                 }
    461             }
    462         };
    463 
    464         pub fn readAll(self: Source, allocator: std.mem.Allocator) !Data {
    465             return switch (self) {
    466                 inline .file, .stdio => |file| .{
    467                     .bytes = try file.readToEndAlloc(allocator, std.math.maxInt(usize)),
    468                     .needs_free = true,
    469                 },
    470                 .memory => |list| .{ .bytes = list.items, .needs_free = false },
    471                 .closed => unreachable,
    472             };
    473         }
    474 
    475         pub const WriterContext = struct {
    476             self: *Source,
    477             allocator: std.mem.Allocator,
    478         };
    479         pub const WriteError = std.mem.Allocator.Error || std.fs.File.WriteError;
    480         pub const Writer = std.io.GenericWriter(WriterContext, WriteError, write);
    481 
    482         pub fn write(ctx: WriterContext, bytes: []const u8) WriteError!usize {
    483             switch (ctx.self.*) {
    484                 inline .file, .stdio => |file| return file.write(bytes),
    485                 .memory => |*list| {
    486                     try list.appendSlice(ctx.allocator, bytes);
    487                     return bytes.len;
    488                 },
    489                 .closed => unreachable,
    490             }
    491         }
    492 
    493         pub fn writer(self: *Source, allocator: std.mem.Allocator) Writer {
    494             return .{ .context = .{ .self = self, .allocator = allocator } };
    495         }
    496     };
    497 };
    498 
    499 const LazyIncludePaths = struct {
    500     arena: std.mem.Allocator,
    501     auto_includes_option: cli.Options.AutoIncludes,
    502     zig_lib_dir: []const u8,
    503     target_machine_type: std.coff.MachineType,
    504     resolved_include_paths: ?[]const []const u8 = null,
    505 
    506     pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 {
    507         if (self.resolved_include_paths) |include_paths|
    508             return include_paths;
    509 
    510         return getIncludePaths(self.arena, self.auto_includes_option, self.zig_lib_dir, self.target_machine_type) catch |err| switch (err) {
    511             error.OutOfMemory => |e| return e,
    512             else => |e| {
    513                 switch (e) {
    514                     error.UnsupportedAutoIncludesMachineType => {
    515                         try error_handler.emitMessage(self.arena, .err, "automatic include path detection is not supported for target '{s}'", .{@tagName(self.target_machine_type)});
    516                     },
    517                     error.MsvcIncludesNotFound => {
    518                         try error_handler.emitMessage(self.arena, .err, "MSVC include paths could not be automatically detected", .{});
    519                     },
    520                     error.MingwIncludesNotFound => {
    521                         try error_handler.emitMessage(self.arena, .err, "MinGW include paths could not be automatically detected", .{});
    522                     },
    523                 }
    524                 try error_handler.emitMessage(self.arena, .note, "to disable auto includes, use the option /:auto-includes none", .{});
    525                 std.process.exit(1);
    526             },
    527         };
    528     }
    529 };
    530 
    531 fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8, target_machine_type: std.coff.MachineType) ![]const []const u8 {
    532     if (auto_includes_option == .none) return &[_][]const u8{};
    533 
    534     const includes_arch: std.Target.Cpu.Arch = switch (target_machine_type) {
    535         .X64 => .x86_64,
    536         .I386 => .x86,
    537         .ARMNT => .thumb,
    538         .ARM64 => .aarch64,
    539         .ARM64EC => .aarch64,
    540         .ARM64X => .aarch64,
    541         .IA64, .EBC => {
    542             return error.UnsupportedAutoIncludesMachineType;
    543         },
    544         // The above cases are exhaustive of all the `MachineType`s supported (see supported_targets in cvtres.zig)
    545         // This is enforced by the argument parser in cli.zig.
    546         else => unreachable,
    547     };
    548 
    549     var includes = auto_includes_option;
    550     if (builtin.target.os.tag != .windows) {
    551         switch (includes) {
    552             .none => unreachable,
    553             // MSVC can't be found when the host isn't Windows, so short-circuit.
    554             .msvc => return error.MsvcIncludesNotFound,
    555             // Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts.
    556             .any => includes = .gnu,
    557             .gnu => {},
    558         }
    559     }
    560 
    561     while (true) {
    562         switch (includes) {
    563             .none => unreachable,
    564             .any, .msvc => {
    565                 // MSVC is only detectable on Windows targets. This unreachable is to signify
    566                 // that .any and .msvc should be dealt with on non-Windows targets before this point,
    567                 // since getting MSVC include paths uses Windows-only APIs.
    568                 if (builtin.target.os.tag != .windows) unreachable;
    569 
    570                 const target_query: std.Target.Query = .{
    571                     .os_tag = .windows,
    572                     .cpu_arch = includes_arch,
    573                     .abi = .msvc,
    574                 };
    575                 const target = std.zig.resolveTargetQueryOrFatal(target_query);
    576                 const is_native_abi = target_query.isNativeAbi();
    577                 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch {
    578                     if (includes == .any) {
    579                         // fall back to mingw
    580                         includes = .gnu;
    581                         continue;
    582                     }
    583                     return error.MsvcIncludesNotFound;
    584                 };
    585                 if (detected_libc.libc_include_dir_list.len == 0) {
    586                     if (includes == .any) {
    587                         // fall back to mingw
    588                         includes = .gnu;
    589                         continue;
    590                     }
    591                     return error.MsvcIncludesNotFound;
    592                 }
    593                 return detected_libc.libc_include_dir_list;
    594             },
    595             .gnu => {
    596                 const target_query: std.Target.Query = .{
    597                     .os_tag = .windows,
    598                     .cpu_arch = includes_arch,
    599                     .abi = .gnu,
    600                 };
    601                 const target = std.zig.resolveTargetQueryOrFatal(target_query);
    602                 const is_native_abi = target_query.isNativeAbi();
    603                 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {
    604                     error.OutOfMemory => |e| return e,
    605                     else => return error.MingwIncludesNotFound,
    606                 };
    607                 return detected_libc.libc_include_dir_list;
    608             },
    609         }
    610     }
    611 }
    612 
    613 const ErrorBundle = std.zig.ErrorBundle;
    614 const SourceMappings = @import("source_mapping.zig").SourceMappings;
    615 
    616 const ErrorHandler = union(enum) {
    617     server: std.zig.Server,
    618     tty: std.io.tty.Config,
    619 
    620     pub fn emitCliDiagnostics(
    621         self: *ErrorHandler,
    622         allocator: std.mem.Allocator,
    623         args: []const []const u8,
    624         diagnostics: *cli.Diagnostics,
    625     ) !void {
    626         switch (self.*) {
    627             .server => |*server| {
    628                 var error_bundle = try cliDiagnosticsToErrorBundle(allocator, diagnostics);
    629                 defer error_bundle.deinit(allocator);
    630 
    631                 try server.serveErrorBundle(error_bundle);
    632             },
    633             .tty => {
    634                 diagnostics.renderToStdErr(args, self.tty);
    635             },
    636         }
    637     }
    638 
    639     pub fn emitAroDiagnostics(
    640         self: *ErrorHandler,
    641         allocator: std.mem.Allocator,
    642         fail_msg: []const u8,
    643         comp: *aro.Compilation,
    644     ) !void {
    645         switch (self.*) {
    646             .server => |*server| {
    647                 var error_bundle = try aroDiagnosticsToErrorBundle(allocator, fail_msg, comp);
    648                 defer error_bundle.deinit(allocator);
    649 
    650                 try server.serveErrorBundle(error_bundle);
    651             },
    652             .tty => {
    653                 // extra newline to separate this line from the aro errors
    654                 const stderr = std.debug.lockStderrWriter(&.{});
    655                 defer std.debug.unlockStderrWriter();
    656                 try renderErrorMessage(stderr, self.tty, .err, "{s}\n", .{fail_msg});
    657                 aro.Diagnostics.render(comp, self.tty);
    658             },
    659         }
    660     }
    661 
    662     pub fn emitDiagnostics(
    663         self: *ErrorHandler,
    664         allocator: std.mem.Allocator,
    665         cwd: std.fs.Dir,
    666         source: []const u8,
    667         diagnostics: *Diagnostics,
    668         mappings: SourceMappings,
    669     ) !void {
    670         switch (self.*) {
    671             .server => |*server| {
    672                 var error_bundle = try diagnosticsToErrorBundle(allocator, source, diagnostics, mappings);
    673                 defer error_bundle.deinit(allocator);
    674 
    675                 try server.serveErrorBundle(error_bundle);
    676             },
    677             .tty => {
    678                 diagnostics.renderToStdErr(cwd, source, self.tty, mappings);
    679             },
    680         }
    681     }
    682 
    683     pub fn emitMessage(
    684         self: *ErrorHandler,
    685         allocator: std.mem.Allocator,
    686         msg_type: @import("utils.zig").ErrorMessageType,
    687         comptime format: []const u8,
    688         args: anytype,
    689     ) !void {
    690         switch (self.*) {
    691             .server => |*server| {
    692                 // only emit errors
    693                 if (msg_type != .err) return;
    694 
    695                 var error_bundle = try errorStringToErrorBundle(allocator, format, args);
    696                 defer error_bundle.deinit(allocator);
    697 
    698                 try server.serveErrorBundle(error_bundle);
    699             },
    700             .tty => {
    701                 const stderr = std.debug.lockStderrWriter(&.{});
    702                 defer std.debug.unlockStderrWriter();
    703                 try renderErrorMessage(stderr, self.tty, msg_type, format, args);
    704             },
    705         }
    706     }
    707 };
    708 
    709 fn cliDiagnosticsToErrorBundle(
    710     gpa: std.mem.Allocator,
    711     diagnostics: *cli.Diagnostics,
    712 ) !ErrorBundle {
    713     @branchHint(.cold);
    714 
    715     var bundle: ErrorBundle.Wip = undefined;
    716     try bundle.init(gpa);
    717     errdefer bundle.deinit();
    718 
    719     try bundle.addRootErrorMessage(.{
    720         .msg = try bundle.addString("invalid command line option(s)"),
    721     });
    722 
    723     var cur_err: ?ErrorBundle.ErrorMessage = null;
    724     var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty;
    725     defer cur_notes.deinit(gpa);
    726     for (diagnostics.errors.items) |err_details| {
    727         switch (err_details.type) {
    728             .err => {
    729                 if (cur_err) |err| {
    730                     try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
    731                 }
    732                 cur_err = .{
    733                     .msg = try bundle.addString(err_details.msg.items),
    734                 };
    735                 cur_notes.clearRetainingCapacity();
    736             },
    737             .warning => cur_err = null,
    738             .note => {
    739                 if (cur_err == null) continue;
    740                 cur_err.?.notes_len += 1;
    741                 try cur_notes.append(gpa, .{
    742                     .msg = try bundle.addString(err_details.msg.items),
    743                 });
    744             },
    745         }
    746     }
    747     if (cur_err) |err| {
    748         try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
    749     }
    750 
    751     return try bundle.toOwnedBundle("");
    752 }
    753 
    754 fn diagnosticsToErrorBundle(
    755     gpa: std.mem.Allocator,
    756     source: []const u8,
    757     diagnostics: *Diagnostics,
    758     mappings: SourceMappings,
    759 ) !ErrorBundle {
    760     @branchHint(.cold);
    761 
    762     var bundle: ErrorBundle.Wip = undefined;
    763     try bundle.init(gpa);
    764     errdefer bundle.deinit();
    765 
    766     var msg_buf: std.ArrayListUnmanaged(u8) = .empty;
    767     defer msg_buf.deinit(gpa);
    768     var cur_err: ?ErrorBundle.ErrorMessage = null;
    769     var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty;
    770     defer cur_notes.deinit(gpa);
    771     for (diagnostics.errors.items) |err_details| {
    772         switch (err_details.type) {
    773             .hint => continue,
    774             // Clear the current error so that notes don't bleed into unassociated errors
    775             .warning => {
    776                 cur_err = null;
    777                 continue;
    778             },
    779             .note => if (cur_err == null) continue,
    780             .err => {},
    781         }
    782         const corresponding_span = mappings.getCorrespondingSpan(err_details.token.line_number).?;
    783         const err_line = corresponding_span.start_line;
    784         const err_filename = mappings.files.get(corresponding_span.filename_offset);
    785 
    786         const source_line_start = err_details.token.getLineStartForErrorDisplay(source);
    787         // Treat tab stops as 1 column wide for error display purposes,
    788         // and add one to get a 1-based column
    789         const column = err_details.token.calculateColumn(source, 1, source_line_start) + 1;
    790 
    791         msg_buf.clearRetainingCapacity();
    792         try err_details.render(msg_buf.writer(gpa), source, diagnostics.strings.items);
    793 
    794         const src_loc = src_loc: {
    795             var src_loc: ErrorBundle.SourceLocation = .{
    796                 .src_path = try bundle.addString(err_filename),
    797                 .line = @intCast(err_line - 1), // 1-based -> 0-based
    798                 .column = @intCast(column - 1), // 1-based -> 0-based
    799                 .span_start = 0,
    800                 .span_main = 0,
    801                 .span_end = 0,
    802             };
    803             if (err_details.print_source_line) {
    804                 const source_line = err_details.token.getLineForErrorDisplay(source, source_line_start);
    805                 const visual_info = err_details.visualTokenInfo(source_line_start, source_line_start + source_line.len, source);
    806                 src_loc.span_start = @intCast(visual_info.point_offset - visual_info.before_len);
    807                 src_loc.span_main = @intCast(visual_info.point_offset);
    808                 src_loc.span_end = @intCast(visual_info.point_offset + 1 + visual_info.after_len);
    809                 src_loc.source_line = try bundle.addString(source_line);
    810             }
    811             break :src_loc try bundle.addSourceLocation(src_loc);
    812         };
    813 
    814         switch (err_details.type) {
    815             .err => {
    816                 if (cur_err) |err| {
    817                     try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
    818                 }
    819                 cur_err = .{
    820                     .msg = try bundle.addString(msg_buf.items),
    821                     .src_loc = src_loc,
    822                 };
    823                 cur_notes.clearRetainingCapacity();
    824             },
    825             .note => {
    826                 cur_err.?.notes_len += 1;
    827                 try cur_notes.append(gpa, .{
    828                     .msg = try bundle.addString(msg_buf.items),
    829                     .src_loc = src_loc,
    830                 });
    831             },
    832             .warning, .hint => unreachable,
    833         }
    834     }
    835     if (cur_err) |err| {
    836         try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
    837     }
    838 
    839     return try bundle.toOwnedBundle("");
    840 }
    841 
    842 fn flushErrorMessageIntoBundle(wip: *ErrorBundle.Wip, msg: ErrorBundle.ErrorMessage, notes: []const ErrorBundle.ErrorMessage) !void {
    843     try wip.addRootErrorMessage(msg);
    844     const notes_start = try wip.reserveNotes(@intCast(notes.len));
    845     for (notes_start.., notes) |i, note| {
    846         wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note));
    847     }
    848 }
    849 
    850 fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
    851     @branchHint(.cold);
    852     var bundle: ErrorBundle.Wip = undefined;
    853     try bundle.init(allocator);
    854     errdefer bundle.deinit();
    855     try bundle.addRootErrorMessage(.{
    856         .msg = try bundle.printString(format, args),
    857     });
    858     return try bundle.toOwnedBundle("");
    859 }
    860 
    861 fn aroDiagnosticsToErrorBundle(
    862     gpa: std.mem.Allocator,
    863     fail_msg: []const u8,
    864     comp: *aro.Compilation,
    865 ) !ErrorBundle {
    866     @branchHint(.cold);
    867 
    868     var bundle: ErrorBundle.Wip = undefined;
    869     try bundle.init(gpa);
    870     errdefer bundle.deinit();
    871 
    872     try bundle.addRootErrorMessage(.{
    873         .msg = try bundle.addString(fail_msg),
    874     });
    875 
    876     var msg_writer = MsgWriter.init(gpa);
    877     defer msg_writer.deinit();
    878     var cur_err: ?ErrorBundle.ErrorMessage = null;
    879     var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty;
    880     defer cur_notes.deinit(gpa);
    881     for (comp.diagnostics.list.items) |msg| {
    882         switch (msg.kind) {
    883             // Clear the current error so that notes don't bleed into unassociated errors
    884             .off, .warning => {
    885                 cur_err = null;
    886                 continue;
    887             },
    888             .note => if (cur_err == null) continue,
    889             .@"fatal error", .@"error" => {},
    890             .default => unreachable,
    891         }
    892         msg_writer.resetRetainingCapacity();
    893         aro.Diagnostics.renderMessage(comp, &msg_writer, msg);
    894 
    895         const src_loc = src_loc: {
    896             if (msg_writer.path) |src_path| {
    897                 var src_loc: ErrorBundle.SourceLocation = .{
    898                     .src_path = try bundle.addString(src_path),
    899                     .line = msg_writer.line - 1, // 1-based -> 0-based
    900                     .column = msg_writer.col - 1, // 1-based -> 0-based
    901                     .span_start = 0,
    902                     .span_main = 0,
    903                     .span_end = 0,
    904                 };
    905                 if (msg_writer.source_line) |source_line| {
    906                     src_loc.span_start = msg_writer.span_main;
    907                     src_loc.span_main = msg_writer.span_main;
    908                     src_loc.span_end = msg_writer.span_main;
    909                     src_loc.source_line = try bundle.addString(source_line);
    910                 }
    911                 break :src_loc try bundle.addSourceLocation(src_loc);
    912             }
    913             break :src_loc ErrorBundle.SourceLocationIndex.none;
    914         };
    915 
    916         switch (msg.kind) {
    917             .@"fatal error", .@"error" => {
    918                 if (cur_err) |err| {
    919                     try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
    920                 }
    921                 cur_err = .{
    922                     .msg = try bundle.addString(msg_writer.buf.items),
    923                     .src_loc = src_loc,
    924                 };
    925                 cur_notes.clearRetainingCapacity();
    926             },
    927             .note => {
    928                 cur_err.?.notes_len += 1;
    929                 try cur_notes.append(gpa, .{
    930                     .msg = try bundle.addString(msg_writer.buf.items),
    931                     .src_loc = src_loc,
    932                 });
    933             },
    934             .off, .warning, .default => unreachable,
    935         }
    936     }
    937     if (cur_err) |err| {
    938         try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
    939     }
    940 
    941     return try bundle.toOwnedBundle("");
    942 }
    943 
    944 // Similar to aro.Diagnostics.MsgWriter but:
    945 // - Writers to an ArrayList
    946 // - Only prints the message itself (no location, source line, error: prefix, etc)
    947 // - Keeps track of source path/line/col instead
    948 const MsgWriter = struct {
    949     buf: std.array_list.Managed(u8),
    950     path: ?[]const u8 = null,
    951     // 1-indexed
    952     line: u32 = undefined,
    953     col: u32 = undefined,
    954     source_line: ?[]const u8 = null,
    955     span_main: u32 = undefined,
    956 
    957     fn init(allocator: std.mem.Allocator) MsgWriter {
    958         return .{
    959             .buf = std.array_list.Managed(u8).init(allocator),
    960         };
    961     }
    962 
    963     fn deinit(m: *MsgWriter) void {
    964         m.buf.deinit();
    965     }
    966 
    967     fn resetRetainingCapacity(m: *MsgWriter) void {
    968         m.buf.clearRetainingCapacity();
    969         m.path = null;
    970         m.source_line = null;
    971     }
    972 
    973     pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void {
    974         m.buf.writer().print(fmt, args) catch {};
    975     }
    976 
    977     pub fn write(m: *MsgWriter, msg: []const u8) void {
    978         m.buf.writer().writeAll(msg) catch {};
    979     }
    980 
    981     pub fn setColor(m: *MsgWriter, color: std.io.tty.Color) void {
    982         _ = m;
    983         _ = color;
    984     }
    985 
    986     pub fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void {
    987         m.path = path;
    988         m.line = line;
    989         m.col = col;
    990     }
    991 
    992     pub fn start(m: *MsgWriter, kind: aro.Diagnostics.Kind) void {
    993         _ = m;
    994         _ = kind;
    995     }
    996 
    997     pub fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void {
    998         _ = end_with_splice;
    999         m.source_line = maybe_line;
   1000         m.span_main = col;
   1001     }
   1002 };