1//! Builds of the Zig compiler are distributed partly in source form. That
2//! source lives here. These APIs are provided as-is and have absolutely no API
3//! guarantees whatsoever.
4
5const builtin = @import("builtin");
6
7const std = @import("std.zig");
8const assert = std.debug.assert;
9const mem = std.mem;
10const log = std.log;
11const Allocator = std.mem.Allocator;
12const Io = std.Io;
13const Writer = std.Io.Writer;
14const Cache = std.Build.Cache;
15const fatal = std.process.fatal;
16const Dir = std.Io.Dir;
17
18const tokenizer = @import("zig/tokenizer.zig");
19
20pub const ErrorBundle = @import("zig/ErrorBundle.zig");
21pub const Server = @import("zig/Server.zig");
22pub const Client = @import("zig/Client.zig");
23pub const Token = tokenizer.Token;
24pub const Tokenizer = tokenizer.Tokenizer;
25pub const TokenSmith = @import("zig/TokenSmith.zig");
26pub const string_literal = @import("zig/string_literal.zig");
27pub const number_literal = @import("zig/number_literal.zig");
28pub const primitives = @import("zig/primitives.zig");
29pub const isPrimitive = primitives.isPrimitive;
30pub const Ast = @import("zig/Ast.zig");
31pub const AstGen = @import("zig/AstGen.zig");
32pub const Zir = @import("zig/Zir.zig");
33pub const Zoir = @import("zig/Zoir.zig");
34pub const ZonGen = @import("zig/ZonGen.zig");
35pub const system = @import("zig/system.zig");
36pub const BuiltinFn = @import("zig/BuiltinFn.zig");
37pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");
38pub const LibCInstallation = @import("zig/LibCInstallation.zig");
39pub const WindowsSdk = @import("zig/WindowsSdk.zig");
40pub const LibCDirs = @import("zig/LibCDirs.zig");
41pub const PkgConfig = @import("zig/PkgConfig.zig");
42pub const target = @import("zig/target.zig");
43pub const llvm = @import("zig/llvm.zig");
44
45pub const parser_generated_oracle = @import("zig/parser_generated_oracle.zig");
46
47// Character literal parsing
48pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;
49pub const parseCharLiteral = string_literal.parseCharLiteral;
50pub const parseNumberLiteral = number_literal.parseNumberLiteral;
51
52pub const c_translation = struct {
53 pub const builtins = @import("zig/c_translation/builtins.zig");
54 pub const helpers = @import("zig/c_translation/helpers.zig");
55};
56
57pub const default_local_zig_cache_basename = ".zig-cache";
58pub const build_zig_basename = "build.zig";
59
60pub const SrcHasher = std.crypto.hash.Blake3;
61pub const SrcHash = [16]u8;
62
63pub const Color = enum {
64 /// Auto-detect whether stream supports terminal colors.
65 auto,
66 /// Force-enable colors.
67 off,
68 /// Suppress colors.
69 on,
70
71 pub fn terminalMode(color: Color) ?Io.Terminal.Mode {
72 return switch (color) {
73 .auto => null,
74 .on => .escape_codes,
75 .off => .no_color,
76 };
77 }
78
79 /// Determine the preference for color or no color based on the NO_COLOR and
80 /// CLICOLOR_FORCE environment variables. Color is always disabled on WASI per
81 /// https://github.com/WebAssembly/WASI/issues/162
82 pub fn settingFromEnvironment(environ_map: *const std.process.Environ.Map) Color {
83 return if (builtin.os.tag == .wasi or EnvVar.NO_COLOR.isSet(environ_map))
84 .off
85 else if (EnvVar.CLICOLOR_FORCE.isSet(environ_map))
86 .on
87 else
88 .auto;
89 }
90};
91
92/// There are many assumptions in the entire codebase that Zig source files can
93/// be byte-indexed with a u32 integer.
94pub const max_src_size = std.math.maxInt(u32);
95
96pub fn hashSrc(src: []const u8) SrcHash {
97 var out: SrcHash = undefined;
98 SrcHasher.hash(src, &out, .{});
99 return out;
100}
101
102pub fn srcHashEql(a: SrcHash, b: SrcHash) bool {
103 return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
104}
105
106pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash {
107 var out: SrcHash = undefined;
108 var hasher = SrcHasher.init(.{});
109 hasher.update(&parent_hash);
110 hasher.update(sep);
111 hasher.update(name);
112 hasher.final(&out);
113 return out;
114}
115
116pub const Loc = struct {
117 line: usize,
118 column: usize,
119 /// Does not include the trailing newline.
120 source_line: []const u8,
121
122 pub fn eql(a: Loc, b: Loc) bool {
123 return a.line == b.line and a.column == b.column and mem.eql(u8, a.source_line, b.source_line);
124 }
125};
126
127pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
128 var line: usize = 0;
129 var column: usize = 0;
130 var line_start: usize = 0;
131 var i: usize = 0;
132 while (i < byte_offset) : (i += 1) {
133 switch (source[i]) {
134 '\n' => {
135 line += 1;
136 column = 0;
137 line_start = i + 1;
138 },
139 else => {
140 column += 1;
141 },
142 }
143 }
144 while (i < source.len and source[i] != '\n') {
145 i += 1;
146 }
147 return .{
148 .line = line,
149 .column = column,
150 .source_line = source[line_start..i],
151 };
152}
153
154pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
155 var line: isize = 0;
156 if (end >= start) {
157 for (source[start..end]) |byte| switch (byte) {
158 '\n' => line += 1,
159 else => continue,
160 };
161 } else {
162 for (source[end..start]) |byte| switch (byte) {
163 '\n' => line -= 1,
164 else => continue,
165 };
166 }
167 return line;
168}
169
170pub const BinNameOptions = struct {
171 root_name: []const u8,
172 cpu_arch: std.Target.Cpu.Arch,
173 os_tag: std.Target.Os.Tag,
174 ofmt: std.Target.ObjectFormat,
175 abi: std.Target.Abi,
176 output_mode: std.lang.OutputMode,
177 link_mode: ?std.lang.LinkMode = null,
178 version: ?std.SemanticVersion = null,
179};
180
181/// Returns the standard file system basename of a binary generated by the Zig compiler.
182pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 {
183 const root_name = options.root_name;
184 switch (options.ofmt) {
185 .coff => switch (options.output_mode) {
186 .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{
187 root_name,
188 options.os_tag.exeFileExt(options.cpu_arch),
189 }),
190 .Lib => {
191 const suffix = switch (options.link_mode orelse .static) {
192 .static => ".lib",
193 .dynamic => ".dll",
194 };
195 return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
196 },
197 .Obj => return std.fmt.allocPrint(allocator, "{s}.obj", .{root_name}),
198 },
199 .elf => switch (options.output_mode) {
200 .Exe => return allocator.dupe(u8, root_name),
201 .Lib => {
202 switch (options.link_mode orelse .static) {
203 .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
204 options.os_tag.libPrefix(options.abi), root_name,
205 }),
206 .dynamic => {
207 if (options.version) |ver| {
208 return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{
209 options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch,
210 });
211 } else {
212 return std.fmt.allocPrint(allocator, "{s}{s}.so", .{
213 options.os_tag.libPrefix(options.abi), root_name,
214 });
215 }
216 },
217 }
218 },
219 .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
220 },
221 .macho => switch (options.output_mode) {
222 .Exe => return allocator.dupe(u8, root_name),
223 .Lib => {
224 switch (options.link_mode orelse .static) {
225 .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
226 options.os_tag.libPrefix(options.abi), root_name,
227 }),
228 .dynamic => {
229 if (options.version) |ver| {
230 return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{
231 options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch,
232 });
233 } else {
234 return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{
235 options.os_tag.libPrefix(options.abi), root_name,
236 });
237 }
238 },
239 }
240 },
241 .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
242 },
243 .wasm => switch (options.output_mode) {
244 .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{
245 root_name,
246 options.os_tag.exeFileExt(options.cpu_arch),
247 }),
248 .Lib => {
249 switch (options.link_mode orelse .static) {
250 .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
251 options.os_tag.libPrefix(options.abi), root_name,
252 }),
253 .dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
254 }
255 },
256 .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
257 },
258 .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
259 .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}),
260 .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
261 .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
262 .plan9 => switch (options.output_mode) {
263 .Exe => return allocator.dupe(u8, root_name),
264 .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{
265 root_name, options.ofmt.fileExt(options.cpu_arch),
266 }),
267 .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
268 options.os_tag.libPrefix(options.abi), root_name,
269 }),
270 },
271 }
272}
273
274pub const SanitizeC = enum {
275 off,
276 trap,
277 full,
278};
279
280pub const BuildId = union(enum) {
281 none,
282 fast,
283 uuid,
284 sha1,
285 md5,
286 hexstring: HexString,
287
288 pub fn eql(a: BuildId, b: BuildId) bool {
289 const Tag = @typeInfo(BuildId).@"union".tag_type.?;
290 const a_tag: Tag = a;
291 const b_tag: Tag = b;
292 if (a_tag != b_tag) return false;
293 return switch (a) {
294 .none, .fast, .uuid, .sha1, .md5 => true,
295 .hexstring => |a_hexstring| mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()),
296 };
297 }
298
299 pub const HexString = struct {
300 bytes: [32]u8,
301 len: u8,
302
303 /// Result is byte values, *not* hex-encoded.
304 pub fn toSlice(hs: *const HexString) []const u8 {
305 return hs.bytes[0..hs.len];
306 }
307 };
308
309 /// Input is byte values, *not* hex-encoded.
310 /// Asserts `bytes` fits inside `HexString`
311 pub fn initHexString(bytes: []const u8) BuildId {
312 var result: BuildId = .{ .hexstring = .{
313 .bytes = undefined,
314 .len = @intCast(bytes.len),
315 } };
316 @memcpy(result.hexstring.bytes[0..bytes.len], bytes);
317 return result;
318 }
319
320 /// Converts UTF-8 text to a `BuildId`.
321 pub fn parse(text: []const u8) !BuildId {
322 if (mem.eql(u8, text, "none")) {
323 return .none;
324 } else if (mem.eql(u8, text, "fast")) {
325 return .fast;
326 } else if (mem.eql(u8, text, "uuid")) {
327 return .uuid;
328 } else if (mem.eql(u8, text, "sha1") or mem.eql(u8, text, "tree")) {
329 return .sha1;
330 } else if (mem.eql(u8, text, "md5")) {
331 return .md5;
332 } else if (mem.startsWith(u8, text, "0x")) {
333 var result: BuildId = .{ .hexstring = undefined };
334 const slice = try std.fmt.hexToBytes(&result.hexstring.bytes, text[2..]);
335 result.hexstring.len = @as(u8, @intCast(slice.len));
336 return result;
337 }
338 return error.InvalidBuildIdStyle;
339 }
340
341 test parse {
342 try std.testing.expectEqual(BuildId.md5, try parse("md5"));
343 try std.testing.expectEqual(BuildId.none, try parse("none"));
344 try std.testing.expectEqual(BuildId.fast, try parse("fast"));
345 try std.testing.expectEqual(BuildId.uuid, try parse("uuid"));
346 try std.testing.expectEqual(BuildId.sha1, try parse("sha1"));
347 try std.testing.expectEqual(BuildId.sha1, try parse("tree"));
348
349 try std.testing.expect(BuildId.initHexString("").eql(try parse("0x")));
350 try std.testing.expect(BuildId.initHexString("\x12\x34\x56").eql(try parse("0x123456")));
351 try std.testing.expectError(error.InvalidLength, parse("0x12-34"));
352 try std.testing.expectError(error.InvalidCharacter, parse("0xfoobbb"));
353 try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));
354 }
355
356 pub fn format(id: BuildId, writer: *Writer) Writer.Error!void {
357 switch (id) {
358 .none, .fast, .uuid, .sha1, .md5 => {
359 try writer.writeAll(@tagName(id));
360 },
361 .hexstring => |hs| {
362 try writer.print("0x{x}", .{hs.toSlice()});
363 },
364 }
365 }
366
367 test format {
368 try std.testing.expectFmt("none", "{f}", .{@as(BuildId, .none)});
369 try std.testing.expectFmt("fast", "{f}", .{@as(BuildId, .fast)});
370 try std.testing.expectFmt("uuid", "{f}", .{@as(BuildId, .uuid)});
371 try std.testing.expectFmt("sha1", "{f}", .{@as(BuildId, .sha1)});
372 try std.testing.expectFmt("md5", "{f}", .{@as(BuildId, .md5)});
373 try std.testing.expectFmt("0x", "{f}", .{BuildId.initHexString("")});
374 try std.testing.expectFmt("0x1234cdef", "{f}", .{BuildId.initHexString("\x12\x34\xcd\xef")});
375 }
376};
377
378pub const LtoMode = enum { none, full, thin };
379
380pub const Subsystem = enum {
381 console,
382 windows,
383 posix,
384 native,
385 efi_application,
386 efi_boot_service_driver,
387 efi_rom,
388 efi_runtime_driver,
389};
390
391pub const CompressDebugSections = enum(u2) { none, zlib, zstd };
392
393pub const RcIncludes = enum(u2) {
394 /// Use MSVC if available, fall back to MinGW.
395 any,
396 /// Use MSVC include paths (MSVC install + Windows SDK, must be present on the system).
397 msvc,
398 /// Use MinGW include paths (distributed with Zig).
399 gnu,
400 /// Do not use any autodetected include paths.
401 none,
402};
403
404/// Renders a `std.Target.Cpu` value into a textual representation that can be parsed
405/// via the `-mcpu` flag passed to the Zig compiler.
406/// Appends the result to `buffer`.
407pub fn serializeCpu(buffer: *std.array_list.Managed(u8), cpu: std.Target.Cpu) Allocator.Error!void {
408 const all_features = cpu.arch.allFeaturesList();
409 var populated_cpu_features = cpu.model.features;
410 populated_cpu_features.populateDependencies(all_features);
411
412 try buffer.appendSlice(cpu.model.name);
413
414 if (populated_cpu_features.eql(cpu.features)) {
415 // The CPU name alone is sufficient.
416 return;
417 }
418
419 for (all_features, 0..) |feature, i_usize| {
420 const i: std.Target.Cpu.Feature.Set.Index = @intCast(i_usize);
421 const in_cpu_set = populated_cpu_features.isEnabled(i);
422 const in_actual_set = cpu.features.isEnabled(i);
423 try buffer.ensureUnusedCapacity(feature.name.len + 1);
424 if (in_cpu_set and !in_actual_set) {
425 buffer.appendAssumeCapacity('-');
426 buffer.appendSliceAssumeCapacity(feature.name);
427 } else if (!in_cpu_set and in_actual_set) {
428 buffer.appendAssumeCapacity('+');
429 buffer.appendSliceAssumeCapacity(feature.name);
430 }
431 }
432}
433
434pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![]u8 {
435 var buffer = std.array_list.Managed(u8).init(ally);
436 try serializeCpu(&buffer, cpu);
437 return buffer.toOwnedSlice();
438}
439
440/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
441///
442/// See also `fmtIdFlags`.
443pub fn fmtId(bytes: []const u8) FormatId {
444 return .{ .bytes = bytes, .flags = .{} };
445}
446
447/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
448///
449/// See also `fmtId`.
450pub fn fmtIdFlags(bytes: []const u8, flags: FormatId.Flags) FormatId {
451 return .{ .bytes = bytes, .flags = flags };
452}
453
454pub fn fmtIdPU(bytes: []const u8) FormatId {
455 return .{ .bytes = bytes, .flags = .{ .allow_primitive = true, .allow_underscore = true } };
456}
457
458pub fn fmtIdP(bytes: []const u8) FormatId {
459 return .{ .bytes = bytes, .flags = .{ .allow_primitive = true } };
460}
461
462test fmtId {
463 const expectFmt = std.testing.expectFmt;
464 try expectFmt("@\"while\"", "{f}", .{fmtId("while")});
465 try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true })});
466 try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_underscore = true })});
467 try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true, .allow_underscore = true })});
468
469 try expectFmt("hello", "{f}", .{fmtId("hello")});
470 try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true })});
471 try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_underscore = true })});
472 try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true, .allow_underscore = true })});
473
474 try expectFmt("@\"type\"", "{f}", .{fmtId("type")});
475 try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true })});
476 try expectFmt("@\"type\"", "{f}", .{fmtIdFlags("type", .{ .allow_underscore = true })});
477 try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true, .allow_underscore = true })});
478
479 try expectFmt("@\"_\"", "{f}", .{fmtId("_")});
480 try expectFmt("@\"_\"", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true })});
481 try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_underscore = true })});
482 try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true, .allow_underscore = true })});
483
484 try expectFmt("@\"i123\"", "{f}", .{fmtId("i123")});
485 try expectFmt("i123", "{f}", .{fmtIdFlags("i123", .{ .allow_primitive = true })});
486 try expectFmt("@\"4four\"", "{f}", .{fmtId("4four")});
487 try expectFmt("_underscore", "{f}", .{fmtId("_underscore")});
488 try expectFmt("@\"11\\\"23\"", "{f}", .{fmtId("11\"23")});
489 try expectFmt("@\"11\\x0f23\"", "{f}", .{fmtId("11\x0F23")});
490
491 // These are technically not currently legal in Zig.
492 try expectFmt("@\"\"", "{f}", .{fmtId("")});
493 try expectFmt("@\"\\x00\"", "{f}", .{fmtId("\x00")});
494}
495
496pub const FormatId = struct {
497 bytes: []const u8,
498 flags: Flags,
499 pub const Flags = struct {
500 allow_primitive: bool = false,
501 allow_underscore: bool = false,
502 };
503
504 /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
505 pub fn format(ctx: FormatId, writer: *Writer) Writer.Error!void {
506 const bytes = ctx.bytes;
507 if (isValidId(bytes) and
508 (ctx.flags.allow_primitive or !isPrimitive(bytes)) and
509 (ctx.flags.allow_underscore or !isUnderscore(bytes)))
510 {
511 return writer.writeAll(bytes);
512 }
513 try writer.writeAll("@\"");
514 try stringEscape(bytes, writer);
515 try writer.writeByte('"');
516 }
517};
518
519/// Return a formatter for escaping a double quoted Zig string.
520pub fn fmtString(bytes: []const u8) std.fmt.Alt([]const u8, stringEscape) {
521 return .{ .data = bytes };
522}
523
524/// Return a formatter for escaping a single quoted Zig string.
525pub fn fmtChar(c: u21) std.fmt.Alt(u21, charEscape) {
526 return .{ .data = c };
527}
528
529test fmtString {
530 try std.testing.expectFmt("\\x0f", "{f}", .{fmtString("\x0f")});
531 try std.testing.expectFmt(
532 \\" \\ hi \x07 \x11 \" derp '"
533 , "\"{f}\"", .{fmtString(" \\ hi \x07 \x11 \" derp '")});
534}
535
536test fmtChar {
537 try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('⚡') });
538}
539
540/// Print the string as escaped contents of a double quoted string.
541///
542/// The following transformations are made:
543/// * escaped: '\n', '\r', '\t', '\\', '"'
544/// * hex-encoded: ascii control characters
545///
546/// Everything else is passed through unmodified.
547pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
548 _ = try stringEscapeCounting(bytes, w);
549}
550
551pub fn stringEscapeCounting(bytes: []const u8, w: *Writer) Writer.Error!usize {
552 var n: usize = 0;
553 for (bytes) |byte| switch (byte) {
554 '\t' => {
555 try w.writeAll("\\t");
556 n += 2;
557 },
558 '\n' => {
559 try w.writeAll("\\n");
560 n += 2;
561 },
562 '\r' => {
563 try w.writeAll("\\r");
564 n += 2;
565 },
566 '\\' => {
567 try w.writeAll("\\\\");
568 n += 2;
569 },
570 '"' => {
571 try w.writeAll("\\\"");
572 n += 2;
573 },
574 0...8, 11, 12, 14...0x1f, 0x7f => {
575 try w.writeAll("\\x");
576 try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
577 n += 4;
578 },
579 else => {
580 try w.writeByte(byte);
581 n += 1;
582 },
583 };
584 return n;
585}
586
587pub const StringEscapeWriter = struct {
588 out: *Writer,
589 writer: Writer,
590
591 pub fn init(out: *Writer, buffer: []u8) @This() {
592 return .{
593 .out = out,
594 .writer = .{
595 .vtable = &.{ .drain = @This().drain },
596 .buffer = buffer,
597 },
598 };
599 }
600
601 fn drain(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
602 const sew: *StringEscapeWriter = @alignCast(@fieldParentPtr("writer", w));
603 const out = sew.out;
604 try stringEscape(w.buffered(), out);
605 w.end = 0;
606 var n: usize = 0;
607 for (data[0 .. data.len - 1]) |bytes| {
608 try stringEscape(bytes, out);
609 n += bytes.len;
610 }
611 const pattern = data[data.len - 1];
612 for (0..splat) |_| {
613 try stringEscape(pattern, out);
614 n += pattern.len;
615 }
616 return n;
617 }
618};
619
620test StringEscapeWriter {
621 const bytes = "\x7f\t\n\r\\\"abc";
622 const escaped = "\\x7f\\t\\n\\r\\\\\\\"abc";
623
624 var out_buf: [escaped.len]u8 = undefined;
625 var out: Io.Writer = .fixed(&out_buf);
626 var w: StringEscapeWriter = .init(&out, &.{});
627
628 const n = try w.writer.write(bytes);
629 try std.testing.expectEqual(bytes.len, n);
630 try std.testing.expectEqualStrings(escaped, out.buffered());
631}
632
633/// Print as escaped contents of a single-quoted string.
634pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void {
635 switch (codepoint) {
636 '\n' => try w.writeAll("\\n"),
637 '\r' => try w.writeAll("\\r"),
638 '\t' => try w.writeAll("\\t"),
639 '\\' => try w.writeAll("\\\\"),
640 '\'' => try w.writeAll("\\'"),
641 '"', ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(@intCast(codepoint)),
642 else => {
643 if (std.math.cast(u8, codepoint)) |byte| {
644 try w.writeAll("\\x");
645 try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
646 } else {
647 try w.writeAll("\\u{");
648 try w.printInt(codepoint, 16, .lower, .{});
649 try w.writeByte('}');
650 }
651 },
652 }
653}
654
655pub fn isValidId(bytes: []const u8) bool {
656 if (bytes.len == 0) return false;
657 for (bytes, 0..) |c, i| {
658 switch (c) {
659 '_', 'a'...'z', 'A'...'Z' => {},
660 '0'...'9' => if (i == 0) return false,
661 else => return false,
662 }
663 }
664 return Token.getKeyword(bytes) == null;
665}
666
667test isValidId {
668 try std.testing.expect(!isValidId(""));
669 try std.testing.expect(isValidId("foobar"));
670 try std.testing.expect(!isValidId("a b c"));
671 try std.testing.expect(!isValidId("3d"));
672 try std.testing.expect(!isValidId("enum"));
673 try std.testing.expect(isValidId("i386"));
674}
675
676pub fn isUnderscore(bytes: []const u8) bool {
677 return bytes.len == 1 and bytes[0] == '_';
678}
679
680test isUnderscore {
681 try std.testing.expect(isUnderscore("_"));
682 try std.testing.expect(!isUnderscore("__"));
683 try std.testing.expect(!isUnderscore("_foo"));
684 try std.testing.expect(isUnderscore("\x5f"));
685 try std.testing.expect(!isUnderscore("\\x5f"));
686}
687
688/// If the source can be UTF-16LE encoded, this function asserts that `gpa`
689/// will align a byte-sized allocation to at least 2. Allocators that don't do
690/// this are rare.
691pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![:0]u8 {
692 var buffer: std.ArrayList(u8) = .empty;
693 defer buffer.deinit(gpa);
694
695 if (file_reader.getSize()) |size| {
696 const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong;
697 // +1 to avoid resizing for the null byte added in toOwnedSliceSentinel below.
698 try buffer.ensureTotalCapacityPrecise(gpa, casted_size + 1);
699 } else |_| {}
700
701 try file_reader.interface.appendRemaining(gpa, &buffer, .limited(max_src_size));
702
703 // Detect unsupported file types with their Byte Order Mark
704 const unsupported_boms = [_][]const u8{
705 "\xff\xfe\x00\x00", // UTF-32 little endian
706 "\xfe\xff\x00\x00", // UTF-32 big endian
707 "\xfe\xff", // UTF-16 big endian
708 };
709 for (unsupported_boms) |bom| {
710 if (mem.startsWith(u8, buffer.items, bom)) {
711 return error.UnsupportedEncoding;
712 }
713 }
714
715 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
716 if (mem.startsWith(u8, buffer.items, "\xff\xfe")) {
717 if (buffer.items.len % 2 != 0) return error.InvalidEncoding;
718 return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(@alignCast(buffer.items))) catch |err| switch (err) {
719 error.DanglingSurrogateHalf => error.UnsupportedEncoding,
720 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
721 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
722 else => |e| return e,
723 };
724 }
725
726 return buffer.toOwnedSliceSentinel(gpa, 0);
727}
728
729pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void {
730 var wip_errors: ErrorBundle.Wip = undefined;
731 try wip_errors.init(gpa);
732 defer wip_errors.deinit();
733
734 try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);
735
736 var error_bundle = try wip_errors.toOwnedBundle("");
737 defer error_bundle.deinit(gpa);
738 return error_bundle.renderToStderr(io, .{}, color);
739}
740
741pub fn putAstErrorsIntoBundle(
742 gpa: Allocator,
743 tree: Ast,
744 path: []const u8,
745 wip_errors: *ErrorBundle.Wip,
746) Allocator.Error!void {
747 switch (tree.mode) {
748 .zig => {
749 var zir = try AstGen.generate(gpa, tree);
750 defer zir.deinit(gpa);
751
752 try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);
753 },
754 .zon => {
755 var zoir = try ZonGen.generate(gpa, tree, .{});
756 defer zoir.deinit(gpa);
757
758 try wip_errors.addZoirErrorMessages(zoir, tree, tree.source, path);
759 },
760 }
761}
762
763pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target {
764 return system.resolveTargetQuery(io, target_query) catch |err|
765 std.process.fatal("unable to resolve target: {t}", .{err});
766}
767
768pub fn parseTargetQueryOrReportFatalError(
769 allocator: Allocator,
770 opts: std.Target.Query.ParseOptions,
771) std.Target.Query {
772 var opts_with_diags = opts;
773 var diags: std.Target.Query.ParseOptions.Diagnostics = .{};
774 if (opts_with_diags.diagnostics == null) {
775 opts_with_diags.diagnostics = &diags;
776 }
777 return std.Target.Query.parse(opts_with_diags) catch |err| switch (err) {
778 error.UnknownCpuModel => {
779 help: {
780 var help_text = std.array_list.Managed(u8).init(allocator);
781 defer help_text.deinit();
782 for (diags.arch.?.allCpuModels()) |cpu| {
783 help_text.print(" {s}\n", .{cpu.name}) catch break :help;
784 }
785 log.info("available CPUs for architecture '{s}':\n{s}", .{
786 @tagName(diags.arch.?), help_text.items,
787 });
788 }
789 std.process.fatal("unknown CPU: '{s}'", .{diags.cpu_name.?});
790 },
791 error.UnknownCpuFeature => {
792 help: {
793 var help_text = std.array_list.Managed(u8).init(allocator);
794 defer help_text.deinit();
795 for (diags.arch.?.allFeaturesList()) |feature| {
796 help_text.print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help;
797 }
798 log.info("available CPU features for architecture '{s}':\n{s}", .{
799 @tagName(diags.arch.?), help_text.items,
800 });
801 }
802 std.process.fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?});
803 },
804 error.UnknownObjectFormat => {
805 help: {
806 var help_text = std.array_list.Managed(u8).init(allocator);
807 defer help_text.deinit();
808 inline for (@typeInfo(std.Target.ObjectFormat).@"enum".field_names) |field_name| {
809 help_text.print(" {s}\n", .{field_name}) catch break :help;
810 }
811 log.info("available object formats:\n{s}", .{help_text.items});
812 }
813 std.process.fatal("unknown object format: '{s}'", .{opts.object_format.?});
814 },
815 error.UnknownArchitecture => {
816 help: {
817 var help_text = std.array_list.Managed(u8).init(allocator);
818 defer help_text.deinit();
819 inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".field_names) |field_name| {
820 help_text.print(" {s}\n", .{field_name}) catch break :help;
821 }
822 log.info("available architectures:\n{s} native\n", .{help_text.items});
823 }
824 std.process.fatal("unknown architecture: '{s}'", .{diags.unknown_architecture_name.?});
825 },
826 else => |e| std.process.fatal("unable to parse target query '{s}': {s}", .{
827 opts.arch_os_abi, @errorName(e),
828 }),
829 };
830}
831
832/// Collects all the environment variables that Zig could possibly inspect, so
833/// that we can do reflection on this and print them with `zig env`.
834pub const EnvVar = enum {
835 ZIG_GLOBAL_CACHE_DIR,
836 ZIG_LOCAL_CACHE_DIR,
837 ZIG_LOCAL_PKG_DIR,
838 ZIG_LIB_DIR,
839 ZIG_LIBC,
840 ZIG_BUILD_ERROR_STYLE,
841 ZIG_BUILD_MULTILINE_ERRORS,
842 ZIG_BUILD_SUMMARY,
843 ZIG_VERBOSE_LINK,
844 ZIG_VERBOSE_CC,
845 ZIG_VERBOSE_CMD,
846 ZIG_DEBUG_CMD,
847 ZIG_IS_DETECTING_LIBC_PATHS,
848 ZIG_IS_AVOIDING_CALLING_ITSELF,
849
850 // C toolchain integration
851 NIX_CFLAGS_COMPILE,
852 NIX_CFLAGS_LINK,
853 NIX_LDFLAGS,
854 C_INCLUDE_PATH,
855 CPLUS_INCLUDE_PATH,
856 LIBRARY_PATH,
857 CC,
858 PKG_CONFIG,
859
860 // Terminal integration
861 NO_COLOR,
862 CLICOLOR_FORCE,
863
864 // Debug info integration
865 XDG_CACHE_HOME,
866 LOCALAPPDATA,
867 HOME,
868
869 // Windows SDK integration
870 PROGRAMDATA,
871
872 // Homebrew integration
873 HOMEBREW_PREFIX,
874
875 pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool {
876 return map.contains(@tagName(ev));
877 }
878
879 pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 {
880 return map.get(@tagName(ev));
881 }
882};
883
884pub const SimpleComptimeReason = enum(u32) {
885 // Evaluating at comptime because a builtin operand must be comptime-known.
886 // These messages all mention a specific builtin.
887 operand_setEvalBranchQuota,
888 operand_setFloatMode,
889 operand_branchHint,
890 operand_setRuntimeSafety,
891 operand_embedFile,
892 operand_shuffle_mask,
893 operand_atomicRmw_operation,
894 operand_reduce_operation,
895
896 // Evaluating at comptime because an operand must be comptime-known.
897 // These messages do not mention a specific builtin (and may not be about a builtin at all).
898 export_target,
899 export_options,
900 extern_options,
901 prefetch_options,
902 call_modifier,
903 compile_error_string,
904 inline_assembly_code,
905 atomic_order,
906 slice_cat_operand,
907 inline_call_target,
908 generic_call_target,
909 wasm_memory_index,
910 work_group_dim_index,
911 clobber,
912
913 // Evaluating at comptime because types must be comptime-known.
914 // Reasons other than `.type` are just more specific messages.
915 type,
916 int_signedness,
917 int_bit_width,
918 array_sentinel,
919 array_length,
920 pointer_size,
921 pointer_attrs,
922 pointer_sentinel,
923 slice_sentinel,
924 vector_length,
925 fn_ret_ty,
926 fn_param_types,
927 fn_param_attrs,
928 fn_attrs,
929 struct_layout,
930 struct_field_names,
931 struct_field_types,
932 struct_field_attrs,
933 union_layout,
934 union_field_names,
935 union_field_types,
936 union_field_attrs,
937 tuple_field_types,
938 enum_field_names,
939 enum_field_values,
940 union_enum_tag_type,
941 enum_int_tag_type,
942 packed_struct_backing_int_type,
943 packed_union_backing_int_type,
944
945 // Evaluating at comptime because decl/field name must be comptime-known.
946 decl_name,
947 field_name,
948 tuple_field_index,
949
950 // Evaluating at comptime because it is an attribute of a global declaration.
951 container_var_init,
952 @"callconv",
953 @"align",
954 @"addrspace",
955 @"linksection",
956
957 // Miscellaneous reasons.
958 comptime_keyword,
959 comptime_call_modifier,
960 inline_loop_operand,
961 switch_item,
962 tuple_field_default_value,
963 struct_field_default_value,
964 enum_field_tag_value,
965 slice_single_item_ptr_bounds,
966 stored_to_comptime_field,
967 stored_to_comptime_var,
968 casted_to_comptime_int,
969 casted_to_comptime_float,
970 std_lang_decl,
971
972 pub fn message(r: SimpleComptimeReason) []const u8 {
973 return switch (r) {
974 // zig fmt: off
975 .operand_setEvalBranchQuota => "operand to '@setEvalBranchQuota' must be comptime-known",
976 .operand_setFloatMode => "operand to '@setFloatMode' must be comptime-known",
977 .operand_branchHint => "operand to '@branchHint' must be comptime-known",
978 .operand_setRuntimeSafety => "operand to '@setRuntimeSafety' must be comptime-known",
979 .operand_embedFile => "operand to '@embedFile' must be comptime-known",
980 .operand_shuffle_mask => "'@shuffle' mask must be comptime-known",
981 .operand_atomicRmw_operation => "'@atomicRmw' operation must be comptime-known",
982 .operand_reduce_operation => "'@reduce' operation must be comptime-known",
983
984 .export_target => "export target must be comptime-known",
985 .export_options => "export options must be comptime-known",
986 .extern_options => "extern options must be comptime-known",
987 .prefetch_options => "prefetch options must be comptime-known",
988 .call_modifier => "call modifier must be comptime-known",
989 .compile_error_string => "compile error string must be comptime-known",
990 .inline_assembly_code => "inline assembly code must be comptime-known",
991 .atomic_order => "atomic order must be comptime-known",
992 .slice_cat_operand => "slice being concatenated must be comptime-known",
993 .inline_call_target => "function being called inline must be comptime-known",
994 .generic_call_target => "generic function being called must be comptime-known",
995 .wasm_memory_index => "wasm memory index must be comptime-known",
996 .work_group_dim_index => "work group dimension index must be comptime-known",
997 .clobber => "clobber must be comptime-known",
998
999 .type => "types must be comptime-known",
1000 .int_signedness => "integer signedness must be comptime-known",
1001 .int_bit_width => "integer bit width must be comptime-known",
1002 .array_sentinel => "array sentinel value must be comptime-known",
1003 .array_length => "array length must be comptime-known",
1004 .pointer_size => "pointer size must be comptime-known",
1005 .pointer_attrs => "pointer attributes must be comptime-known",
1006 .pointer_sentinel => "pointer sentinel value must be comptime-known",
1007 .slice_sentinel => "slice sentinel value must be comptime-known",
1008 .vector_length => "vector length must be comptime-known",
1009 .fn_ret_ty => "function return type must be comptime-known",
1010 .fn_param_types => "function parameter types must be comptime-known",
1011 .fn_param_attrs => "function parameter attributes must be comptime-known",
1012 .fn_attrs => "function attributes must be comptime-known",
1013 .struct_layout => "struct layout must be comptime-known",
1014 .struct_field_names => "struct field names must be comptime-known",
1015 .struct_field_types => "struct field types must be comptime-known",
1016 .struct_field_attrs => "struct field attributes must be comptime-known",
1017 .union_layout => "union layout must be comptime-known",
1018 .union_field_names => "union field names must be comptime-known",
1019 .union_field_types => "union field types must be comptime-known",
1020 .union_field_attrs => "union field attributes must be comptime-known",
1021 .tuple_field_types => "tuple field types must be comptime-known",
1022 .enum_field_names => "enum field names must be comptime-known",
1023 .enum_field_values => "enum field values must be comptime-known",
1024
1025 .union_enum_tag_type => "enum tag type of union must be comptime-known",
1026 .enum_int_tag_type => "integer tag type of enum must be comptime-known",
1027 .packed_struct_backing_int_type => "packed struct backing integer type must be comptime-known",
1028 .packed_union_backing_int_type => "packed struct backing integer type must be comptime-known",
1029
1030 .decl_name => "declaration name must be comptime-known",
1031 .field_name => "field name must be comptime-known",
1032 .tuple_field_index => "tuple field index must be comptime-known",
1033
1034 .container_var_init => "initializer of container-level variable must be comptime-known",
1035 .@"callconv" => "calling convention must be comptime-known",
1036 .@"align" => "alignment must be comptime-known",
1037 .@"addrspace" => "address space must be comptime-known",
1038 .@"linksection" => "linksection must be comptime-known",
1039
1040 .comptime_keyword => "'comptime' keyword forces comptime evaluation",
1041 .comptime_call_modifier => "'.compile_time' call modifier forces comptime evaluation",
1042 .inline_loop_operand => "inline loop condition must be comptime-known",
1043 .switch_item => "switch prong values must be comptime-known",
1044 .tuple_field_default_value => "tuple field default value must be comptime-known",
1045 .struct_field_default_value => "struct field default value must be comptime-known",
1046 .enum_field_tag_value => "enum field tag value must be comptime-known",
1047 .slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds",
1048 .stored_to_comptime_field => "value stored to a comptime field must be comptime-known",
1049 .stored_to_comptime_var => "value stored to a comptime variable must be comptime-known",
1050 .casted_to_comptime_int => "value casted to 'comptime_int' must be comptime-known",
1051 .casted_to_comptime_float => "value casted to 'comptime_float' must be comptime-known",
1052 .std_lang_decl => "'std.lang' declaration values must be comptime-known",
1053 // zig fmt: on
1054 };
1055 }
1056};
1057
1058/// Every kind of artifact which the compiler can emit.
1059pub const EmitArtifact = enum {
1060 bin,
1061 @"asm",
1062 implib,
1063 llvm_ir,
1064 llvm_bc,
1065 docs,
1066 pdb,
1067 h,
1068
1069 /// If using `Server` to communicate with the compiler, it will place requested artifacts in
1070 /// paths under the output directory, where those paths are named according to this function.
1071 /// Returned string is allocated with `gpa` and owned by the caller.
1072 pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
1073 const suffix: []const u8 = switch (ea) {
1074 .bin => return binNameAlloc(gpa, opts),
1075 .@"asm" => ".s",
1076 .implib => ".lib",
1077 .llvm_ir => ".ll",
1078 .llvm_bc => ".bc",
1079 .docs => "-docs",
1080 .pdb => ".pdb",
1081 .h => ".h",
1082 };
1083 return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
1084 }
1085};
1086
1087/// The defaults are chosen here to reduce the size of src/clang_options.zon
1088pub const ClangCliParam = struct {
1089 name: []const u8,
1090 ze: ZigEquivalent = .other,
1091 syntax: Syntax = .flag,
1092 /// Prefixed by "-"
1093 pd1: bool = true,
1094 /// Prefixed by "--"
1095 pd2: bool = false,
1096 /// Prefixed by "/"
1097 psl: bool = false,
1098
1099 pub const Syntax = union(enum) {
1100 /// A flag with no values.
1101 flag,
1102 /// An option which prefixes its (single) value.
1103 joined,
1104 /// An option which is followed by its value.
1105 separate,
1106 /// An option which is either joined to its (non-empty) value, or followed by its value.
1107 joined_or_separate,
1108 /// An option which is both joined to its (first) value, and followed by its (second) value.
1109 joined_and_separate,
1110 /// An option followed by its values, which are separated by commas.
1111 comma_joined,
1112 /// An option which consumes an optional joined argument and any other remaining arguments.
1113 remaining_args_joined,
1114 /// An option which is which takes multiple (separate) arguments.
1115 multi_arg: u8,
1116 };
1117
1118 pub const ZigEquivalent = enum {
1119 target,
1120 o,
1121 c,
1122 r,
1123 m,
1124 x,
1125 other,
1126 positional,
1127 l,
1128 ignore,
1129 driver_punt,
1130 pic,
1131 no_pic,
1132 pie,
1133 no_pie,
1134 lto,
1135 no_lto,
1136 unwind_tables,
1137 no_unwind_tables,
1138 asynchronous_unwind_tables,
1139 no_asynchronous_unwind_tables,
1140 nostdlib,
1141 nostdlib_cpp,
1142 shared,
1143 rdynamic,
1144 wl,
1145 wp,
1146 preprocess_only,
1147 asm_only,
1148 optimize,
1149 debug,
1150 gdwarf32,
1151 gdwarf64,
1152 sanitize,
1153 no_sanitize,
1154 sanitize_trap,
1155 no_sanitize_trap,
1156 linker_script,
1157 dry_run,
1158 verbose,
1159 for_linker,
1160 linker_input_z,
1161 lib_dir,
1162 mcpu,
1163 dep_file,
1164 dep_file_to_stdout,
1165 framework_dir,
1166 framework,
1167 nostdlibinc,
1168 red_zone,
1169 no_red_zone,
1170 omit_frame_pointer,
1171 no_omit_frame_pointer,
1172 function_sections,
1173 no_function_sections,
1174 data_sections,
1175 no_data_sections,
1176 builtin,
1177 no_builtin,
1178 color_diagnostics,
1179 no_color_diagnostics,
1180 stack_check,
1181 no_stack_check,
1182 stack_protector,
1183 no_stack_protector,
1184 strip,
1185 exec_model,
1186 emit_llvm,
1187 sysroot,
1188 entry,
1189 force_undefined_symbol,
1190 weak_library,
1191 weak_framework,
1192 headerpad_max_install_names,
1193 compress_debug_sections,
1194 install_name,
1195 undefined,
1196 force_load_objc,
1197 mingw_unicode_entry_point,
1198 san_cov_trace_pc_guard,
1199 san_cov,
1200 no_san_cov,
1201 rtlib,
1202 static,
1203 dynamic,
1204 version,
1205 };
1206
1207 pub fn matchEql(self: @This(), arg: []const u8) u2 {
1208 if (self.pd1 and arg.len >= self.name.len + 1 and
1209 mem.startsWith(u8, arg, "-") and mem.eql(u8, arg[1..], self.name))
1210 {
1211 return 1;
1212 }
1213 if (self.pd2 and arg.len >= self.name.len + 2 and
1214 mem.startsWith(u8, arg, "--") and mem.eql(u8, arg[2..], self.name))
1215 {
1216 return 2;
1217 }
1218 if (self.psl and arg.len >= self.name.len + 1 and
1219 mem.startsWith(u8, arg, "/") and mem.eql(u8, arg[1..], self.name))
1220 {
1221 return 1;
1222 }
1223 return 0;
1224 }
1225
1226 pub fn matchStartsWith(self: @This(), arg: []const u8) usize {
1227 if (self.pd1 and arg.len >= self.name.len + 1 and
1228 mem.startsWith(u8, arg, "-") and mem.startsWith(u8, arg[1..], self.name))
1229 {
1230 return self.name.len + 1;
1231 }
1232 if (self.pd2 and arg.len >= self.name.len + 2 and
1233 mem.startsWith(u8, arg, "--") and mem.startsWith(u8, arg[2..], self.name))
1234 {
1235 return self.name.len + 2;
1236 }
1237 if (self.psl and arg.len >= self.name.len + 1 and
1238 mem.startsWith(u8, arg, "/") and mem.startsWith(u8, arg[1..], self.name))
1239 {
1240 return self.name.len + 1;
1241 }
1242 return 0;
1243 }
1244};
1245
1246/// Deprecated
1247pub const AllocPrintCmdOptions = struct {
1248 cwd: ?[]const u8 = null,
1249 parent_env: ?*const std.process.Environ.Map = null,
1250 child_env: ?*const std.process.Environ.Map = null,
1251};
1252
1253/// Deprecated
1254pub fn allocPrintCmd(gpa: Allocator, argv: []const []const u8, options: AllocPrintCmdOptions) Allocator.Error![]u8 {
1255 var aw: Io.Writer.Allocating = .init(gpa);
1256 defer aw.deinit();
1257 SubprocessCommand.format(.{
1258 .argv = argv,
1259 .cwd = options.cwd,
1260 .parent_env = options.parent_env,
1261 .child_env = options.child_env,
1262 }, &aw.writer) catch return error.OutOfMemory;
1263 return aw.toOwnedSlice();
1264}
1265
1266fn shellEscape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void {
1267 for (string) |c| {
1268 if (switch (c) {
1269 else => true,
1270 '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false,
1271 '=' => is_argv0,
1272 }) break;
1273 } else return writer.writeAll(string);
1274
1275 try writer.writeByte('"');
1276 for (string) |c| {
1277 if (switch (c) {
1278 std.ascii.control_code.nul => break,
1279 '!', '"', '$', '\\', '`' => true,
1280 else => !std.ascii.isPrint(c),
1281 }) try writer.writeByte('\\');
1282 switch (c) {
1283 std.ascii.control_code.nul => unreachable,
1284 std.ascii.control_code.bel => try writer.writeByte('a'),
1285 std.ascii.control_code.bs => try writer.writeByte('b'),
1286 std.ascii.control_code.ht => try writer.writeByte('t'),
1287 std.ascii.control_code.lf => try writer.writeByte('n'),
1288 std.ascii.control_code.vt => try writer.writeByte('v'),
1289 std.ascii.control_code.ff => try writer.writeByte('f'),
1290 std.ascii.control_code.cr => try writer.writeByte('r'),
1291 std.ascii.control_code.esc => try writer.writeByte('E'),
1292 ' '...'~' => try writer.writeByte(c),
1293 else => try writer.print("{o:0>3}", .{c}),
1294 }
1295 }
1296 try writer.writeByte('"');
1297}
1298
1299pub const SubprocessCommand = struct {
1300 argv: []const []const u8,
1301 cwd: ?[]const u8 = null,
1302 parent_env: ?*const std.process.Environ.Map = null,
1303 child_env: ?*const std.process.Environ.Map = null,
1304
1305 pub fn format(sc: SubprocessCommand, w: *Io.Writer) Io.Writer.Error!void {
1306 if (sc.cwd) |path| {
1307 try w.print("cd {s} && ", .{path});
1308 }
1309 if (sc.child_env) |child_env| {
1310 for (child_env.keys(), child_env.values()) |key, value| {
1311 if (sc.parent_env) |parent_env| {
1312 if (parent_env.get(key)) |process_value| {
1313 if (mem.eql(u8, value, process_value)) continue;
1314 }
1315 }
1316 try w.print("{s}=", .{key});
1317 try shellEscape(w, value, false);
1318 try w.writeByte(' ');
1319 }
1320 }
1321 try shellEscape(w, sc.argv[0], true);
1322 for (sc.argv[1..]) |arg| {
1323 try w.writeByte(' ');
1324 try shellEscape(w, arg, false);
1325 }
1326 }
1327};
1328
1329/// Like `std.process.currentPathAlloc`, but also resolves the path with `Dir.path.resolve`. This
1330/// means the path has no repeated separators, no "." or ".." components, and no trailing separator.
1331/// On WASI, "" is returned instead of ".".
1332pub fn getResolvedCwd(io: Io, gpa: Allocator) std.process.CurrentPathAllocError![]u8 {
1333 if (builtin.os.tag == .wasi) {
1334 if (std.debug.runtime_safety) {
1335 const cwd = try std.process.currentPathAlloc(io, gpa);
1336 defer gpa.free(cwd);
1337 assert(mem.eql(u8, cwd, "."));
1338 }
1339 return "";
1340 }
1341 const cwd = try std.process.currentPathAlloc(io, gpa);
1342 defer gpa.free(cwd);
1343 const resolved = try Dir.path.resolve(gpa, &.{cwd});
1344 assert(Dir.path.isAbsolute(resolved));
1345 return resolved;
1346}
1347
1348pub const Directories = struct {
1349 /// The string returned by `introspect.getResolvedCwd`. This is typically an absolute path,
1350 /// but on WASI is the empty string "" instead, because WASI does not have absolute paths.
1351 cwd: []const u8,
1352 /// The Zig 'lib' directory.
1353 /// `zig_lib.path` is resolved (`resolvePath`) or `null` for cwd.
1354 /// Guaranteed to be a different path from `global_cache` and `local_cache`.
1355 zig_lib: Cache.Directory,
1356 /// The global Zig cache directory.
1357 /// `global_cache.path` is resolved (`resolvePath`) or `null` for cwd.
1358 global_cache: Cache.Directory,
1359 /// The local Zig cache directory.
1360 /// `local_cache.path` is resolved (`resolvePath`) or `null` for cwd.
1361 /// This may be the same as `global_cache`.
1362 local_cache: Cache.Directory,
1363 /// The directory that contains build.zig. This path is provided by the
1364 /// build system, when the build system is used, otherwise, it is `null`
1365 /// for cwd.
1366 build_root: Cache.Directory,
1367
1368 pub fn deinit(dirs: *Directories, io: Io) void {
1369 // The local and global caches could be the same.
1370 const close_local = dirs.local_cache.handle.handle != dirs.global_cache.handle.handle;
1371 const close_build_root = dirs.build_root.handle.handle != Io.Dir.cwd().handle;
1372
1373 dirs.global_cache.handle.close(io);
1374 if (close_local) dirs.local_cache.handle.close(io);
1375 dirs.zig_lib.handle.close(io);
1376 if (close_build_root) dirs.build_root.handle.close(io);
1377 }
1378
1379 /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for
1380 /// use by sub-compilations (e.g. compiler_rt). Do not `deinit` the returned `Directories`; it
1381 /// shares handles with `dirs`.
1382 pub fn withoutLocalCache(dirs: Directories) Directories {
1383 return .{
1384 .cwd = dirs.cwd,
1385 .zig_lib = dirs.zig_lib,
1386 .global_cache = dirs.global_cache,
1387 .local_cache = dirs.global_cache,
1388 .build_root = dirs.build_root,
1389 };
1390 }
1391
1392 const LocalCacheStrategy = union(enum) {
1393 override: []const u8,
1394 search,
1395 global,
1396 };
1397
1398 pub const InitOptions = struct {
1399 override_zig_lib: ?[]const u8,
1400 override_global_cache: ?[]const u8,
1401 build_root: ?[]const u8,
1402 local_cache_strat: LocalCacheStrategy,
1403 preopens: std.process.Preopens,
1404 self_exe_path: switch (builtin.target.os.tag) {
1405 .wasi => void,
1406 else => []const u8,
1407 },
1408 environ_map: *const std.process.Environ.Map,
1409 cwd: []const u8,
1410 };
1411
1412 /// Uses `std.process.fatal` on error conditions.
1413 pub fn init(arena: Allocator, io: Io, options: InitOptions) Directories {
1414 const wasi = builtin.target.os.tag == .wasi;
1415 const cwd = options.cwd;
1416
1417 const zig_lib: Cache.Directory = d: {
1418 if (options.override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib");
1419 if (wasi) break :d getPreopen(options.preopens, "/lib");
1420 break :d findZigLibDirFromSelfExe(arena, io, cwd, options.self_exe_path) catch |err| {
1421 fatal("unable to find zig installation directory from executable path {q}: {t}", .{
1422 options.self_exe_path, err,
1423 });
1424 };
1425 };
1426 const build_root: Cache.Directory = if (options.build_root) |s|
1427 openUnresolved(arena, io, cwd, s, .@"build root")
1428 else
1429 .cwd();
1430
1431 const global_cache: Cache.Directory = d: {
1432 if (options.override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache");
1433 if (wasi) break :d getPreopen(options.preopens, "/cache");
1434 const path = resolveGlobalCacheDir(arena, options.environ_map) catch |err| {
1435 fatal("unable to resolve zig cache directory: {t}", .{err});
1436 };
1437 break :d openUnresolved(arena, io, cwd, path, .@"global cache");
1438 };
1439
1440 const local_cache = getLocalCacheDirectory(arena, io, cwd, global_cache, options.local_cache_strat);
1441
1442 if (mem.eql(u8, zig_lib.path orelse "", global_cache.path orelse "")) {
1443 fatal("zig lib directory '{f}' cannot be equal to global cache directory '{f}'", .{ zig_lib, global_cache });
1444 }
1445 if (mem.eql(u8, zig_lib.path orelse "", local_cache.path orelse "")) {
1446 fatal("zig lib directory '{f}' cannot be equal to local cache directory '{f}'", .{ zig_lib, local_cache });
1447 }
1448
1449 return .{
1450 .cwd = cwd,
1451 .zig_lib = zig_lib,
1452 .global_cache = global_cache,
1453 .local_cache = local_cache,
1454 .build_root = build_root,
1455 };
1456 }
1457
1458 fn getLocalCacheDirectory(
1459 arena: Allocator,
1460 io: Io,
1461 cwd: []const u8,
1462 global_cache: Cache.Directory,
1463 local_cache_strat: LocalCacheStrategy,
1464 ) Cache.Directory {
1465 return switch (local_cache_strat) {
1466 .override => |path| openUnresolved(arena, io, cwd, path, .@"local cache"),
1467 .search => d: {
1468 const maybe_path = resolveSuitableLocalCacheDir(arena, io, cwd) catch |err|
1469 fatal("unable to resolve zig cache directory: {t}", .{err});
1470 const path = maybe_path orelse break :d global_cache;
1471 break :d openUnresolved(arena, io, cwd, path, .@"local cache");
1472 },
1473 .global => global_cache,
1474 };
1475 }
1476
1477 fn getPreopen(preopens: std.process.Preopens, name: []const u8) Cache.Directory {
1478 return .{
1479 .path = if (mem.eql(u8, name, ".")) null else name,
1480 .handle = switch (preopens.get(name) orelse fatal("preopen not found: {q}", .{name})) {
1481 .file => fatal("preopen {q} is not a directory", .{name}),
1482 .dir => |d| d,
1483 },
1484 };
1485 }
1486 pub fn openUnresolved(
1487 arena: Allocator,
1488 io: Io,
1489 cwd: []const u8,
1490 unresolved_path: []const u8,
1491 thing: enum { @"zig lib", @"global cache", @"local cache", @"build root" },
1492 ) Cache.Directory {
1493 const path = resolvePath(arena, cwd, &.{unresolved_path}) catch |err| {
1494 fatal("unable to resolve {t} directory: {t}", .{ thing, err });
1495 };
1496 const nonempty_path = if (path.len == 0) "." else path;
1497 const handle_or_err = switch (thing) {
1498 .@"zig lib", .@"build root" => Dir.cwd().openDir(io, nonempty_path, .{}),
1499 .@"global cache", .@"local cache" => Dir.cwd().createDirPathOpen(io, nonempty_path, .{}),
1500 };
1501 return .{
1502 .path = if (path.len == 0) null else path,
1503 .handle = handle_or_err catch |err| {
1504 const extra_str: []const u8 = e: {
1505 if (thing == .@"global cache") switch (err) {
1506 error.AccessDenied, error.ReadOnlyFileSystem => break :e "\n" ++
1507 "If this location is not writable then consider specifying an alternative with " ++
1508 "the ZIG_GLOBAL_CACHE_DIR environment variable or the --global-cache-dir option.",
1509 else => {},
1510 };
1511 break :e "";
1512 };
1513 fatal("unable to open {t} directory {q}: {t}{s}", .{ thing, nonempty_path, err, extra_str });
1514 },
1515 };
1516 }
1517};
1518
1519/// Both the directory handle and the path are newly allocated resources which the caller now owns.
1520pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory {
1521 const cwd_path = try getResolvedCwd(io, gpa);
1522 defer gpa.free(cwd_path);
1523 const self_exe_path = try std.process.executablePathAlloc(io, gpa);
1524 defer gpa.free(self_exe_path);
1525
1526 return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path);
1527}
1528
1529/// Both the directory handle and the path are newly allocated resources which the caller now owns.
1530pub fn findZigLibDirFromSelfExe(
1531 allocator: Allocator,
1532 io: Io,
1533 /// The return value of `getResolvedCwd`.
1534 /// Passed as an argument to avoid pointlessly repeating the call.
1535 cwd_path: []const u8,
1536 self_exe_path: []const u8,
1537) error{ OutOfMemory, FileNotFound }!Cache.Directory {
1538 const cwd = Dir.cwd();
1539 var cur_path: []const u8 = self_exe_path;
1540 while (Dir.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {
1541 var base_dir = cwd.openDir(io, dirname, .{}) catch continue;
1542 defer base_dir.close(io);
1543
1544 const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue;
1545 const p = try Dir.path.join(allocator, &.{ dirname, sub_directory.path.? });
1546 defer allocator.free(p);
1547
1548 const resolved = try resolvePath(allocator, cwd_path, &.{p});
1549 return .{
1550 .handle = sub_directory.handle,
1551 .path = if (resolved.len == 0) null else resolved,
1552 };
1553 }
1554 return error.FileNotFound;
1555}
1556
1557/// Returns the sub_path that worked, or `null` if none did.
1558/// The path of the returned Directory is relative to `base`.
1559/// The handle of the returned Directory is open.
1560fn testZigInstallPrefix(io: Io, base_dir: Dir) ?Cache.Directory {
1561 const test_index_file = "std" ++ Dir.path.sep_str ++ "std.zig";
1562
1563 zig_dir: {
1564 // Try lib/zig/std/std.zig
1565 const lib_zig = "lib" ++ Dir.path.sep_str ++ "zig";
1566 var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir;
1567 const file = test_zig_dir.openFile(io, test_index_file, .{}) catch {
1568 test_zig_dir.close(io);
1569 break :zig_dir;
1570 };
1571 file.close(io);
1572 return .{ .handle = test_zig_dir, .path = lib_zig };
1573 }
1574
1575 // Try lib/std/std.zig
1576 var test_zig_dir = base_dir.openDir(io, "lib", .{}) catch return null;
1577 const file = test_zig_dir.openFile(io, test_index_file, .{}) catch {
1578 test_zig_dir.close(io);
1579 return null;
1580 };
1581 file.close(io);
1582 return .{ .handle = test_zig_dir, .path = "lib" };
1583}
1584
1585pub fn resolveGlobalCacheDir(arena: Allocator, environ_map: *const std.process.Environ.Map) ![]const u8 {
1586 if (EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map)) |value| return value;
1587
1588 const app_name = "zig";
1589
1590 switch (builtin.os.tag) {
1591 .wasi => @compileError("on WASI the global cache dir must be resolved with preopens"),
1592 .windows => {
1593 const local_app_data_dir = EnvVar.LOCALAPPDATA.get(environ_map) orelse
1594 return error.AppDataDirUnavailable;
1595 return Dir.path.join(arena, &.{ local_app_data_dir, app_name });
1596 },
1597 else => {
1598 if (EnvVar.XDG_CACHE_HOME.get(environ_map)) |cache_root| {
1599 if (cache_root.len > 0) {
1600 return Dir.path.join(arena, &.{ cache_root, app_name });
1601 }
1602 }
1603 if (EnvVar.HOME.get(environ_map)) |home| {
1604 if (home.len > 0) {
1605 return Dir.path.join(arena, &.{ home, ".cache", app_name });
1606 }
1607 }
1608 return error.AppDataDirUnavailable;
1609 },
1610 }
1611}
1612
1613/// Searches upwards from `cwd` for a directory containing a `build.zig` file.
1614/// If such a directory is found, returns the path to it joined to the `.zig_cache` name.
1615/// Otherwise, returns `null`, indicating no suitable local cache location.
1616pub fn resolveSuitableLocalCacheDir(arena: Allocator, io: Io, cwd: []const u8) Allocator.Error!?[]u8 {
1617 var cur_dir = cwd;
1618 while (true) {
1619 const joined = try Dir.path.join(arena, &.{ cur_dir, build_zig_basename });
1620 if (Dir.cwd().access(io, joined, .{})) |_| {
1621 return try Dir.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename });
1622 } else |err| switch (err) {
1623 error.FileNotFound => {
1624 cur_dir = Dir.path.dirname(cur_dir) orelse return null;
1625 continue;
1626 },
1627 else => return null,
1628 }
1629 }
1630}
1631
1632/// Similar to `Dir.path.resolve`, but converts to a cwd-relative path, or, if that would
1633/// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd
1634/// returns the empty string ("") instead of ".".
1635pub fn resolvePath(
1636 gpa: Allocator,
1637 /// The return value of `getResolvedCwd`.
1638 /// Passed as an argument to avoid pointlessly repeating the call.
1639 cwd_resolved: []const u8,
1640 paths: []const []const u8,
1641) Allocator.Error![]u8 {
1642 if (builtin.target.os.tag == .wasi) {
1643 assert(mem.eql(u8, cwd_resolved, ""));
1644 const res = try Dir.path.resolve(gpa, paths);
1645 if (mem.eql(u8, res, ".")) {
1646 gpa.free(res);
1647 return "";
1648 }
1649 return res;
1650 }
1651
1652 // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`.
1653 for (paths) |p| {
1654 if (Dir.path.isAbsolute(p)) break; // absolute path
1655 if (mem.find(u8, p, "..") != null) break; // may contain up-dir
1656 } else {
1657 // no absolute path, no "..".
1658 const res = try Dir.path.resolve(gpa, paths);
1659 if (mem.eql(u8, res, ".")) {
1660 gpa.free(res);
1661 return "";
1662 }
1663 assert(!Dir.path.isAbsolute(res));
1664 assert(!isUpDir(res));
1665 return res;
1666 }
1667
1668 // The fast path failed; resolve the whole thing.
1669 // Optimization: `paths` often has just one element.
1670 const path_resolved = switch (paths.len) {
1671 0 => unreachable,
1672 1 => try Dir.path.resolve(gpa, &.{ cwd_resolved, paths[0] }),
1673 else => r: {
1674 const all_paths = try gpa.alloc([]const u8, paths.len + 1);
1675 defer gpa.free(all_paths);
1676 all_paths[0] = cwd_resolved;
1677 @memcpy(all_paths[1..], paths);
1678 break :r try Dir.path.resolve(gpa, all_paths);
1679 },
1680 };
1681 errdefer gpa.free(path_resolved);
1682
1683 assert(Dir.path.isAbsolute(path_resolved));
1684 assert(Dir.path.isAbsolute(cwd_resolved));
1685
1686 if (!mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd
1687 if (path_resolved.len == cwd_resolved.len) {
1688 // equal to cwd
1689 gpa.free(path_resolved);
1690 return "";
1691 }
1692 if (path_resolved[cwd_resolved.len] != Dir.path.sep) return path_resolved; // not in cwd (last component differs)
1693
1694 // in cwd; extract sub path
1695 const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]);
1696 gpa.free(path_resolved);
1697 return sub_path;
1698}
1699
1700pub fn isUpDir(p: []const u8) bool {
1701 return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == Dir.path.sep);
1702}
1703
1704pub const BuildExeSubprocessOptions = struct {
1705 argv: []const []const u8,
1706 cache_root: Cache.Directory,
1707 root_name: []const u8,
1708
1709 environ_map: ?*std.process.Environ.Map = null,
1710 cache_manifest: ?*Cache.Manifest = null,
1711 arch_os_abi: ?[]const u8 = null,
1712 cpu_features: ?[]const u8 = null,
1713 progress_node: std.Progress.Node = .none,
1714 skip_log_cmdline_on_compile_errors: bool = false,
1715};
1716
1717pub const BuildExeSubprocessError = error{
1718 /// Error message has been logged.
1719 AlreadyReported,
1720 /// Error message has been logged, and source files added to the `Cache.Manifest`.
1721 FailedButCacheIntact,
1722} || Io.Cancelable || Allocator.Error;
1723
1724pub const BuildExeSubprocessResult = struct {
1725 received_fs_inputs: bool,
1726 cache_hit: bool,
1727 path: Cache.Path,
1728};
1729
1730/// Assumes `argv` has `--listen=-` in it and the child process is `zig build-exe`.
1731///
1732/// Result path is allocated via gpa.
1733pub fn buildExeSubprocess(
1734 gpa: Allocator,
1735 io: Io,
1736 options: BuildExeSubprocessOptions,
1737) BuildExeSubprocessError!BuildExeSubprocessResult {
1738 const cmd: SubprocessCommand = .{ .argv = options.argv };
1739
1740 var child = std.process.spawn(io, .{
1741 .argv = options.argv,
1742 .environ_map = options.environ_map,
1743 .stdin = .pipe,
1744 .stdout = .pipe,
1745 .stderr = .pipe,
1746 .progress_node = options.progress_node,
1747 }) catch |err| {
1748 log.err("spawning command {t}: {f}", .{ err, cmd });
1749 return error.AlreadyReported;
1750 };
1751 defer child.kill(io);
1752
1753 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
1754 var multi_reader: Io.File.MultiReader = undefined;
1755 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
1756 defer multi_reader.deinit();
1757 const stdout = multi_reader.reader(0);
1758 const stderr = multi_reader.reader(1);
1759
1760 var stdin_buffer: [8]u8 = undefined;
1761 var stdin_writer = child.stdin.?.writerStreaming(io, &stdin_buffer);
1762
1763 var client: Client = .{
1764 .in = stdout,
1765 .out = &stdin_writer.interface,
1766 };
1767
1768 (blk: {
1769 client.serveMessageHeader(.{ .tag = .update, .bytes_len = 0 }) catch |err| break :blk err;
1770 client.serveMessageHeader(.{ .tag = .exit, .bytes_len = 0 }) catch |err| break :blk err;
1771 client.out.flush() catch |err| break :blk err;
1772 }) catch |err| switch (err) {
1773 error.WriteFailed => {
1774 if (stdin_writer.err.? == error.Canceled) return error.Canceled;
1775 log.err("{t} writing to command: {f}", .{ stdin_writer.err.?, cmd });
1776 return error.AlreadyReported;
1777 },
1778 };
1779
1780 var result: ?Cache.Path = null;
1781 defer if (result) |r| gpa.free(r.sub_path);
1782
1783 var result_error_bundle: ErrorBundle = .empty;
1784 defer result_error_bundle.deinit(gpa);
1785
1786 var received_fs_inputs = false;
1787 var cache_hit = false;
1788
1789 var eos_err: error{EndOfStream}!void = {};
1790
1791 while (true) {
1792 const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
1793 error.Timeout => unreachable,
1794 error.EndOfStream => |e| {
1795 if (client.in.bufferedLen() == 0) break;
1796 // Better to report the crash with stderr below, but we set
1797 // this in case the child exits successfully while violating
1798 // this protocol.
1799 eos_err = e;
1800 break;
1801 },
1802 error.Canceled, error.OutOfMemory => |e| return e,
1803 else => |e| {
1804 log.err("{t} reading from command: {f}", .{ e, cmd });
1805 return error.AlreadyReported;
1806 },
1807 };
1808 const body = stdout.take(header.bytes_len) catch unreachable;
1809
1810 switch (header.tag) {
1811 .zig_version => {
1812 if (!mem.eql(u8, builtin.zig_version_string, body)) {
1813 log.err("zig protocol version mismatch from command: {f}", .{cmd});
1814 return error.AlreadyReported;
1815 }
1816 },
1817 .error_bundle => {
1818 result_error_bundle.deinit(gpa);
1819 result_error_bundle = Server.allocErrorBundle(gpa, body) catch |err| switch (err) {
1820 error.EndOfStream => break,
1821 else => |e| return e,
1822 };
1823 },
1824 .emit_digest => {
1825 const EmitDigest = Server.Message.EmitDigest;
1826 const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body);
1827 cache_hit = ebp_hdr.flags.cache_hit;
1828 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
1829 if (result) |r| gpa.free(r.sub_path);
1830 result = .{
1831 .root_dir = options.cache_root,
1832 .sub_path = try Dir.path.join(gpa, &.{ "o", &Cache.binToHex(digest.*) }),
1833 };
1834 },
1835 .file_system_inputs => if (options.cache_manifest) |man| {
1836 received_fs_inputs = true;
1837 var it = mem.splitScalar(u8, body, 0);
1838 while (it.next()) |prefixed_path| {
1839 const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1));
1840 const sub_path = try gpa.dupe(u8, prefixed_path[1..]);
1841 var keep = false;
1842 defer if (!keep) gpa.free(sub_path);
1843 keep = man.addPrefixedPathPost(.{
1844 .prefix = @backingInt(prefix),
1845 .sub_path = sub_path,
1846 }) catch |err| switch (err) {
1847 error.Canceled, error.OutOfMemory => |e| return e,
1848 else => |e| {
1849 log.err("adding {t} {s} to cache failed: {t}", .{ prefix, sub_path, e });
1850 return error.AlreadyReported;
1851 },
1852 };
1853 }
1854 },
1855 else => {}, // ignore other messages
1856 }
1857 }
1858
1859 const stderr_contents = stderr.buffered();
1860 if (stderr_contents.len > 0)
1861 log.warn("unexpected stderr from {s} command:\n{s}", .{ options.argv[0], stderr_contents });
1862
1863 eos_err catch {
1864 log.err("unexpected end of stream from command: {f}", .{cmd});
1865 return error.AlreadyReported;
1866 };
1867
1868 // Send EOF to stdin.
1869 child.stdin.?.close(io);
1870 child.stdin = null;
1871
1872 const term = child.wait(io) catch |err| switch (err) {
1873 error.Canceled => |e| return e,
1874 else => |e| {
1875 log.err("{t} waiting for command: {f}", .{ e, cmd });
1876 return error.AlreadyReported;
1877 },
1878 };
1879
1880 if (!term.success()) {
1881 log.err("command {f}: {f}", .{ term, cmd });
1882 if (received_fs_inputs) return error.FailedButCacheIntact;
1883 return error.AlreadyReported;
1884 }
1885
1886 if (result_error_bundle.errorMessageCount() > 0) {
1887 result_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) {
1888 error.Canceled => |e| return e,
1889 else => |e| {
1890 log.err("failed rendering error bundle: {t}", .{e});
1891 return error.AlreadyReported;
1892 },
1893 };
1894 if (!options.skip_log_cmdline_on_compile_errors) log.err("command reported {d} compilation errors: {f}", .{
1895 result_error_bundle.errorMessageCount(), cmd,
1896 });
1897 if (received_fs_inputs) return error.FailedButCacheIntact;
1898 return error.AlreadyReported;
1899 }
1900
1901 const base_path = result orelse {
1902 log.err("command failed to report result: {f}", .{cmd});
1903 return error.AlreadyReported;
1904 };
1905 const parsed_target = system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
1906 .arch_os_abi = options.arch_os_abi orelse "native",
1907 .cpu_features = options.cpu_features,
1908 }) catch unreachable) catch unreachable;
1909 const bin_name = try binNameAlloc(gpa, .{
1910 .root_name = options.root_name,
1911 .cpu_arch = parsed_target.cpu.arch,
1912 .os_tag = parsed_target.os.tag,
1913 .ofmt = parsed_target.ofmt,
1914 .abi = parsed_target.abi,
1915 .output_mode = .Exe,
1916 });
1917 defer gpa.free(bin_name);
1918 return .{
1919 .received_fs_inputs = received_fs_inputs,
1920 .cache_hit = cache_hit,
1921 .path = try base_path.join(gpa, bin_name),
1922 };
1923}
1924
1925test {
1926 _ = Ast;
1927 _ = AstRlAnnotate;
1928 _ = BuiltinFn;
1929 _ = Client;
1930 _ = ErrorBundle;
1931 _ = LibCDirs;
1932 _ = LibCInstallation;
1933 _ = Server;
1934 _ = TokenSmith;
1935 _ = WindowsSdk;
1936 _ = number_literal;
1937 _ = primitives;
1938 _ = string_literal;
1939 _ = system;
1940 _ = target;
1941 _ = c_translation;
1942 _ = llvm;
1943 _ = @import("zig/parser_fuzz.zig");
1944}