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