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.
//! 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}),
},
.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")});
// 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.
pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
for (bytes) |byte| switch (byte) {
'\n' => try w.writeAll("\\n"),
'\r' => try w.writeAll("\\r"),
'\t' => try w.writeAll("\\t"),
'\\' => try w.writeAll("\\\\"),
'"' => try w.writeAll("\\\""),
'\'' => try w.writeByte('\''),
' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte),
else => {
try w.writeAll("\\x");
try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
},
};
}
/// 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 = undefined;
try wip_errors.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.array_list.Managed(u8).init(allocator);
defer help_text.deinit();
for (diags.arch.?.allCpuModels()) |cpu| {
help_text.print(" {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.array_list.Managed(u8).init(allocator);
defer help_text.deinit();
for (diags.arch.?.allFeaturesList()) |feature| {
help_text.print(" {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.array_list.Managed(u8).init(allocator);
defer help_text.deinit();
inline for (@typeInfo(std.Target.ObjectFormat).@"enum".field_names) |field_name| {
help_text.print(" {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.array_list.Managed(u8).init(allocator);
defer help_text.deinit();
inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".field_names) |field_name| {
help_text.print(" {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_VERBOSE_LINK,
ZIG_VERBOSE_CC,
ZIG_VERBOSE_CMD,
ZIG_DEBUG_CMD,
ZIG_DEBUG_MAKER,
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,
};
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,
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;
dirs.global_cache.handle.close(io);
if (close_local) dirs.local_cache.handle.close(io);
dirs.zig_lib.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,
};
}
const LocalCacheStrategy = union(enum) {
override: []const u8,
search,
global,
};
/// Uses `std.process.fatal` on error conditions.
pub fn init(
arena: Allocator,
io: Io,
override_zig_lib: ?[]const u8,
override_global_cache: ?[]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,
) Directories {
const wasi = builtin.target.os.tag == .wasi;
const zig_lib: Cache.Directory = d: {
if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib");
if (wasi) break :d getPreopen(preopens, "/lib");
break :d findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| {
fatal("unable to find zig installation directory from executable path {q}: {t}", .{ self_exe_path, err });
};
};
const global_cache: Cache.Directory = d: {
if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache");
if (wasi) break :d getPreopen(preopens, "/cache");
const path = resolveGlobalCacheDir(arena, 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, 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,
};
}
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" },
) 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" => 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.indexOf(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,
};
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 stderr_task = io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited }) catch
@panic("TODO use multireader instead");
defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {};
var stdout_buffer: [512]u8 = undefined;
var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer);
const stdout = &stdout_reader.interface;
{
var w = child.stdin.?.writer(io, &.{});
w.interface.writeStruct(Client.Message.Header{ .tag = .update, .bytes_len = 0 }, .little) catch |err| switch (err) {
error.WriteFailed => {
log.err("{t} writing to command: {f}", .{ w.err.?, cmd });
return error.AlreadyReported;
},
};
w.interface.writeStruct(Client.Message.Header{ .tag = .exit, .bytes_len = 0 }, .little) catch |err| switch (err) {
error.WriteFailed => {
log.err("{t} writing to command: {f}", .{ w.err.?, cmd });
return error.AlreadyReported;
},
};
}
const Header = Server.Message.Header;
var result: ?Cache.Path = null;
defer if (result) |r| gpa.free(r.sub_path);
var result_error_bundle: ErrorBundle = .empty;
defer result_error_bundle.deinit(gpa);
var body_buffer: std.ArrayList(u8) = .empty;
defer body_buffer.deinit(gpa);
var received_fs_inputs = false;
var cache_hit = false;
while (true) {
const header = stdout.takeStruct(Header, .little) catch |err| switch (err) {
error.ReadFailed => {
log.err("{t} reading from command: {f}", .{ stdout_reader.err.?, cmd });
return error.AlreadyReported;
},
error.EndOfStream => break,
};
body_buffer.clearRetainingCapacity();
stdout.appendExact(gpa, &body_buffer, header.bytes_len) catch |err| switch (err) {
error.ReadFailed => {
log.err("{t} reading from command: {f}", .{ stdout_reader.err.?, cmd });
return error.AlreadyReported;
},
error.OutOfMemory => |e| return e,
error.EndOfStream => {
log.err("unexpected end of stream from command: {f}", .{cmd});
return error.AlreadyReported;
},
};
const body = body_buffer.items;
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 => {
result_error_bundle.deinit(gpa);
result_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 = try gpa.dupe(u8, prefixed_path[1..]);
var keep = false;
defer if (!keep) gpa.free(sub_path);
keep = man.addPrefixedPathPost(.{
.prefix = @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_task.await(io) catch |err| switch (err) {
error.Canceled, error.OutOfMemory => |e| return e,
else => |e| c: {
log.warn("{t} reading stderr from command: {f}", .{ e, cmd });
break :c "";
},
};
if (stderr_contents.len > 0)
log.warn("unexpected stderr from {s} command:\n{s}", .{ options.argv[0], stderr_contents });
// 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 (result_error_bundle.errorMessageCount() > 0) {
result_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}", .{
result_error_bundle.errorMessageCount(), cmd,
});
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),
};
}
fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 {
var file_reader: Io.File.Reader = .initStreaming(file, io, &.{});
return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?,
else => |e| return e,
};
}
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");
}