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}