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