mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-10-28 10:45:48 +08:00
2079 lines
77 KiB
Zig
2079 lines
77 KiB
Zig
//! 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");
|
|
}
|