//! Builds of the Zig compiler are distributed partly in source form. That //! source lives here. These APIs are provided as-is and have absolutely no API //! guarantees whatsoever. const builtin = @import("builtin"); const std = @import("std.zig"); const assert = std.debug.assert; const mem = std.mem; const log = std.log; const Allocator = std.mem.Allocator; const Io = std.Io; const Writer = std.Io.Writer; const Cache = std.Build.Cache; const fatal = std.process.fatal; const Dir = std.Io.Dir; const tokenizer = @import("zig/tokenizer.zig"); pub const ErrorBundle = @import("zig/ErrorBundle.zig"); pub const Server = @import("zig/Server.zig"); pub const Client = @import("zig/Client.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const TokenSmith = @import("zig/TokenSmith.zig"); pub const string_literal = @import("zig/string_literal.zig"); pub const number_literal = @import("zig/number_literal.zig"); pub const primitives = @import("zig/primitives.zig"); pub const isPrimitive = primitives.isPrimitive; pub const Ast = @import("zig/Ast.zig"); pub const AstGen = @import("zig/AstGen.zig"); pub const Zir = @import("zig/Zir.zig"); pub const Zoir = @import("zig/Zoir.zig"); pub const ZonGen = @import("zig/ZonGen.zig"); pub const system = @import("zig/system.zig"); pub const BuiltinFn = @import("zig/BuiltinFn.zig"); pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig"); pub const LibCInstallation = @import("zig/LibCInstallation.zig"); pub const WindowsSdk = @import("zig/WindowsSdk.zig"); pub const LibCDirs = @import("zig/LibCDirs.zig"); pub const PkgConfig = @import("zig/PkgConfig.zig"); pub const target = @import("zig/target.zig"); pub const llvm = @import("zig/llvm.zig"); pub const parser_generated_oracle = @import("zig/parser_generated_oracle.zig"); // Character literal parsing pub const ParsedCharLiteral = string_literal.ParsedCharLiteral; pub const parseCharLiteral = string_literal.parseCharLiteral; pub const parseNumberLiteral = number_literal.parseNumberLiteral; pub const c_translation = struct { pub const builtins = @import("zig/c_translation/builtins.zig"); pub const helpers = @import("zig/c_translation/helpers.zig"); }; pub const default_local_zig_cache_basename = ".zig-cache"; pub const build_zig_basename = "build.zig"; pub const SrcHasher = std.crypto.hash.Blake3; pub const SrcHash = [16]u8; pub const Color = enum { /// Auto-detect whether stream supports terminal colors. auto, /// Force-enable colors. off, /// Suppress colors. on, pub fn terminalMode(color: Color) ?Io.Terminal.Mode { return switch (color) { .auto => null, .on => .escape_codes, .off => .no_color, }; } /// Determine the preference for color or no color based on the NO_COLOR and /// CLICOLOR_FORCE environment variables. Color is always disabled on WASI per /// https://github.com/WebAssembly/WASI/issues/162 pub fn settingFromEnvironment(environ_map: *const std.process.Environ.Map) Color { return if (builtin.os.tag == .wasi or EnvVar.NO_COLOR.isSet(environ_map)) .off else if (EnvVar.CLICOLOR_FORCE.isSet(environ_map)) .on else .auto; } }; /// There are many assumptions in the entire codebase that Zig source files can /// be byte-indexed with a u32 integer. pub const max_src_size = std.math.maxInt(u32); pub fn hashSrc(src: []const u8) SrcHash { var out: SrcHash = undefined; SrcHasher.hash(src, &out, .{}); return out; } pub fn srcHashEql(a: SrcHash, b: SrcHash) bool { return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b)); } pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash { var out: SrcHash = undefined; var hasher = SrcHasher.init(.{}); hasher.update(&parent_hash); hasher.update(sep); hasher.update(name); hasher.final(&out); return out; } pub const Loc = struct { line: usize, column: usize, /// Does not include the trailing newline. source_line: []const u8, pub fn eql(a: Loc, b: Loc) bool { return a.line == b.line and a.column == b.column and mem.eql(u8, a.source_line, b.source_line); } }; pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc { var line: usize = 0; var column: usize = 0; var line_start: usize = 0; var i: usize = 0; while (i < byte_offset) : (i += 1) { switch (source[i]) { '\n' => { line += 1; column = 0; line_start = i + 1; }, else => { column += 1; }, } } while (i < source.len and source[i] != '\n') { i += 1; } return .{ .line = line, .column = column, .source_line = source[line_start..i], }; } pub fn lineDelta(source: []const u8, start: usize, end: usize) isize { var line: isize = 0; if (end >= start) { for (source[start..end]) |byte| switch (byte) { '\n' => line += 1, else => continue, }; } else { for (source[end..start]) |byte| switch (byte) { '\n' => line -= 1, else => continue, }; } return line; } pub const BinNameOptions = struct { root_name: []const u8, cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag, ofmt: std.Target.ObjectFormat, abi: std.Target.Abi, output_mode: std.lang.OutputMode, link_mode: ?std.lang.LinkMode = null, version: ?std.SemanticVersion = null, }; /// Returns the standard file system basename of a binary generated by the Zig compiler. pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 { const root_name = options.root_name; switch (options.ofmt) { .coff => switch (options.output_mode) { .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, options.os_tag.exeFileExt(options.cpu_arch), }), .Lib => { const suffix = switch (options.link_mode orelse .static) { .static => ".lib", .dynamic => ".dll", }; return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix }); }, .Obj => return std.fmt.allocPrint(allocator, "{s}.obj", .{root_name}), }, .elf => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), .Lib => { switch (options.link_mode orelse .static) { .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ options.os_tag.libPrefix(options.abi), root_name, }), .dynamic => { if (options.version) |ver| { return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{ options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch, }); } else { return std.fmt.allocPrint(allocator, "{s}{s}.so", .{ options.os_tag.libPrefix(options.abi), root_name, }); } }, } }, .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), }, .llvmbc => return std.fmt.allocPrint(allocator, "{s}.bc", .{root_name}), .llvmir => return std.fmt.allocPrint(allocator, "{s}.ll", .{root_name}), .macho => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), .Lib => { switch (options.link_mode orelse .static) { .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ options.os_tag.libPrefix(options.abi), root_name, }), .dynamic => { if (options.version) |ver| { return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{ options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch, }); } else { return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{ options.os_tag.libPrefix(options.abi), root_name, }); } }, } }, .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), }, .wasm => switch (options.output_mode) { .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, options.os_tag.exeFileExt(options.cpu_arch), }), .Lib => { switch (options.link_mode orelse .static) { .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ options.os_tag.libPrefix(options.abi), root_name, }), .dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}), } }, .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), }, .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}), .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}), .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}), .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), .plan9 => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, options.ofmt.fileExt(options.cpu_arch), }), .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ options.os_tag.libPrefix(options.abi), root_name, }), }, } } pub const SanitizeC = enum { off, trap, full, }; pub const BuildId = union(enum) { none, fast, uuid, sha1, md5, hexstring: HexString, pub fn eql(a: BuildId, b: BuildId) bool { const Tag = @typeInfo(BuildId).@"union".tag_type.?; const a_tag: Tag = a; const b_tag: Tag = b; if (a_tag != b_tag) return false; return switch (a) { .none, .fast, .uuid, .sha1, .md5 => true, .hexstring => |a_hexstring| mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()), }; } pub const HexString = struct { bytes: [32]u8, len: u8, /// Result is byte values, *not* hex-encoded. pub fn toSlice(hs: *const HexString) []const u8 { return hs.bytes[0..hs.len]; } }; /// Input is byte values, *not* hex-encoded. /// Asserts `bytes` fits inside `HexString` pub fn initHexString(bytes: []const u8) BuildId { var result: BuildId = .{ .hexstring = .{ .bytes = undefined, .len = @intCast(bytes.len), } }; @memcpy(result.hexstring.bytes[0..bytes.len], bytes); return result; } /// Converts UTF-8 text to a `BuildId`. pub fn parse(text: []const u8) !BuildId { if (mem.eql(u8, text, "none")) { return .none; } else if (mem.eql(u8, text, "fast")) { return .fast; } else if (mem.eql(u8, text, "uuid")) { return .uuid; } else if (mem.eql(u8, text, "sha1") or mem.eql(u8, text, "tree")) { return .sha1; } else if (mem.eql(u8, text, "md5")) { return .md5; } else if (mem.startsWith(u8, text, "0x")) { var result: BuildId = .{ .hexstring = undefined }; const slice = try std.fmt.hexToBytes(&result.hexstring.bytes, text[2..]); result.hexstring.len = @as(u8, @intCast(slice.len)); return result; } return error.InvalidBuildIdStyle; } test parse { try std.testing.expectEqual(BuildId.md5, try parse("md5")); try std.testing.expectEqual(BuildId.none, try parse("none")); try std.testing.expectEqual(BuildId.fast, try parse("fast")); try std.testing.expectEqual(BuildId.uuid, try parse("uuid")); try std.testing.expectEqual(BuildId.sha1, try parse("sha1")); try std.testing.expectEqual(BuildId.sha1, try parse("tree")); try std.testing.expect(BuildId.initHexString("").eql(try parse("0x"))); try std.testing.expect(BuildId.initHexString("\x12\x34\x56").eql(try parse("0x123456"))); try std.testing.expectError(error.InvalidLength, parse("0x12-34")); try std.testing.expectError(error.InvalidCharacter, parse("0xfoobbb")); try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx")); } pub fn format(id: BuildId, writer: *Writer) Writer.Error!void { switch (id) { .none, .fast, .uuid, .sha1, .md5 => { try writer.writeAll(@tagName(id)); }, .hexstring => |hs| { try writer.print("0x{x}", .{hs.toSlice()}); }, } } test format { try std.testing.expectFmt("none", "{f}", .{@as(BuildId, .none)}); try std.testing.expectFmt("fast", "{f}", .{@as(BuildId, .fast)}); try std.testing.expectFmt("uuid", "{f}", .{@as(BuildId, .uuid)}); try std.testing.expectFmt("sha1", "{f}", .{@as(BuildId, .sha1)}); try std.testing.expectFmt("md5", "{f}", .{@as(BuildId, .md5)}); try std.testing.expectFmt("0x", "{f}", .{BuildId.initHexString("")}); try std.testing.expectFmt("0x1234cdef", "{f}", .{BuildId.initHexString("\x12\x34\xcd\xef")}); } }; pub const LtoMode = enum { none, full, thin }; pub const Subsystem = enum { console, windows, posix, native, efi_application, efi_boot_service_driver, efi_rom, efi_runtime_driver, }; pub const CompressDebugSections = enum(u2) { none, zlib, zstd }; pub const RcIncludes = enum(u2) { /// Use MSVC if available, fall back to MinGW. any, /// Use MSVC include paths (MSVC install + Windows SDK, must be present on the system). msvc, /// Use MinGW include paths (distributed with Zig). gnu, /// Do not use any autodetected include paths. none, }; /// Renders a `std.Target.Cpu` value into a textual representation that can be parsed /// via the `-mcpu` flag passed to the Zig compiler. /// Appends the result to `buffer`. pub fn serializeCpu(buffer: *std.array_list.Managed(u8), cpu: std.Target.Cpu) Allocator.Error!void { const all_features = cpu.arch.allFeaturesList(); var populated_cpu_features = cpu.model.features; populated_cpu_features.populateDependencies(all_features); try buffer.appendSlice(cpu.model.name); if (populated_cpu_features.eql(cpu.features)) { // The CPU name alone is sufficient. return; } for (all_features, 0..) |feature, i_usize| { const i: std.Target.Cpu.Feature.Set.Index = @intCast(i_usize); const in_cpu_set = populated_cpu_features.isEnabled(i); const in_actual_set = cpu.features.isEnabled(i); try buffer.ensureUnusedCapacity(feature.name.len + 1); if (in_cpu_set and !in_actual_set) { buffer.appendAssumeCapacity('-'); buffer.appendSliceAssumeCapacity(feature.name); } else if (!in_cpu_set and in_actual_set) { buffer.appendAssumeCapacity('+'); buffer.appendSliceAssumeCapacity(feature.name); } } } pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![]u8 { var buffer = std.array_list.Managed(u8).init(ally); try serializeCpu(&buffer, cpu); return buffer.toOwnedSlice(); } /// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed. /// /// See also `fmtIdFlags`. pub fn fmtId(bytes: []const u8) FormatId { return .{ .bytes = bytes, .flags = .{} }; } /// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed. /// /// See also `fmtId`. pub fn fmtIdFlags(bytes: []const u8, flags: FormatId.Flags) FormatId { return .{ .bytes = bytes, .flags = flags }; } pub fn fmtIdPU(bytes: []const u8) FormatId { return .{ .bytes = bytes, .flags = .{ .allow_primitive = true, .allow_underscore = true } }; } pub fn fmtIdP(bytes: []const u8) FormatId { return .{ .bytes = bytes, .flags = .{ .allow_primitive = true } }; } test fmtId { const expectFmt = std.testing.expectFmt; try expectFmt("@\"while\"", "{f}", .{fmtId("while")}); try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true })}); try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_underscore = true })}); try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true, .allow_underscore = true })}); try expectFmt("hello", "{f}", .{fmtId("hello")}); try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true })}); try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_underscore = true })}); try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true, .allow_underscore = true })}); try expectFmt("@\"type\"", "{f}", .{fmtId("type")}); try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true })}); try expectFmt("@\"type\"", "{f}", .{fmtIdFlags("type", .{ .allow_underscore = true })}); try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true, .allow_underscore = true })}); try expectFmt("@\"_\"", "{f}", .{fmtId("_")}); try expectFmt("@\"_\"", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true })}); try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_underscore = true })}); try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true, .allow_underscore = true })}); try expectFmt("@\"i123\"", "{f}", .{fmtId("i123")}); try expectFmt("i123", "{f}", .{fmtIdFlags("i123", .{ .allow_primitive = true })}); try expectFmt("@\"4four\"", "{f}", .{fmtId("4four")}); try expectFmt("_underscore", "{f}", .{fmtId("_underscore")}); try expectFmt("@\"11\\\"23\"", "{f}", .{fmtId("11\"23")}); try expectFmt("@\"11\\x0f23\"", "{f}", .{fmtId("11\x0F23")}); try expectFmt("@\"\\r\\\"\\n\\xff😎\\xe2\\x80\\xa8\"", "{f}", .{fmtId("\r\"\n\xFF😎\u{2028}")}); // These are technically not currently legal in Zig. try expectFmt("@\"\"", "{f}", .{fmtId("")}); try expectFmt("@\"\\x00\"", "{f}", .{fmtId("\x00")}); } pub const FormatId = struct { bytes: []const u8, flags: Flags, pub const Flags = struct { allow_primitive: bool = false, allow_underscore: bool = false, }; /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed. pub fn format(ctx: FormatId, writer: *Writer) Writer.Error!void { const bytes = ctx.bytes; if (isValidId(bytes) and (ctx.flags.allow_primitive or !isPrimitive(bytes)) and (ctx.flags.allow_underscore or !isUnderscore(bytes))) { return writer.writeAll(bytes); } try writer.writeAll("@\""); try stringEscape(bytes, writer); try writer.writeByte('"'); } }; /// Return a formatter for escaping a double quoted Zig string. pub fn fmtString(bytes: []const u8) std.fmt.Alt([]const u8, stringEscape) { return .{ .data = bytes }; } /// Return a formatter for escaping a single quoted Zig string. pub fn fmtChar(c: u21) std.fmt.Alt(u21, charEscape) { return .{ .data = c }; } test fmtString { try std.testing.expectFmt("\\x0f", "{f}", .{fmtString("\x0f")}); try std.testing.expectFmt( \\" \\ hi \x07 \x11 \" derp '" , "\"{f}\"", .{fmtString(" \\ hi \x07 \x11 \" derp '")}); } test fmtChar { try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('⚡') }); } /// Print the string as escaped contents of a double quoted string. /// /// The following transformations are made: /// * escaped: '\n', '\r', '\t', '\\', '"' /// * hex-encoded: /// * ascii control characters /// * invalid UTF-8 sequences /// * non-ascii line endings (U+0085, U+2028, U+2029) /// * byte order marks (U+FEFF) /// /// Everything else is passed through unmodified. pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void { var remaining = bytes.len; while (remaining > 0) { remaining -= try stringEscapeInner(bytes[bytes.len - remaining ..], w); // Escape the first byte and try again. // Needing to escape the rest is not guaranteed. if (remaining > 0) { try w.writeAll("\\x"); try w.printInt(bytes[bytes.len - remaining], 16, .lower, .{ .width = 2, .fill = '0' }); remaining -= 1; } } } /// Returns the number of bytes consumed from `bytes`, which may be less than `bytes.len`. fn stringEscapeInner(bytes: []const u8, w: *Writer) Writer.Error!usize { var i: usize = 0; while (i < bytes.len) { const byte = bytes[i]; switch (byte) { '\t' => { try w.writeAll("\\t"); }, '\n' => { try w.writeAll("\\n"); }, '\r' => { try w.writeAll("\\r"); }, '\\' => { try w.writeAll("\\\\"); }, '"' => { try w.writeAll("\\\""); }, 0...8, 11, 12, 14...0x1f, 0x7f => { try w.writeAll("\\x"); try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); }, 0x20, 0x21, 0x23...0x5b, 0x5d...0x7e => { try w.writeByte(byte); }, 0x80...0xff => { const len, const escape = blk: { const len = std.unicode.utf8ByteSequenceLength(byte) catch break :blk .{ 1, true }; if (i + len > bytes.len) { return i; } const sequence = bytes[i..][0..len]; const code_point = std.unicode.utf8Decode(sequence) catch |err| switch (err) { error.Utf8CodepointTooLarge => break :blk .{ len, true }, else => break :blk .{ 1, true }, }; switch (code_point) { '\u{feff}', '\u{0085}', '\u{2028}', '\u{2029}' => break :blk .{ len, true }, else => break :blk .{ len, false }, } }; const sequence = bytes[i..][0..len]; if (escape) { for (sequence) |b| { try w.writeAll("\\x"); try w.printInt(b, 16, .lower, .{ .width = 2, .fill = '0' }); } } else { try w.writeAll(sequence); } i += len; continue; }, } i += 1; } return i; } pub const StringEscapeWriter = struct { out: *Writer, writer: Writer, pub const min_buffer_len = 4; pub fn init(out: *Writer, buffer: []u8) @This() { assert(buffer.len >= min_buffer_len); return .{ .out = out, .writer = .{ .vtable = &.{ .drain = @This().drain, .flush = @This().flush }, .buffer = buffer, }, }; } fn drain(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { var n: usize = try drainBufferRemaining(w, ""); for (data[0 .. data.len - 1]) |bytes| { n += try drainBufferRemaining(w, bytes); } const pattern = data[data.len - 1]; for (0..splat) |_| { n += try drainBufferRemaining(w, pattern); } return n; } fn flush(w: *Writer) Io.Writer.Error!void { const sew: *StringEscapeWriter = @alignCast(@fieldParentPtr("writer", w)); const out = sew.out; while (w.end != 0) { _ = try drainBufferRemaining(w, ""); // Escape the first byte and try again. // Needing to escape the rest is not guaranteed. const remaining = w.buffered(); if (remaining.len > 0) { try out.writeAll("\\x"); try out.printInt(remaining[0], 16, .lower, .{ .width = 2, .fill = '0' }); _ = w.consume(1); } } } /// Drain from the buffer first, taking from `bytes` as necessary to complete any /// incomplete UTF-8 sequences. Then, consume whatever is remaining of `bytes`, /// storing a possible trailing incomplete UTF-8 sequence in the buffer. /// /// On success, `w.buffered().len` is guaranteed to be < 4. fn drainBufferRemaining(w: *Writer, bytes: []const u8) Io.Writer.Error!usize { const sew: *StringEscapeWriter = @alignCast(@fieldParentPtr("writer", w)); const out = sew.out; var remaining_bytes = bytes; while (w.end != 0) { const n = try stringEscapeInner(w.buffered(), out); _ = w.consume(n); if (w.end != 0) { if (remaining_bytes.len == 0) return bytes.len; const len = std.unicode.utf8ByteSequenceLength(w.buffer[0]) catch unreachable; const missing = len - w.end; const available = remaining_bytes.len; const copy_len = @min(missing, available); const start = len - missing; @memcpy(w.buffer[start..][0..copy_len], remaining_bytes[0..copy_len]); w.end = start + copy_len; remaining_bytes = remaining_bytes[copy_len..]; } } const n = try stringEscapeInner(remaining_bytes, out); if (n < remaining_bytes.len) { const remaining = remaining_bytes.len - n; @memcpy(w.buffer[0..remaining], remaining_bytes[n..]); w.end = remaining; } return bytes.len; } }; test stringEscape { const bytes = "\x7f\t\n\r\\\"abc\xff\u{feff}\u{0085}\u{2028}\u{2029}\xed\xa0\x80\xf4\x90\x80\x80\xf4\x90a"; const escaped = "\\x7f\\t\\n\\r\\\\\\\"abc\\xff\\xef\\xbb\\xbf\\xc2\\x85\\xe2\\x80\\xa8\\xe2\\x80\\xa9\\xed\\xa0\\x80\\xf4\\x90\\x80\\x80\\xf4\\x90a"; var out_buf: [escaped.len]u8 = undefined; var out: Io.Writer = .fixed(&out_buf); try stringEscape(bytes, &out); try std.testing.expectEqualStrings(escaped, out.buffered()); } test StringEscapeWriter { const bytes = "\x7f\t\n\r\\\"abc\xff\u{feff}\u{0085}\u{2028}\u{2029}\xed\xa0\x80\xf4\x90\x80\x80\xf4\x90a"; const escaped = "\\x7f\\t\\n\\r\\\\\\\"abc\\xff\\xef\\xbb\\xbf\\xc2\\x85\\xe2\\x80\\xa8\\xe2\\x80\\xa9\\xed\\xa0\\x80\\xf4\\x90\\x80\\x80\\xf4\\x90a"; var sew_buf: [StringEscapeWriter.min_buffer_len]u8 = undefined; { var out_buf: [escaped.len]u8 = undefined; var out: Io.Writer = .fixed(&out_buf); var w: StringEscapeWriter = .init(&out, &sew_buf); const n = try w.writer.write(bytes); try w.writer.flush(); try std.testing.expectEqual(bytes.len, n); try std.testing.expectEqualStrings(escaped, out.buffered()); } { var out_buf: [escaped.len]u8 = undefined; var out: Io.Writer = .fixed(&out_buf); var w: StringEscapeWriter = .init(&out, &sew_buf); for (bytes) |byte| { try w.writer.writeByte(byte); } try w.writer.flush(); try std.testing.expectEqualStrings(escaped, out.buffered()); } } /// Print as escaped contents of a single-quoted string. pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void { switch (codepoint) { '\n' => try w.writeAll("\\n"), '\r' => try w.writeAll("\\r"), '\t' => try w.writeAll("\\t"), '\\' => try w.writeAll("\\\\"), '\'' => try w.writeAll("\\'"), '"', ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(@intCast(codepoint)), else => { if (std.math.cast(u8, codepoint)) |byte| { try w.writeAll("\\x"); try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); } else { try w.writeAll("\\u{"); try w.printInt(codepoint, 16, .lower, .{}); try w.writeByte('}'); } }, } } pub fn isValidId(bytes: []const u8) bool { if (bytes.len == 0) return false; for (bytes, 0..) |c, i| { switch (c) { '_', 'a'...'z', 'A'...'Z' => {}, '0'...'9' => if (i == 0) return false, else => return false, } } return Token.getKeyword(bytes) == null; } test isValidId { try std.testing.expect(!isValidId("")); try std.testing.expect(isValidId("foobar")); try std.testing.expect(!isValidId("a b c")); try std.testing.expect(!isValidId("3d")); try std.testing.expect(!isValidId("enum")); try std.testing.expect(isValidId("i386")); } pub fn isUnderscore(bytes: []const u8) bool { return bytes.len == 1 and bytes[0] == '_'; } test isUnderscore { try std.testing.expect(isUnderscore("_")); try std.testing.expect(!isUnderscore("__")); try std.testing.expect(!isUnderscore("_foo")); try std.testing.expect(isUnderscore("\x5f")); try std.testing.expect(!isUnderscore("\\x5f")); } /// If the source can be UTF-16LE encoded, this function asserts that `gpa` /// will align a byte-sized allocation to at least 2. Allocators that don't do /// this are rare. pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![:0]u8 { var buffer: std.ArrayList(u8) = .empty; defer buffer.deinit(gpa); if (file_reader.getSize()) |size| { const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong; // +1 to avoid resizing for the null byte added in toOwnedSliceSentinel below. try buffer.ensureTotalCapacityPrecise(gpa, casted_size + 1); } else |_| {} try file_reader.interface.appendRemaining(gpa, &buffer, .limited(max_src_size)); // Detect unsupported file types with their Byte Order Mark const unsupported_boms = [_][]const u8{ "\xff\xfe\x00\x00", // UTF-32 little endian "\xfe\xff\x00\x00", // UTF-32 big endian "\xfe\xff", // UTF-16 big endian }; for (unsupported_boms) |bom| { if (mem.startsWith(u8, buffer.items, bom)) { return error.UnsupportedEncoding; } } // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8 if (mem.startsWith(u8, buffer.items, "\xff\xfe")) { if (buffer.items.len % 2 != 0) return error.InvalidEncoding; return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(@alignCast(buffer.items))) catch |err| switch (err) { error.DanglingSurrogateHalf => error.UnsupportedEncoding, error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding, error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding, else => |e| return e, }; } return buffer.toOwnedSliceSentinel(gpa, 0); } pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void { var wip_errors: ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); return error_bundle.renderToStderr(io, .{}, color); } pub fn putAstErrorsIntoBundle( gpa: Allocator, tree: Ast, path: []const u8, wip_errors: *ErrorBundle.Wip, ) Allocator.Error!void { switch (tree.mode) { .zig => { var zir = try AstGen.generate(gpa, tree); defer zir.deinit(gpa); try wip_errors.addZirErrorMessages(zir, tree, tree.source, path); }, .zon => { var zoir = try ZonGen.generate(gpa, tree, .{}); defer zoir.deinit(gpa); try wip_errors.addZoirErrorMessages(zoir, tree, tree.source, path); }, } } pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target { return system.resolveTargetQuery(io, target_query) catch |err| std.process.fatal("unable to resolve target: {t}", .{err}); } pub fn parseTargetQueryOrReportFatalError( allocator: Allocator, opts: std.Target.Query.ParseOptions, ) std.Target.Query { var opts_with_diags = opts; var diags: std.Target.Query.ParseOptions.Diagnostics = .{}; if (opts_with_diags.diagnostics == null) { opts_with_diags.diagnostics = &diags; } return std.Target.Query.parse(opts_with_diags) catch |err| switch (err) { error.UnknownCpuModel => { help: { var help_text: std.ArrayList(u8) = .empty; defer help_text.deinit(allocator); for (diags.arch.?.allCpuModels()) |cpu| { help_text.print(allocator, " {s}\n", .{cpu.name}) catch break :help; } log.info("available CPUs for architecture '{s}':\n{s}", .{ @tagName(diags.arch.?), help_text.items, }); } std.process.fatal("unknown CPU: '{s}'", .{diags.cpu_name.?}); }, error.UnknownCpuFeature => { help: { var help_text: std.ArrayList(u8) = .empty; defer help_text.deinit(allocator); for (diags.arch.?.allFeaturesList()) |feature| { help_text.print(allocator, " {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; } log.info("available CPU features for architecture '{s}':\n{s}", .{ @tagName(diags.arch.?), help_text.items, }); } std.process.fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?}); }, error.UnknownObjectFormat => { help: { var help_text: std.ArrayList(u8) = .empty; defer help_text.deinit(allocator); inline for (@typeInfo(std.Target.ObjectFormat).@"enum".field_names) |field_name| { help_text.print(allocator, " {s}\n", .{field_name}) catch break :help; } log.info("available object formats:\n{s}", .{help_text.items}); } std.process.fatal("unknown object format: '{s}'", .{opts.object_format.?}); }, error.UnknownArchitecture => { help: { var help_text: std.ArrayList(u8) = .empty; defer help_text.deinit(allocator); inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".field_names) |field_name| { help_text.print(allocator, " {s}\n", .{field_name}) catch break :help; } log.info("available architectures:\n{s} native\n", .{help_text.items}); } std.process.fatal("unknown architecture: '{s}'", .{diags.unknown_architecture_name.?}); }, else => |e| std.process.fatal("unable to parse target query '{s}': {s}", .{ opts.arch_os_abi, @errorName(e), }), }; } /// Collects all the environment variables that Zig could possibly inspect, so /// that we can do reflection on this and print them with `zig env`. pub const EnvVar = enum { ZIG_GLOBAL_CACHE_DIR, ZIG_LOCAL_CACHE_DIR, ZIG_LOCAL_PKG_DIR, ZIG_LIB_DIR, ZIG_LIBC, ZIG_BUILD_ERROR_STYLE, ZIG_BUILD_MULTILINE_ERRORS, ZIG_BUILD_SUMMARY, ZIG_VERBOSE_LINK, ZIG_VERBOSE_CC, ZIG_VERBOSE_CMD, ZIG_DEBUG_CMD, ZIG_IS_DETECTING_LIBC_PATHS, ZIG_IS_AVOIDING_CALLING_ITSELF, // C toolchain integration NIX_CFLAGS_COMPILE, NIX_CFLAGS_LINK, NIX_LDFLAGS, C_INCLUDE_PATH, CPLUS_INCLUDE_PATH, LIBRARY_PATH, CC, PKG_CONFIG, // Terminal integration NO_COLOR, CLICOLOR_FORCE, // Debug info integration XDG_CACHE_HOME, LOCALAPPDATA, HOME, // Windows SDK integration PROGRAMDATA, // Homebrew integration HOMEBREW_PREFIX, pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { return map.contains(@tagName(ev)); } pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 { return map.get(@tagName(ev)); } }; pub const SimpleComptimeReason = enum(u32) { // Evaluating at comptime because a builtin operand must be comptime-known. // These messages all mention a specific builtin. operand_setEvalBranchQuota, operand_setFloatMode, operand_branchHint, operand_setRuntimeSafety, operand_embedFile, operand_shuffle_mask, operand_atomicRmw_operation, operand_reduce_operation, // Evaluating at comptime because an operand must be comptime-known. // These messages do not mention a specific builtin (and may not be about a builtin at all). export_target, export_options, extern_options, prefetch_options, call_modifier, compile_error_string, inline_assembly_code, atomic_order, slice_cat_operand, inline_call_target, generic_call_target, wasm_memory_index, work_group_dim_index, clobber, // Evaluating at comptime because types must be comptime-known. // Reasons other than `.type` are just more specific messages. type, int_signedness, int_bit_width, array_sentinel, array_length, pointer_size, pointer_attrs, pointer_sentinel, slice_sentinel, vector_length, fn_ret_ty, fn_param_types, fn_param_attrs, fn_attrs, struct_layout, struct_field_names, struct_field_types, struct_field_attrs, union_layout, union_field_names, union_field_types, union_field_attrs, tuple_field_types, enum_field_names, enum_field_values, union_enum_tag_type, enum_int_tag_type, packed_struct_backing_int_type, packed_union_backing_int_type, // Evaluating at comptime because decl/field name must be comptime-known. decl_name, field_name, tuple_field_index, // Evaluating at comptime because it is an attribute of a global declaration. container_var_init, @"callconv", @"align", @"addrspace", @"linksection", // Miscellaneous reasons. comptime_keyword, comptime_call_modifier, inline_loop_operand, switch_item, tuple_field_default_value, struct_field_default_value, enum_field_tag_value, slice_single_item_ptr_bounds, stored_to_comptime_field, stored_to_comptime_var, casted_to_comptime_int, casted_to_comptime_float, std_lang_decl, pub fn message(r: SimpleComptimeReason) []const u8 { return switch (r) { // zig fmt: off .operand_setEvalBranchQuota => "operand to '@setEvalBranchQuota' must be comptime-known", .operand_setFloatMode => "operand to '@setFloatMode' must be comptime-known", .operand_branchHint => "operand to '@branchHint' must be comptime-known", .operand_setRuntimeSafety => "operand to '@setRuntimeSafety' must be comptime-known", .operand_embedFile => "operand to '@embedFile' must be comptime-known", .operand_shuffle_mask => "'@shuffle' mask must be comptime-known", .operand_atomicRmw_operation => "'@atomicRmw' operation must be comptime-known", .operand_reduce_operation => "'@reduce' operation must be comptime-known", .export_target => "export target must be comptime-known", .export_options => "export options must be comptime-known", .extern_options => "extern options must be comptime-known", .prefetch_options => "prefetch options must be comptime-known", .call_modifier => "call modifier must be comptime-known", .compile_error_string => "compile error string must be comptime-known", .inline_assembly_code => "inline assembly code must be comptime-known", .atomic_order => "atomic order must be comptime-known", .slice_cat_operand => "slice being concatenated must be comptime-known", .inline_call_target => "function being called inline must be comptime-known", .generic_call_target => "generic function being called must be comptime-known", .wasm_memory_index => "wasm memory index must be comptime-known", .work_group_dim_index => "work group dimension index must be comptime-known", .clobber => "clobber must be comptime-known", .type => "types must be comptime-known", .int_signedness => "integer signedness must be comptime-known", .int_bit_width => "integer bit width must be comptime-known", .array_sentinel => "array sentinel value must be comptime-known", .array_length => "array length must be comptime-known", .pointer_size => "pointer size must be comptime-known", .pointer_attrs => "pointer attributes must be comptime-known", .pointer_sentinel => "pointer sentinel value must be comptime-known", .slice_sentinel => "slice sentinel value must be comptime-known", .vector_length => "vector length must be comptime-known", .fn_ret_ty => "function return type must be comptime-known", .fn_param_types => "function parameter types must be comptime-known", .fn_param_attrs => "function parameter attributes must be comptime-known", .fn_attrs => "function attributes must be comptime-known", .struct_layout => "struct layout must be comptime-known", .struct_field_names => "struct field names must be comptime-known", .struct_field_types => "struct field types must be comptime-known", .struct_field_attrs => "struct field attributes must be comptime-known", .union_layout => "union layout must be comptime-known", .union_field_names => "union field names must be comptime-known", .union_field_types => "union field types must be comptime-known", .union_field_attrs => "union field attributes must be comptime-known", .tuple_field_types => "tuple field types must be comptime-known", .enum_field_names => "enum field names must be comptime-known", .enum_field_values => "enum field values must be comptime-known", .union_enum_tag_type => "enum tag type of union must be comptime-known", .enum_int_tag_type => "integer tag type of enum must be comptime-known", .packed_struct_backing_int_type => "packed struct backing integer type must be comptime-known", .packed_union_backing_int_type => "packed struct backing integer type must be comptime-known", .decl_name => "declaration name must be comptime-known", .field_name => "field name must be comptime-known", .tuple_field_index => "tuple field index must be comptime-known", .container_var_init => "initializer of container-level variable must be comptime-known", .@"callconv" => "calling convention must be comptime-known", .@"align" => "alignment must be comptime-known", .@"addrspace" => "address space must be comptime-known", .@"linksection" => "linksection must be comptime-known", .comptime_keyword => "'comptime' keyword forces comptime evaluation", .comptime_call_modifier => "'.compile_time' call modifier forces comptime evaluation", .inline_loop_operand => "inline loop condition must be comptime-known", .switch_item => "switch prong values must be comptime-known", .tuple_field_default_value => "tuple field default value must be comptime-known", .struct_field_default_value => "struct field default value must be comptime-known", .enum_field_tag_value => "enum field tag value must be comptime-known", .slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds", .stored_to_comptime_field => "value stored to a comptime field must be comptime-known", .stored_to_comptime_var => "value stored to a comptime variable must be comptime-known", .casted_to_comptime_int => "value casted to 'comptime_int' must be comptime-known", .casted_to_comptime_float => "value casted to 'comptime_float' must be comptime-known", .std_lang_decl => "'std.lang' declaration values must be comptime-known", // zig fmt: on }; } }; /// Every kind of artifact which the compiler can emit. pub const EmitArtifact = enum { bin, @"asm", implib, llvm_ir, llvm_bc, docs, pdb, h, /// If using `Server` to communicate with the compiler, it will place requested artifacts in /// paths under the output directory, where those paths are named according to this function. /// Returned string is allocated with `gpa` and owned by the caller. pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 { const suffix: []const u8 = switch (ea) { .bin => return binNameAlloc(gpa, opts), .@"asm" => ".s", .implib => ".lib", .llvm_ir => ".ll", .llvm_bc => ".bc", .docs => "-docs", .pdb => ".pdb", .h => ".h", }; return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix }); } }; /// The defaults are chosen here to reduce the size of src/clang_options.zon pub const ClangCliParam = struct { name: []const u8, ze: ZigEquivalent = .other, syntax: Syntax = .flag, /// Prefixed by "-" pd1: bool = true, /// Prefixed by "--" pd2: bool = false, /// Prefixed by "/" psl: bool = false, pub const Syntax = union(enum) { /// A flag with no values. flag, /// An option which prefixes its (single) value. joined, /// An option which is followed by its value. separate, /// An option which is either joined to its (non-empty) value, or followed by its value. joined_or_separate, /// An option which is both joined to its (first) value, and followed by its (second) value. joined_and_separate, /// An option followed by its values, which are separated by commas. comma_joined, /// An option which consumes an optional joined argument and any other remaining arguments. remaining_args_joined, /// An option which is which takes multiple (separate) arguments. multi_arg: u8, }; pub const ZigEquivalent = enum { target, o, c, r, m, x, other, positional, l, ignore, driver_punt, pic, no_pic, pie, no_pie, lto, no_lto, unwind_tables, no_unwind_tables, asynchronous_unwind_tables, no_asynchronous_unwind_tables, nostdlib, nostdlib_cpp, shared, rdynamic, wl, wp, preprocess_only, asm_only, optimize, debug, gdwarf32, gdwarf64, sanitize, no_sanitize, sanitize_trap, no_sanitize_trap, linker_script, dry_run, verbose, for_linker, linker_input_z, lib_dir, mcpu, dep_file, dep_file_to_stdout, framework_dir, framework, nostdlibinc, red_zone, no_red_zone, omit_frame_pointer, no_omit_frame_pointer, function_sections, no_function_sections, data_sections, no_data_sections, builtin, no_builtin, color_diagnostics, no_color_diagnostics, stack_check, no_stack_check, stack_protector, no_stack_protector, strip, exec_model, emit_llvm, sysroot, entry, force_undefined_symbol, weak_library, weak_framework, headerpad_max_install_names, compress_debug_sections, install_name, undefined, force_load_objc, mingw_unicode_entry_point, san_cov_trace_pc_guard, san_cov, no_san_cov, rtlib, static, dynamic, version, patchable_function_entry, }; pub fn matchEql(self: @This(), arg: []const u8) u2 { if (self.pd1 and arg.len >= self.name.len + 1 and mem.startsWith(u8, arg, "-") and mem.eql(u8, arg[1..], self.name)) { return 1; } if (self.pd2 and arg.len >= self.name.len + 2 and mem.startsWith(u8, arg, "--") and mem.eql(u8, arg[2..], self.name)) { return 2; } if (self.psl and arg.len >= self.name.len + 1 and mem.startsWith(u8, arg, "/") and mem.eql(u8, arg[1..], self.name)) { return 1; } return 0; } pub fn matchStartsWith(self: @This(), arg: []const u8) usize { if (self.pd1 and arg.len >= self.name.len + 1 and mem.startsWith(u8, arg, "-") and mem.startsWith(u8, arg[1..], self.name)) { return self.name.len + 1; } if (self.pd2 and arg.len >= self.name.len + 2 and mem.startsWith(u8, arg, "--") and mem.startsWith(u8, arg[2..], self.name)) { return self.name.len + 2; } if (self.psl and arg.len >= self.name.len + 1 and mem.startsWith(u8, arg, "/") and mem.startsWith(u8, arg[1..], self.name)) { return self.name.len + 1; } return 0; } }; /// Deprecated pub const AllocPrintCmdOptions = struct { cwd: ?[]const u8 = null, parent_env: ?*const std.process.Environ.Map = null, child_env: ?*const std.process.Environ.Map = null, }; /// Deprecated pub fn allocPrintCmd(gpa: Allocator, argv: []const []const u8, options: AllocPrintCmdOptions) Allocator.Error![]u8 { var aw: Io.Writer.Allocating = .init(gpa); defer aw.deinit(); SubprocessCommand.format(.{ .argv = argv, .cwd = options.cwd, .parent_env = options.parent_env, .child_env = options.child_env, }, &aw.writer) catch return error.OutOfMemory; return aw.toOwnedSlice(); } fn shellEscape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void { for (string) |c| { if (switch (c) { else => true, '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false, '=' => is_argv0, }) break; } else return writer.writeAll(string); try writer.writeByte('"'); for (string) |c| { if (switch (c) { std.ascii.control_code.nul => break, '!', '"', '$', '\\', '`' => true, else => !std.ascii.isPrint(c), }) try writer.writeByte('\\'); switch (c) { std.ascii.control_code.nul => unreachable, std.ascii.control_code.bel => try writer.writeByte('a'), std.ascii.control_code.bs => try writer.writeByte('b'), std.ascii.control_code.ht => try writer.writeByte('t'), std.ascii.control_code.lf => try writer.writeByte('n'), std.ascii.control_code.vt => try writer.writeByte('v'), std.ascii.control_code.ff => try writer.writeByte('f'), std.ascii.control_code.cr => try writer.writeByte('r'), std.ascii.control_code.esc => try writer.writeByte('E'), ' '...'~' => try writer.writeByte(c), else => try writer.print("{o:0>3}", .{c}), } } try writer.writeByte('"'); } pub const SubprocessCommand = struct { argv: []const []const u8, cwd: ?[]const u8 = null, parent_env: ?*const std.process.Environ.Map = null, child_env: ?*const std.process.Environ.Map = null, pub fn format(sc: SubprocessCommand, w: *Io.Writer) Io.Writer.Error!void { if (sc.cwd) |path| { try w.print("cd {s} && ", .{path}); } if (sc.child_env) |child_env| { for (child_env.keys(), child_env.values()) |key, value| { if (sc.parent_env) |parent_env| { if (parent_env.get(key)) |process_value| { if (mem.eql(u8, value, process_value)) continue; } } try w.print("{s}=", .{key}); try shellEscape(w, value, false); try w.writeByte(' '); } } try shellEscape(w, sc.argv[0], true); for (sc.argv[1..]) |arg| { try w.writeByte(' '); try shellEscape(w, arg, false); } } }; /// Like `std.process.currentPathAlloc`, but also resolves the path with `Dir.path.resolve`. This /// means the path has no repeated separators, no "." or ".." components, and no trailing separator. /// On WASI, "" is returned instead of ".". pub fn getResolvedCwd(io: Io, gpa: Allocator) std.process.CurrentPathAllocError![]u8 { if (builtin.os.tag == .wasi) { if (std.debug.runtime_safety) { const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); assert(mem.eql(u8, cwd, ".")); } return ""; } const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); const resolved = try Dir.path.resolve(gpa, &.{cwd}); assert(Dir.path.isAbsolute(resolved)); return resolved; } pub const Directories = struct { /// The string returned by `introspect.getResolvedCwd`. This is typically an absolute path, /// but on WASI is the empty string "" instead, because WASI does not have absolute paths. cwd: []const u8, /// The Zig 'lib' directory. /// `zig_lib.path` is resolved (`resolvePath`) or `null` for cwd. /// Guaranteed to be a different path from `global_cache` and `local_cache`. zig_lib: Cache.Directory, /// The global Zig cache directory. /// `global_cache.path` is resolved (`resolvePath`) or `null` for cwd. global_cache: Cache.Directory, /// The local Zig cache directory. /// `local_cache.path` is resolved (`resolvePath`) or `null` for cwd. /// This may be the same as `global_cache`. local_cache: Cache.Directory, /// The directory that contains build.zig. This path is provided by the /// build system, when the build system is used, otherwise, it is `null` /// for cwd. build_root: Cache.Directory, pub fn deinit(dirs: *Directories, io: Io) void { // The local and global caches could be the same. const close_local = dirs.local_cache.handle.handle != dirs.global_cache.handle.handle; const close_build_root = dirs.build_root.handle.handle != Io.Dir.cwd().handle; dirs.global_cache.handle.close(io); if (close_local) dirs.local_cache.handle.close(io); dirs.zig_lib.handle.close(io); if (close_build_root) dirs.build_root.handle.close(io); } /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for /// use by sub-compilations (e.g. compiler_rt). Do not `deinit` the returned `Directories`; it /// shares handles with `dirs`. pub fn withoutLocalCache(dirs: Directories) Directories { return .{ .cwd = dirs.cwd, .zig_lib = dirs.zig_lib, .global_cache = dirs.global_cache, .local_cache = dirs.global_cache, .build_root = dirs.build_root, }; } const LocalCacheStrategy = union(enum) { override: []const u8, search, global, }; pub const InitOptions = struct { override_zig_lib: ?[]const u8, override_global_cache: ?[]const u8, build_root: ?[]const u8, local_cache_strat: LocalCacheStrategy, preopens: std.process.Preopens, self_exe_path: switch (builtin.target.os.tag) { .wasi => void, else => []const u8, }, environ_map: *const std.process.Environ.Map, cwd: []const u8, }; /// Uses `std.process.fatal` on error conditions. pub fn init(arena: Allocator, io: Io, options: InitOptions) Directories { const wasi = builtin.target.os.tag == .wasi; const cwd = options.cwd; const zig_lib: Cache.Directory = d: { if (options.override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); if (wasi) break :d getPreopen(options.preopens, "/lib"); break :d findZigLibDirFromSelfExe(arena, io, cwd, options.self_exe_path) catch |err| { fatal("unable to find zig installation directory from executable path {q}: {t}", .{ options.self_exe_path, err, }); }; }; const build_root: Cache.Directory = if (options.build_root) |s| openUnresolved(arena, io, cwd, s, .@"build root") else .cwd(); const global_cache: Cache.Directory = d: { if (options.override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache"); if (wasi) break :d getPreopen(options.preopens, "/cache"); const path = resolveGlobalCacheDir(arena, options.environ_map) catch |err| { fatal("unable to resolve zig cache directory: {t}", .{err}); }; break :d openUnresolved(arena, io, cwd, path, .@"global cache"); }; const local_cache = getLocalCacheDirectory(arena, io, cwd, global_cache, options.local_cache_strat); if (mem.eql(u8, zig_lib.path orelse "", global_cache.path orelse "")) { fatal("zig lib directory '{f}' cannot be equal to global cache directory '{f}'", .{ zig_lib, global_cache }); } if (mem.eql(u8, zig_lib.path orelse "", local_cache.path orelse "")) { fatal("zig lib directory '{f}' cannot be equal to local cache directory '{f}'", .{ zig_lib, local_cache }); } return .{ .cwd = cwd, .zig_lib = zig_lib, .global_cache = global_cache, .local_cache = local_cache, .build_root = build_root, }; } fn getLocalCacheDirectory( arena: Allocator, io: Io, cwd: []const u8, global_cache: Cache.Directory, local_cache_strat: LocalCacheStrategy, ) Cache.Directory { return switch (local_cache_strat) { .override => |path| openUnresolved(arena, io, cwd, path, .@"local cache"), .search => d: { const maybe_path = resolveSuitableLocalCacheDir(arena, io, cwd) catch |err| fatal("unable to resolve zig cache directory: {t}", .{err}); const path = maybe_path orelse break :d global_cache; break :d openUnresolved(arena, io, cwd, path, .@"local cache"); }, .global => global_cache, }; } fn getPreopen(preopens: std.process.Preopens, name: []const u8) Cache.Directory { return .{ .path = if (mem.eql(u8, name, ".")) null else name, .handle = switch (preopens.get(name) orelse fatal("preopen not found: {q}", .{name})) { .file => fatal("preopen {q} is not a directory", .{name}), .dir => |d| d, }, }; } pub fn openUnresolved( arena: Allocator, io: Io, cwd: []const u8, unresolved_path: []const u8, thing: enum { @"zig lib", @"global cache", @"local cache", @"build root" }, ) Cache.Directory { const path = resolvePath(arena, cwd, &.{unresolved_path}) catch |err| { fatal("unable to resolve {t} directory: {t}", .{ thing, err }); }; const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { .@"zig lib", .@"build root" => Dir.cwd().openDir(io, nonempty_path, .{}), .@"global cache", .@"local cache" => Dir.cwd().createDirPathOpen(io, nonempty_path, .{}), }; return .{ .path = if (path.len == 0) null else path, .handle = handle_or_err catch |err| { const extra_str: []const u8 = e: { if (thing == .@"global cache") switch (err) { error.AccessDenied, error.ReadOnlyFileSystem => break :e "\n" ++ "If this location is not writable then consider specifying an alternative with " ++ "the ZIG_GLOBAL_CACHE_DIR environment variable or the --global-cache-dir option.", else => {}, }; break :e ""; }; fatal("unable to open {t} directory {q}: {t}{s}", .{ thing, nonempty_path, err, extra_str }); }, }; } }; /// Both the directory handle and the path are newly allocated resources which the caller now owns. pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { const cwd_path = try getResolvedCwd(io, gpa); defer gpa.free(cwd_path); const self_exe_path = try std.process.executablePathAlloc(io, gpa); defer gpa.free(self_exe_path); return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path); } /// Both the directory handle and the path are newly allocated resources which the caller now owns. pub fn findZigLibDirFromSelfExe( allocator: Allocator, io: Io, /// The return value of `getResolvedCwd`. /// Passed as an argument to avoid pointlessly repeating the call. cwd_path: []const u8, self_exe_path: []const u8, ) error{ OutOfMemory, FileNotFound }!Cache.Directory { const cwd = Dir.cwd(); var cur_path: []const u8 = self_exe_path; while (Dir.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { var base_dir = cwd.openDir(io, dirname, .{}) catch continue; defer base_dir.close(io); const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue; const p = try Dir.path.join(allocator, &.{ dirname, sub_directory.path.? }); defer allocator.free(p); const resolved = try resolvePath(allocator, cwd_path, &.{p}); return .{ .handle = sub_directory.handle, .path = if (resolved.len == 0) null else resolved, }; } return error.FileNotFound; } /// Returns the sub_path that worked, or `null` if none did. /// The path of the returned Directory is relative to `base`. /// The handle of the returned Directory is open. fn testZigInstallPrefix(io: Io, base_dir: Dir) ?Cache.Directory { const test_index_file = "std" ++ Dir.path.sep_str ++ "std.zig"; zig_dir: { // Try lib/zig/std/std.zig const lib_zig = "lib" ++ Dir.path.sep_str ++ "zig"; var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir; const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); break :zig_dir; }; file.close(io); return .{ .handle = test_zig_dir, .path = lib_zig }; } // Try lib/std/std.zig var test_zig_dir = base_dir.openDir(io, "lib", .{}) catch return null; const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); return null; }; file.close(io); return .{ .handle = test_zig_dir, .path = "lib" }; } pub fn resolveGlobalCacheDir(arena: Allocator, environ_map: *const std.process.Environ.Map) ![]const u8 { if (EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map)) |value| return value; const app_name = "zig"; switch (builtin.os.tag) { .wasi => @compileError("on WASI the global cache dir must be resolved with preopens"), .windows => { const local_app_data_dir = EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.AppDataDirUnavailable; return Dir.path.join(arena, &.{ local_app_data_dir, app_name }); }, else => { if (EnvVar.XDG_CACHE_HOME.get(environ_map)) |cache_root| { if (cache_root.len > 0) { return Dir.path.join(arena, &.{ cache_root, app_name }); } } if (EnvVar.HOME.get(environ_map)) |home| { if (home.len > 0) { return Dir.path.join(arena, &.{ home, ".cache", app_name }); } } return error.AppDataDirUnavailable; }, } } /// Searches upwards from `cwd` for a directory containing a `build.zig` file. /// If such a directory is found, returns the path to it joined to the `.zig_cache` name. /// Otherwise, returns `null`, indicating no suitable local cache location. pub fn resolveSuitableLocalCacheDir(arena: Allocator, io: Io, cwd: []const u8) Allocator.Error!?[]u8 { var cur_dir = cwd; while (true) { const joined = try Dir.path.join(arena, &.{ cur_dir, build_zig_basename }); if (Dir.cwd().access(io, joined, .{})) |_| { return try Dir.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); } else |err| switch (err) { error.FileNotFound => { cur_dir = Dir.path.dirname(cur_dir) orelse return null; continue; }, else => return null, } } } /// Similar to `Dir.path.resolve`, but converts to a cwd-relative path, or, if that would /// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd /// returns the empty string ("") instead of ".". pub fn resolvePath( gpa: Allocator, /// The return value of `getResolvedCwd`. /// Passed as an argument to avoid pointlessly repeating the call. cwd_resolved: []const u8, paths: []const []const u8, ) Allocator.Error![]u8 { if (builtin.target.os.tag == .wasi) { assert(mem.eql(u8, cwd_resolved, "")); const res = try Dir.path.resolve(gpa, paths); if (mem.eql(u8, res, ".")) { gpa.free(res); return ""; } return res; } // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`. for (paths) |p| { if (Dir.path.isAbsolute(p)) break; // absolute path if (mem.find(u8, p, "..") != null) break; // may contain up-dir } else { // no absolute path, no "..". const res = try Dir.path.resolve(gpa, paths); if (mem.eql(u8, res, ".")) { gpa.free(res); return ""; } assert(!Dir.path.isAbsolute(res)); assert(!isUpDir(res)); return res; } // The fast path failed; resolve the whole thing. // Optimization: `paths` often has just one element. const path_resolved = switch (paths.len) { 0 => unreachable, 1 => try Dir.path.resolve(gpa, &.{ cwd_resolved, paths[0] }), else => r: { const all_paths = try gpa.alloc([]const u8, paths.len + 1); defer gpa.free(all_paths); all_paths[0] = cwd_resolved; @memcpy(all_paths[1..], paths); break :r try Dir.path.resolve(gpa, all_paths); }, }; errdefer gpa.free(path_resolved); assert(Dir.path.isAbsolute(path_resolved)); assert(Dir.path.isAbsolute(cwd_resolved)); if (!mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd if (path_resolved.len == cwd_resolved.len) { // equal to cwd gpa.free(path_resolved); return ""; } if (path_resolved[cwd_resolved.len] != Dir.path.sep) return path_resolved; // not in cwd (last component differs) // in cwd; extract sub path const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]); gpa.free(path_resolved); return sub_path; } pub fn isUpDir(p: []const u8) bool { return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == Dir.path.sep); } pub const BuildExeSubprocessOptions = struct { argv: []const []const u8, cache_root: Cache.Directory, root_name: []const u8, environ_map: ?*std.process.Environ.Map = null, cache_manifest: ?*Cache.Manifest = null, arch_os_abi: ?[]const u8 = null, cpu_features: ?[]const u8 = null, progress_node: std.Progress.Node = .none, skip_log_cmdline_on_compile_errors: bool = false, /// If this is provided, compilation errors are sent here. Otherwise, they are printed to stderr. /// Must be an initialized `ErrorBundle`; if it is updated then it is cleared first. error_bundle: ?*ErrorBundle = null, }; pub const BuildExeSubprocessError = error{ /// Error message has been logged. AlreadyReported, /// Error message has been logged, and source files added to the `Cache.Manifest`. FailedButCacheIntact, } || Io.Cancelable || Allocator.Error; pub const BuildExeSubprocessResult = struct { received_fs_inputs: bool, cache_hit: bool, path: Cache.Path, }; /// Assumes `argv` has `--listen=-` in it and the child process is `zig build-exe`. /// /// Result path is allocated via gpa. pub fn buildExeSubprocess( gpa: Allocator, io: Io, options: BuildExeSubprocessOptions, ) BuildExeSubprocessError!BuildExeSubprocessResult { const cmd: SubprocessCommand = .{ .argv = options.argv }; var child = std.process.spawn(io, .{ .argv = options.argv, .environ_map = options.environ_map, .stdin = .pipe, .stdout = .pipe, .stderr = .pipe, .progress_node = options.progress_node, }) catch |err| { log.err("spawning command {t}: {f}", .{ err, cmd }); return error.AlreadyReported; }; defer child.kill(io); var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; var multi_reader: Io.File.MultiReader = undefined; multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); defer multi_reader.deinit(); const stdout = multi_reader.reader(0); const stderr = multi_reader.reader(1); var stdin_buffer: [8]u8 = undefined; var stdin_writer = child.stdin.?.writerStreaming(io, &stdin_buffer); var client: Client = .{ .in = stdout, .out = &stdin_writer.interface, }; (blk: { client.serveMessageHeader(.{ .tag = .update, .bytes_len = 0 }) catch |err| break :blk err; client.serveMessageHeader(.{ .tag = .exit, .bytes_len = 0 }) catch |err| break :blk err; client.out.flush() catch |err| break :blk err; }) catch |err| switch (err) { error.WriteFailed => { if (stdin_writer.err.? == error.Canceled) return error.Canceled; log.err("{t} writing to command: {f}", .{ stdin_writer.err.?, cmd }); return error.AlreadyReported; }, }; var result: ?Cache.Path = null; defer if (result) |r| gpa.free(r.sub_path); var default_error_bundle: ErrorBundle = .empty; defer default_error_bundle.deinit(gpa); const error_bundle = options.error_bundle orelse &default_error_bundle; var received_fs_inputs = false; var cache_hit = false; var eos_err: error{EndOfStream}!void = {}; while (true) { const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) { error.Timeout => unreachable, error.EndOfStream => |e| { if (client.in.bufferedLen() == 0) break; // Better to report the crash with stderr below, but we set // this in case the child exits successfully while violating // this protocol. eos_err = e; break; }, error.Canceled, error.OutOfMemory => |e| return e, else => |e| { log.err("{t} reading from command: {f}", .{ e, cmd }); return error.AlreadyReported; }, }; const body = stdout.take(header.bytes_len) catch unreachable; switch (header.tag) { .zig_version => { if (!mem.eql(u8, builtin.zig_version_string, body)) { log.err("zig protocol version mismatch from command: {f}", .{cmd}); return error.AlreadyReported; } }, .error_bundle => { error_bundle.deinit(gpa); error_bundle.* = Server.allocErrorBundle(gpa, body) catch |err| switch (err) { error.EndOfStream => break, else => |e| return e, }; }, .emit_digest => { const EmitDigest = Server.Message.EmitDigest; const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body); cache_hit = ebp_hdr.flags.cache_hit; const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; if (result) |r| gpa.free(r.sub_path); result = .{ .root_dir = options.cache_root, .sub_path = try Dir.path.join(gpa, &.{ "o", &Cache.binToHex(digest.*) }), }; }, .file_system_inputs => if (options.cache_manifest) |man| { received_fs_inputs = true; var it = mem.splitScalar(u8, body, 0); while (it.next()) |prefixed_path| { const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1)); const sub_path = prefixed_path[1..]; man.addDiscoveredPath(.{ .discovered_path = .{ .prefixed = .{ .prefix = @intCast(@backingInt(prefix)), .sub_path = sub_path, } }, }) catch |err| switch (err) { error.Canceled, error.OutOfMemory => |e| return e, else => |e| { log.err("adding {t} {s} to cache failed: {t}", .{ prefix, sub_path, e }); return error.AlreadyReported; }, }; } }, else => {}, // ignore other messages } } const stderr_contents = stderr.buffered(); if (stderr_contents.len > 0) log.warn("unexpected stderr from {s} command:\n{s}", .{ options.argv[0], stderr_contents }); eos_err catch { log.err("unexpected end of stream from command: {f}", .{cmd}); return error.AlreadyReported; }; // Send EOF to stdin. child.stdin.?.close(io); child.stdin = null; const term = child.wait(io) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| { log.err("{t} waiting for command: {f}", .{ e, cmd }); return error.AlreadyReported; }, }; if (!term.success()) { log.err("command {f}: {f}", .{ term, cmd }); if (received_fs_inputs) return error.FailedButCacheIntact; return error.AlreadyReported; } if (default_error_bundle.errorMessageCount() > 0) { default_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| { log.err("failed rendering error bundle: {t}", .{e}); return error.AlreadyReported; }, }; if (!options.skip_log_cmdline_on_compile_errors) log.err("command reported {d} compilation errors: {f}", .{ default_error_bundle.errorMessageCount(), cmd, }); if (received_fs_inputs) return error.FailedButCacheIntact; return error.AlreadyReported; } if (error_bundle.errorMessageCount() > 0) { if (received_fs_inputs) return error.FailedButCacheIntact; return error.AlreadyReported; } const base_path = result orelse { log.err("command failed to report result: {f}", .{cmd}); return error.AlreadyReported; }; const parsed_target = system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ .arch_os_abi = options.arch_os_abi orelse "native", .cpu_features = options.cpu_features, }) catch unreachable) catch unreachable; const bin_name = try binNameAlloc(gpa, .{ .root_name = options.root_name, .cpu_arch = parsed_target.cpu.arch, .os_tag = parsed_target.os.tag, .ofmt = parsed_target.ofmt, .abi = parsed_target.abi, .output_mode = .Exe, }); defer gpa.free(bin_name); return .{ .received_fs_inputs = received_fs_inputs, .cache_hit = cache_hit, .path = try base_path.join(gpa, bin_name), }; } test { _ = Ast; _ = AstRlAnnotate; _ = BuiltinFn; _ = Client; _ = ErrorBundle; _ = LibCDirs; _ = LibCInstallation; _ = Server; _ = TokenSmith; _ = WindowsSdk; _ = number_literal; _ = primitives; _ = string_literal; _ = system; _ = target; _ = c_translation; _ = llvm; _ = @import("zig/parser_fuzz.zig"); }