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