zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit a4380a30f5aeafd03e909164b28550e5960500f9 (tree)
parent 6e078883eebd0532928dba0c70e86a2eee0f246b
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Tue, 27 Feb 2024 17:12:53 -0700

move `zig libc` command to be lazily built

part of #19063

This is a prerequisite for doing the same for Resinator.

Diffstat:
MCMakeLists.txt | 10+++++-----
Alib/compiler/libc.zig | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/Target.zig | 16++++++++++++++++
Mlib/std/zig.zig | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Alib/std/zig/LibCDirs.zig | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/std/zig/LibCInstallation.zig | 709+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/std/zig/WindowsSdk.zig | 964+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/std/zig/target.zig | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Compilation.zig | 238++++---------------------------------------------------------------------------
Msrc/glibc.zig | 9++++-----
Msrc/introspect.zig | 44+++-----------------------------------------
Dsrc/libc_installation.zig | 712-------------------------------------------------------------------------------
Msrc/link.zig | 2+-
Msrc/link/MachO.zig | 8+-------
Msrc/main.zig | 228++++++++-----------------------------------------------------------------------
Msrc/musl.zig | 27++-------------------------
Msrc/print_env.zig | 4++--
Msrc/print_targets.zig | 2+-
Msrc/target.zig | 163-------------------------------------------------------------------------------
Msrc/wasi_libc.zig | 4+---
Dsrc/windows_sdk.zig | 965-------------------------------------------------------------------------------
21 files changed, 2395 insertions(+), 2377 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -507,16 +507,18 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/zig/Ast.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/AstGen.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/AstRlAnnotate.zig" - "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/LibCInstallation.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig" - "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/WindowsSdk.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/Zir.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig" - "${CMAKE_SOURCE_DIR}/lib/std/zig/Zir.zig" "${CMAKE_SOURCE_DIR}/src/Air.zig" "${CMAKE_SOURCE_DIR}/src/Compilation.zig" "${CMAKE_SOURCE_DIR}/src/Compilation/Config.zig" @@ -570,7 +572,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig" "${CMAKE_SOURCE_DIR}/src/glibc.zig" "${CMAKE_SOURCE_DIR}/src/introspect.zig" - "${CMAKE_SOURCE_DIR}/src/libc_installation.zig" "${CMAKE_SOURCE_DIR}/src/libcxx.zig" "${CMAKE_SOURCE_DIR}/src/libtsan.zig" "${CMAKE_SOURCE_DIR}/src/libunwind.zig" @@ -645,7 +646,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/translate_c/ast.zig" "${CMAKE_SOURCE_DIR}/src/type.zig" "${CMAKE_SOURCE_DIR}/src/wasi_libc.zig" - "${CMAKE_SOURCE_DIR}/src/windows_sdk.zig" "${CMAKE_SOURCE_DIR}/src/stubs/aro_builtins.zig" "${CMAKE_SOURCE_DIR}/src/stubs/aro_names.zig" ) diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig @@ -0,0 +1,137 @@ +const std = @import("std"); +const mem = std.mem; +const io = std.io; +const LibCInstallation = std.zig.LibCInstallation; + +const usage_libc = + \\Usage: zig libc + \\ + \\ Detect the native libc installation and print the resulting + \\ paths to stdout. You can save this into a file and then edit + \\ the paths to create a cross compilation libc kit. Then you + \\ can pass `--libc [file]` for Zig to use it. + \\ + \\Usage: zig libc [paths_file] + \\ + \\ Parse a libc installation text file and validate it. + \\ + \\Options: + \\ -h, --help Print this help and exit + \\ -target [name] <arch><sub>-<os>-<abi> see the targets command + \\ -includes Print the libc include directories for the target + \\ +; + +pub fn main() !void { + var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + const gpa = arena; + + const args = try std.process.argsAlloc(arena); + const zig_lib_directory = args[1]; + + var input_file: ?[]const u8 = null; + var target_arch_os_abi: []const u8 = "native"; + var print_includes: bool = false; + { + var i: usize = 2; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (mem.startsWith(u8, arg, "-")) { + if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { + const stdout = std.io.getStdOut().writer(); + try stdout.writeAll(usage_libc); + return std.process.cleanExit(); + } else if (mem.eql(u8, arg, "-target")) { + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); + i += 1; + target_arch_os_abi = args[i]; + } else if (mem.eql(u8, arg, "-includes")) { + print_includes = true; + } else { + fatal("unrecognized parameter: '{s}'", .{arg}); + } + } else if (input_file != null) { + fatal("unexpected extra parameter: '{s}'", .{arg}); + } else { + input_file = arg; + } + } + } + + const target_query = std.zig.parseTargetQueryOrReportFatalError(gpa, .{ + .arch_os_abi = target_arch_os_abi, + }); + const target = std.zig.resolveTargetQueryOrFatal(target_query); + + if (print_includes) { + const libc_installation: ?*LibCInstallation = libc: { + if (input_file) |libc_file| { + const libc = try arena.create(LibCInstallation); + libc.* = LibCInstallation.parse(arena, libc_file, target) catch |err| { + fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) }); + }; + break :libc libc; + } else { + break :libc null; + } + }; + + const is_native_abi = target_query.isNativeAbi(); + + const libc_dirs = std.zig.LibCDirs.detect( + arena, + zig_lib_directory, + target, + is_native_abi, + true, + libc_installation, + ) catch |err| { + const zig_target = try target.zigTriple(arena); + fatal("unable to detect libc for target {s}: {s}", .{ zig_target, @errorName(err) }); + }; + + if (libc_dirs.libc_include_dir_list.len == 0) { + const zig_target = try target.zigTriple(arena); + fatal("no include dirs detected for target {s}", .{zig_target}); + } + + var bw = std.io.bufferedWriter(std.io.getStdOut().writer()); + var writer = bw.writer(); + for (libc_dirs.libc_include_dir_list) |include_dir| { + try writer.writeAll(include_dir); + try writer.writeByte('\n'); + } + try bw.flush(); + return std.process.cleanExit(); + } + + if (input_file) |libc_file| { + var libc = LibCInstallation.parse(gpa, libc_file, target) catch |err| { + fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) }); + }; + defer libc.deinit(gpa); + } else { + if (!target_query.isNative()) { + fatal("unable to detect libc for non-native target", .{}); + } + var libc = LibCInstallation.findNative(.{ + .allocator = gpa, + .verbose = true, + .target = target, + }) catch |err| { + fatal("unable to detect native libc: {s}", .{@errorName(err)}); + }; + defer libc.deinit(gpa); + + var bw = std.io.bufferedWriter(std.io.getStdOut().writer()); + try libc.render(bw.writer()); + try bw.flush(); + } +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.log.err(format, args); + std.process.exit(1); +} diff --git a/lib/std/Target.zig b/lib/std/Target.zig @@ -2755,6 +2755,22 @@ fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool { } } +pub fn osArchName(target: std.Target) [:0]const u8 { + return switch (target.os.tag) { + .linux => switch (target.cpu.arch) { + .arm, .armeb, .thumb, .thumbeb => "arm", + .aarch64, .aarch64_be, .aarch64_32 => "aarch64", + .mips, .mipsel, .mips64, .mips64el => "mips", + .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", + .riscv32, .riscv64 => "riscv", + .sparc, .sparcel, .sparc64 => "sparc", + .x86, .x86_64 => "x86", + else => @tagName(target.cpu.arch), + }, + else => @tagName(target.cpu.arch), + }; +} + const Target = @This(); const std = @import("std.zig"); const builtin = @import("builtin"); diff --git a/lib/std/zig.zig b/lib/std/zig.zig @@ -14,6 +14,10 @@ pub const system = @import("zig/system.zig"); pub const CrossTarget = std.Target.Query; 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 target = @import("zig/target.zig"); // Character literal parsing pub const ParsedCharLiteral = string_literal.ParsedCharLiteral; @@ -142,10 +146,10 @@ pub const BinNameOptions = struct { /// 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; - const target = options.target; - switch (target.ofmt) { + const t = options.target; + switch (t.ofmt) { .coff => switch (options.output_mode) { - .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }), + .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }), .Lib => { const suffix = switch (options.link_mode orelse .Static) { .Static => ".lib", @@ -160,16 +164,16 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe .Lib => { switch (options.link_mode orelse .Static) { .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ - target.libPrefix(), root_name, + t.libPrefix(), root_name, }), .Dynamic => { if (options.version) |ver| { return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{ - target.libPrefix(), root_name, ver.major, ver.minor, ver.patch, + t.libPrefix(), root_name, ver.major, ver.minor, ver.patch, }); } else { return std.fmt.allocPrint(allocator, "{s}{s}.so", .{ - target.libPrefix(), root_name, + t.libPrefix(), root_name, }); } }, @@ -182,16 +186,16 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe .Lib => { switch (options.link_mode orelse .Static) { .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ - target.libPrefix(), root_name, + t.libPrefix(), root_name, }), .Dynamic => { if (options.version) |ver| { return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{ - target.libPrefix(), root_name, ver.major, ver.minor, ver.patch, + t.libPrefix(), root_name, ver.major, ver.minor, ver.patch, }); } else { return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{ - target.libPrefix(), root_name, + t.libPrefix(), root_name, }); } }, @@ -200,11 +204,11 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe .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, target.exeFileExt() }), + .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }), .Lib => { switch (options.link_mode orelse .Static) { .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ - target.libPrefix(), root_name, + t.libPrefix(), root_name, }), .Dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}), } @@ -218,10 +222,10 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe .plan9 => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ - root_name, target.ofmt.fileExt(target.cpu.arch), + root_name, t.ofmt.fileExt(t.cpu.arch), }), .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ - target.libPrefix(), root_name, + t.libPrefix(), root_name, }), }, .nvptx => return std.fmt.allocPrint(allocator, "{s}.ptx", .{root_name}), @@ -900,15 +904,117 @@ pub fn putAstErrorsIntoBundle( try wip_errors.addZirErrorMessages(zir, tree, tree.source, path); } +pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target { + return std.zig.system.resolveTargetQuery(target_query) catch |err| + fatal("unable to resolve target: {s}", .{@errorName(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).init(allocator); + defer help_text.deinit(); + for (diags.arch.?.allCpuModels()) |cpu| { + help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help; + } + std.log.info("available CPUs for architecture '{s}':\n{s}", .{ + @tagName(diags.arch.?), help_text.items, + }); + } + fatal("unknown CPU: '{s}'", .{diags.cpu_name.?}); + }, + error.UnknownCpuFeature => { + help: { + var help_text = std.ArrayList(u8).init(allocator); + defer help_text.deinit(); + for (diags.arch.?.allFeaturesList()) |feature| { + help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; + } + std.log.info("available CPU features for architecture '{s}':\n{s}", .{ + @tagName(diags.arch.?), help_text.items, + }); + } + fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?}); + }, + error.UnknownObjectFormat => { + help: { + var help_text = std.ArrayList(u8).init(allocator); + defer help_text.deinit(); + inline for (@typeInfo(std.Target.ObjectFormat).Enum.fields) |field| { + help_text.writer().print(" {s}\n", .{field.name}) catch break :help; + } + std.log.info("available object formats:\n{s}", .{help_text.items}); + } + fatal("unknown object format: '{s}'", .{opts.object_format.?}); + }, + else => |e| fatal("unable to parse target query '{s}': {s}", .{ + opts.arch_os_abi, @errorName(e), + }), + }; +} + +pub fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.log.err(format, args); + std.process.exit(1); +} + +/// 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_LIB_DIR, + ZIG_LIBC, + ZIG_BUILD_RUNNER, + ZIG_VERBOSE_LINK, + ZIG_VERBOSE_CC, + ZIG_BTRFS_WORKAROUND, + ZIG_DEBUG_CMD, + CC, + NO_COLOR, + XDG_CACHE_HOME, + HOME, + + pub fn isSet(comptime ev: EnvVar) bool { + return std.process.hasEnvVarConstant(@tagName(ev)); + } + + pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 { + if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| { + return value; + } else |err| switch (err) { + error.EnvironmentVariableNotFound => return null, + else => |e| return e, + } + } + + pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 { + return std.os.getenvZ(@tagName(ev)); + } +}; + test { _ = Ast; _ = AstRlAnnotate; _ = BuiltinFn; _ = Client; _ = ErrorBundle; + _ = LibCDirs; + _ = LibCInstallation; _ = Server; + _ = WindowsSdk; _ = number_literal; _ = primitives; _ = string_literal; _ = system; + _ = target; } diff --git a/lib/std/zig/LibCDirs.zig b/lib/std/zig/LibCDirs.zig @@ -0,0 +1,281 @@ +libc_include_dir_list: []const []const u8, +libc_installation: ?*const LibCInstallation, +libc_framework_dir_list: []const []const u8, +sysroot: ?[]const u8, +darwin_sdk_layout: ?DarwinSdkLayout, + +/// The filesystem layout of darwin SDK elements. +pub const DarwinSdkLayout = enum { + /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }. + sdk, + /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }. + vendored, +}; + +pub fn detect( + arena: Allocator, + zig_lib_dir: []const u8, + target: std.Target, + is_native_abi: bool, + link_libc: bool, + libc_installation: ?*const LibCInstallation, +) !LibCDirs { + if (!link_libc) { + return .{ + .libc_include_dir_list = &[0][]u8{}, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + .darwin_sdk_layout = null, + }; + } + + if (libc_installation) |lci| { + return detectFromInstallation(arena, target, lci); + } + + // If linking system libraries and targeting the native abi, default to + // using the system libc installation. + if (is_native_abi and !target.isMinGW()) { + const libc = try arena.create(LibCInstallation); + libc.* = LibCInstallation.findNative(.{ .allocator = arena, .target = target }) catch |err| switch (err) { + error.CCompilerExitCode, + error.CCompilerCrashed, + error.CCompilerCannotFindHeaders, + error.UnableToSpawnCCompiler, + error.DarwinSdkNotFound, + => |e| { + // We tried to integrate with the native system C compiler, + // however, it is not installed. So we must rely on our bundled + // libc files. + if (std.zig.target.canBuildLibC(target)) { + return detectFromBuilding(arena, zig_lib_dir, target); + } + return e; + }, + else => |e| return e, + }; + return detectFromInstallation(arena, target, libc); + } + + // If not linking system libraries, build and provide our own libc by + // default if possible. + if (std.zig.target.canBuildLibC(target)) { + return detectFromBuilding(arena, zig_lib_dir, target); + } + + // If zig can't build the libc for the target and we are targeting the + // native abi, fall back to using the system libc installation. + // On windows, instead of the native (mingw) abi, we want to check + // for the MSVC abi as a fallback. + const use_system_abi = if (builtin.os.tag == .windows) + target.abi == .msvc + else + is_native_abi; + + if (use_system_abi) { + const libc = try arena.create(LibCInstallation); + libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true, .target = target }); + return detectFromInstallation(arena, target, libc); + } + + return .{ + .libc_include_dir_list = &[0][]u8{}, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + .darwin_sdk_layout = null, + }; +} + +fn detectFromInstallation(arena: Allocator, target: std.Target, lci: *const LibCInstallation) !LibCDirs { + var list = try std.ArrayList([]const u8).initCapacity(arena, 5); + var framework_list = std.ArrayList([]const u8).init(arena); + + list.appendAssumeCapacity(lci.include_dir.?); + + const is_redundant = std.mem.eql(u8, lci.sys_include_dir.?, lci.include_dir.?); + if (!is_redundant) list.appendAssumeCapacity(lci.sys_include_dir.?); + + if (target.os.tag == .windows) { + if (std.fs.path.dirname(lci.sys_include_dir.?)) |sys_include_dir_parent| { + // This include path will only exist when the optional "Desktop development with C++" + // is installed. It contains headers, .rc files, and resources. It is especially + // necessary when working with Windows resources. + const atlmfc_dir = try std.fs.path.join(arena, &[_][]const u8{ sys_include_dir_parent, "atlmfc", "include" }); + list.appendAssumeCapacity(atlmfc_dir); + } + if (std.fs.path.dirname(lci.include_dir.?)) |include_dir_parent| { + const um_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "um" }); + list.appendAssumeCapacity(um_dir); + + const shared_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "shared" }); + list.appendAssumeCapacity(shared_dir); + } + } + if (target.os.tag == .haiku) { + const include_dir_path = lci.include_dir orelse return error.LibCInstallationNotAvailable; + const os_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_path, "os" }); + list.appendAssumeCapacity(os_dir); + // Errors.h + const os_support_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_path, "os/support" }); + list.appendAssumeCapacity(os_support_dir); + + const config_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_path, "config" }); + list.appendAssumeCapacity(config_dir); + } + + var sysroot: ?[]const u8 = null; + + if (target.isDarwin()) d: { + const down1 = std.fs.path.dirname(lci.sys_include_dir.?) orelse break :d; + const down2 = std.fs.path.dirname(down1) orelse break :d; + try framework_list.append(try std.fs.path.join(arena, &.{ down2, "System", "Library", "Frameworks" })); + sysroot = down2; + } + + return .{ + .libc_include_dir_list = list.items, + .libc_installation = lci, + .libc_framework_dir_list = framework_list.items, + .sysroot = sysroot, + .darwin_sdk_layout = if (sysroot == null) null else .sdk, + }; +} + +pub fn detectFromBuilding( + arena: Allocator, + zig_lib_dir: []const u8, + target: std.Target, +) !LibCDirs { + const s = std.fs.path.sep_str; + + if (target.isDarwin()) { + const list = try arena.alloc([]const u8, 1); + list[0] = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-macos-any", + .{zig_lib_dir}, + ); + return .{ + .libc_include_dir_list = list, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + .darwin_sdk_layout = .vendored, + }; + } + + const generic_name = libCGenericName(target); + // Some architectures are handled by the same set of headers. + const arch_name = if (target.abi.isMusl()) + std.zig.target.muslArchNameHeaders(target.cpu.arch) + else if (target.cpu.arch.isThumb()) + // ARM headers are valid for Thumb too. + switch (target.cpu.arch) { + .thumb => "arm", + .thumbeb => "armeb", + else => unreachable, + } + else + @tagName(target.cpu.arch); + const os_name = @tagName(target.os.tag); + // Musl's headers are ABI-agnostic and so they all have the "musl" ABI name. + const abi_name = if (target.abi.isMusl()) "musl" else @tagName(target.abi); + const arch_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", + .{ zig_lib_dir, arch_name, os_name, abi_name }, + ); + const generic_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{s}", + .{ zig_lib_dir, generic_name }, + ); + const generic_arch_name = target.osArchName(); + const arch_os_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-any", + .{ zig_lib_dir, generic_arch_name, os_name }, + ); + const generic_os_include_dir = try std.fmt.allocPrint( + arena, + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any", + .{ zig_lib_dir, os_name }, + ); + + const list = try arena.alloc([]const u8, 4); + list[0] = arch_include_dir; + list[1] = generic_include_dir; + list[2] = arch_os_include_dir; + list[3] = generic_os_include_dir; + + return .{ + .libc_include_dir_list = list, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + .darwin_sdk_layout = .vendored, + }; +} + +fn libCGenericName(target: std.Target) [:0]const u8 { + switch (target.os.tag) { + .windows => return "mingw", + .macos, .ios, .tvos, .watchos => return "darwin", + else => {}, + } + switch (target.abi) { + .gnu, + .gnuabin32, + .gnuabi64, + .gnueabi, + .gnueabihf, + .gnuf32, + .gnuf64, + .gnusf, + .gnux32, + .gnuilp32, + => return "glibc", + .musl, + .musleabi, + .musleabihf, + .muslx32, + .none, + => return "musl", + .code16, + .eabi, + .eabihf, + .android, + .msvc, + .itanium, + .cygnus, + .coreclr, + .simulator, + .macabi, + => unreachable, + + .pixel, + .vertex, + .geometry, + .hull, + .domain, + .compute, + .library, + .raygeneration, + .intersection, + .anyhit, + .closesthit, + .miss, + .callable, + .mesh, + .amplification, + => unreachable, + } +} + +const LibCDirs = @This(); +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const LibCInstallation = std.zig.LibCInstallation; +const Allocator = std.mem.Allocator; diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig @@ -0,0 +1,709 @@ +//! See the render function implementation for documentation of the fields. + +include_dir: ?[]const u8 = null, +sys_include_dir: ?[]const u8 = null, +crt_dir: ?[]const u8 = null, +msvc_lib_dir: ?[]const u8 = null, +kernel32_lib_dir: ?[]const u8 = null, +gcc_dir: ?[]const u8 = null, + +pub const FindError = error{ + OutOfMemory, + FileSystem, + UnableToSpawnCCompiler, + CCompilerExitCode, + CCompilerCrashed, + CCompilerCannotFindHeaders, + LibCRuntimeNotFound, + LibCStdLibHeaderNotFound, + LibCKernel32LibNotFound, + UnsupportedArchitecture, + WindowsSdkNotFound, + DarwinSdkNotFound, + ZigIsTheCCompiler, +}; + +pub fn parse( + allocator: Allocator, + libc_file: []const u8, + target: std.Target, +) !LibCInstallation { + var self: LibCInstallation = .{}; + + const fields = std.meta.fields(LibCInstallation); + const FoundKey = struct { + found: bool, + allocated: ?[:0]u8, + }; + var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** fields.len; + errdefer { + self = .{}; + for (found_keys) |found_key| { + if (found_key.allocated) |s| allocator.free(s); + } + } + + const contents = try std.fs.cwd().readFileAlloc(allocator, libc_file, std.math.maxInt(usize)); + defer allocator.free(contents); + + var it = std.mem.tokenizeScalar(u8, contents, '\n'); + while (it.next()) |line| { + if (line.len == 0 or line[0] == '#') continue; + var line_it = std.mem.splitScalar(u8, line, '='); + const name = line_it.first(); + const value = line_it.rest(); + inline for (fields, 0..) |field, i| { + if (std.mem.eql(u8, name, field.name)) { + found_keys[i].found = true; + if (value.len == 0) { + @field(self, field.name) = null; + } else { + found_keys[i].allocated = try allocator.dupeZ(u8, value); + @field(self, field.name) = found_keys[i].allocated; + } + break; + } + } + } + inline for (fields, 0..) |field, i| { + if (!found_keys[i].found) { + log.err("missing field: {s}\n", .{field.name}); + return error.ParseError; + } + } + if (self.include_dir == null) { + log.err("include_dir may not be empty\n", .{}); + return error.ParseError; + } + if (self.sys_include_dir == null) { + log.err("sys_include_dir may not be empty\n", .{}); + return error.ParseError; + } + + const os_tag = target.os.tag; + if (self.crt_dir == null and !target.isDarwin()) { + log.err("crt_dir may not be empty for {s}\n", .{@tagName(os_tag)}); + return error.ParseError; + } + + if (self.msvc_lib_dir == null and os_tag == .windows and target.abi == .msvc) { + log.err("msvc_lib_dir may not be empty for {s}-{s}\n", .{ + @tagName(os_tag), + @tagName(target.abi), + }); + return error.ParseError; + } + if (self.kernel32_lib_dir == null and os_tag == .windows and target.abi == .msvc) { + log.err("kernel32_lib_dir may not be empty for {s}-{s}\n", .{ + @tagName(os_tag), + @tagName(target.abi), + }); + return error.ParseError; + } + + if (self.gcc_dir == null and os_tag == .haiku) { + log.err("gcc_dir may not be empty for {s}\n", .{@tagName(os_tag)}); + return error.ParseError; + } + + return self; +} + +pub fn render(self: LibCInstallation, out: anytype) !void { + @setEvalBranchQuota(4000); + const include_dir = self.include_dir orelse ""; + const sys_include_dir = self.sys_include_dir orelse ""; + const crt_dir = self.crt_dir orelse ""; + const msvc_lib_dir = self.msvc_lib_dir orelse ""; + const kernel32_lib_dir = self.kernel32_lib_dir orelse ""; + const gcc_dir = self.gcc_dir orelse ""; + + try out.print( + \\# The directory that contains `stdlib.h`. + \\# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null` + \\include_dir={s} + \\ + \\# The system-specific include directory. May be the same as `include_dir`. + \\# On Windows it's the directory that includes `vcruntime.h`. + \\# On POSIX it's the directory that includes `sys/errno.h`. + \\sys_include_dir={s} + \\ + \\# The directory that contains `crt1.o` or `crt2.o`. + \\# On POSIX, can be found with `cc -print-file-name=crt1.o`. + \\# Not needed when targeting MacOS. + \\crt_dir={s} + \\ + \\# The directory that contains `vcruntime.lib`. + \\# Only needed when targeting MSVC on Windows. + \\msvc_lib_dir={s} + \\ + \\# The directory that contains `kernel32.lib`. + \\# Only needed when targeting MSVC on Windows. + \\kernel32_lib_dir={s} + \\ + \\# The directory that contains `crtbeginS.o` and `crtendS.o` + \\# Only needed when targeting Haiku. + \\gcc_dir={s} + \\ + , .{ + include_dir, + sys_include_dir, + crt_dir, + msvc_lib_dir, + kernel32_lib_dir, + gcc_dir, + }); +} + +pub const FindNativeOptions = struct { + allocator: Allocator, + target: std.Target, + + /// If enabled, will print human-friendly errors to stderr. + verbose: bool = false, +}; + +/// Finds the default, native libc. +pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { + var self: LibCInstallation = .{}; + + if (is_darwin) { + if (!std.zig.system.darwin.isSdkInstalled(args.allocator)) + return error.DarwinSdkNotFound; + const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse + return error.DarwinSdkNotFound; + defer args.allocator.free(sdk); + + self.include_dir = try fs.path.join(args.allocator, &.{ + sdk, "usr/include", + }); + self.sys_include_dir = try fs.path.join(args.allocator, &.{ + sdk, "usr/include", + }); + return self; + } else if (is_windows) { + var sdk = std.zig.WindowsSdk.find(args.allocator) catch |err| switch (err) { + error.NotFound => return error.WindowsSdkNotFound, + error.PathTooLong => return error.WindowsSdkNotFound, + error.OutOfMemory => return error.OutOfMemory, + }; + defer sdk.free(args.allocator); + + try self.findNativeMsvcIncludeDir(args, &sdk); + try self.findNativeMsvcLibDir(args, &sdk); + try self.findNativeKernel32LibDir(args, &sdk); + try self.findNativeIncludeDirWindows(args, &sdk); + try self.findNativeCrtDirWindows(args, &sdk); + } else if (is_haiku) { + try self.findNativeIncludeDirPosix(args); + try self.findNativeCrtBeginDirHaiku(args); + self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib"); + } else if (builtin.target.os.tag.isSolarish()) { + // There is only one libc, and its headers/libraries are always in the same spot. + self.include_dir = try args.allocator.dupeZ(u8, "/usr/include"); + self.sys_include_dir = try args.allocator.dupeZ(u8, "/usr/include"); + self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib/64"); + } else if (std.process.can_spawn) { + try self.findNativeIncludeDirPosix(args); + switch (builtin.target.os.tag) { + .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib"), + .linux => try self.findNativeCrtDirPosix(args), + else => {}, + } + } else { + return error.LibCRuntimeNotFound; + } + return self; +} + +/// Must be the same allocator passed to `parse` or `findNative`. +pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { + const fields = std.meta.fields(LibCInstallation); + inline for (fields) |field| { + if (@field(self, field.name)) |payload| { + allocator.free(payload); + } + } + self.* = undefined; +} + +fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { + const allocator = args.allocator; + + // Detect infinite loops. + var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { + error.Unexpected => unreachable, // WASI-only + else => |e| return e, + }; + defer env_map.deinit(); + const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { + if (std.mem.eql(u8, phase, "1")) { + try env_map.put(inf_loop_env_key, "2"); + break :blk true; + } else { + return error.ZigIsTheCCompiler; + } + } else blk: { + try env_map.put(inf_loop_env_key, "1"); + break :blk false; + }; + + const dev_null = if (is_windows) "nul" else "/dev/null"; + + var argv = std.ArrayList([]const u8).init(allocator); + defer argv.deinit(); + + try appendCcExe(&argv, skip_cc_env_var); + try argv.appendSlice(&.{ + "-E", + "-Wp,-v", + "-xc", + dev_null, + }); + + const run_res = std.ChildProcess.run(.{ + .allocator = allocator, + .argv = argv.items, + .max_output_bytes = 1024 * 1024, + .env_map = &env_map, + // Some C compilers, such as Clang, are known to rely on argv[0] to find the path + // to their own executable, without even bothering to resolve PATH. This results in the message: + // error: unable to execute command: Executable "" doesn't exist! + // So we use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, + }) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + printVerboseInvocation(argv.items, null, args.verbose, null); + return error.UnableToSpawnCCompiler; + }, + }; + defer { + allocator.free(run_res.stdout); + allocator.free(run_res.stderr); + } + switch (run_res.term) { + .Exited => |code| if (code != 0) { + printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr); + return error.CCompilerExitCode; + }, + else => { + printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr); + return error.CCompilerCrashed; + }, + } + + var it = std.mem.tokenizeAny(u8, run_res.stderr, "\n\r"); + var search_paths = std.ArrayList([]const u8).init(allocator); + defer search_paths.deinit(); + while (it.next()) |line| { + if (line.len != 0 and line[0] == ' ') { + try search_paths.append(line); + } + } + if (search_paths.items.len == 0) { + return error.CCompilerCannotFindHeaders; + } + + const include_dir_example_file = if (is_haiku) "posix/stdlib.h" else "stdlib.h"; + const sys_include_dir_example_file = if (is_windows) + "sys\\types.h" + else if (is_haiku) + "errno.h" + else + "sys/errno.h"; + + var path_i: usize = 0; + while (path_i < search_paths.items.len) : (path_i += 1) { + // search in reverse order + const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1]; + const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " "); + var search_dir = fs.cwd().openDir(search_path, .{}) catch |err| switch (err) { + error.FileNotFound, + error.NotDir, + error.NoDevice, + => continue, + + else => return error.FileSystem, + }; + defer search_dir.close(); + + if (self.include_dir == null) { + if (search_dir.accessZ(include_dir_example_file, .{})) |_| { + self.include_dir = try allocator.dupeZ(u8, search_path); + } else |err| switch (err) { + error.FileNotFound => {}, + else => return error.FileSystem, + } + } + + if (self.sys_include_dir == null) { + if (search_dir.accessZ(sys_include_dir_example_file, .{})) |_| { + self.sys_include_dir = try allocator.dupeZ(u8, search_path); + } else |err| switch (err) { + error.FileNotFound => {}, + else => return error.FileSystem, + } + } + + if (self.include_dir != null and self.sys_include_dir != null) { + // Success. + return; + } + } + + return error.LibCStdLibHeaderNotFound; +} + +fn findNativeIncludeDirWindows( + self: *LibCInstallation, + args: FindNativeOptions, + sdk: *std.zig.WindowsSdk, +) FindError!void { + const allocator = args.allocator; + + var search_buf: [2]Search = undefined; + const searches = fillSearch(&search_buf, sdk); + + var result_buf = std.ArrayList(u8).init(allocator); + defer result_buf.deinit(); + + for (searches) |search| { + result_buf.shrinkAndFree(0); + try result_buf.writer().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version }); + + var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + error.FileNotFound, + error.NotDir, + error.NoDevice, + => continue, + + else => return error.FileSystem, + }; + defer dir.close(); + + dir.accessZ("stdlib.h", .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => return error.FileSystem, + }; + + self.include_dir = try result_buf.toOwnedSlice(); + return; + } + + return error.LibCStdLibHeaderNotFound; +} + +fn findNativeCrtDirWindows( + self: *LibCInstallation, + args: FindNativeOptions, + sdk: *std.zig.WindowsSdk, +) FindError!void { + const allocator = args.allocator; + + var search_buf: [2]Search = undefined; + const searches = fillSearch(&search_buf, sdk); + + var result_buf = std.ArrayList(u8).init(allocator); + defer result_buf.deinit(); + + const arch_sub_dir = switch (builtin.target.cpu.arch) { + .x86 => "x86", + .x86_64 => "x64", + .arm, .armeb => "arm", + .aarch64 => "arm64", + else => return error.UnsupportedArchitecture, + }; + + for (searches) |search| { + result_buf.shrinkAndFree(0); + try result_buf.writer().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir }); + + var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + error.FileNotFound, + error.NotDir, + error.NoDevice, + => continue, + + else => return error.FileSystem, + }; + defer dir.close(); + + dir.accessZ("ucrt.lib", .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => return error.FileSystem, + }; + + self.crt_dir = try result_buf.toOwnedSlice(); + return; + } + return error.LibCRuntimeNotFound; +} + +fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { + self.crt_dir = try ccPrintFileName(.{ + .allocator = args.allocator, + .search_basename = "crt1.o", + .want_dirname = .only_dir, + .verbose = args.verbose, + }); +} + +fn findNativeCrtBeginDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindError!void { + self.gcc_dir = try ccPrintFileName(.{ + .allocator = args.allocator, + .search_basename = "crtbeginS.o", + .want_dirname = .only_dir, + .verbose = args.verbose, + }); +} + +fn findNativeKernel32LibDir( + self: *LibCInstallation, + args: FindNativeOptions, + sdk: *std.zig.WindowsSdk, +) FindError!void { + const allocator = args.allocator; + + var search_buf: [2]Search = undefined; + const searches = fillSearch(&search_buf, sdk); + + var result_buf = std.ArrayList(u8).init(allocator); + defer result_buf.deinit(); + + const arch_sub_dir = switch (builtin.target.cpu.arch) { + .x86 => "x86", + .x86_64 => "x64", + .arm, .armeb => "arm", + .aarch64 => "arm64", + else => return error.UnsupportedArchitecture, + }; + + for (searches) |search| { + result_buf.shrinkAndFree(0); + const stream = result_buf.writer(); + try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ search.path, search.version, arch_sub_dir }); + + var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + error.FileNotFound, + error.NotDir, + error.NoDevice, + => continue, + + else => return error.FileSystem, + }; + defer dir.close(); + + dir.accessZ("kernel32.lib", .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => return error.FileSystem, + }; + + self.kernel32_lib_dir = try result_buf.toOwnedSlice(); + return; + } + return error.LibCKernel32LibNotFound; +} + +fn findNativeMsvcIncludeDir( + self: *LibCInstallation, + args: FindNativeOptions, + sdk: *std.zig.WindowsSdk, +) FindError!void { + const allocator = args.allocator; + + const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound; + const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound; + const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound; + + const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" }); + errdefer allocator.free(dir_path); + + var dir = fs.cwd().openDir(dir_path, .{}) catch |err| switch (err) { + error.FileNotFound, + error.NotDir, + error.NoDevice, + => return error.LibCStdLibHeaderNotFound, + + else => return error.FileSystem, + }; + defer dir.close(); + + dir.accessZ("vcruntime.h", .{}) catch |err| switch (err) { + error.FileNotFound => return error.LibCStdLibHeaderNotFound, + else => return error.FileSystem, + }; + + self.sys_include_dir = dir_path; +} + +fn findNativeMsvcLibDir( + self: *LibCInstallation, + args: FindNativeOptions, + sdk: *std.zig.WindowsSdk, +) FindError!void { + const allocator = args.allocator; + const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCRuntimeNotFound; + self.msvc_lib_dir = try allocator.dupe(u8, msvc_lib_dir); +} + +pub const CCPrintFileNameOptions = struct { + allocator: Allocator, + search_basename: []const u8, + want_dirname: enum { full_path, only_dir }, + verbose: bool = false, +}; + +/// caller owns returned memory +fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { + const allocator = args.allocator; + + // Detect infinite loops. + var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { + error.Unexpected => unreachable, // WASI-only + else => |e| return e, + }; + defer env_map.deinit(); + const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { + if (std.mem.eql(u8, phase, "1")) { + try env_map.put(inf_loop_env_key, "2"); + break :blk true; + } else { + return error.ZigIsTheCCompiler; + } + } else blk: { + try env_map.put(inf_loop_env_key, "1"); + break :blk false; + }; + + var argv = std.ArrayList([]const u8).init(allocator); + defer argv.deinit(); + + const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename}); + defer allocator.free(arg1); + + try appendCcExe(&argv, skip_cc_env_var); + try argv.append(arg1); + + const run_res = std.ChildProcess.run(.{ + .allocator = allocator, + .argv = argv.items, + .max_output_bytes = 1024 * 1024, + .env_map = &env_map, + // Some C compilers, such as Clang, are known to rely on argv[0] to find the path + // to their own executable, without even bothering to resolve PATH. This results in the message: + // error: unable to execute command: Executable "" doesn't exist! + // So we use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, + }) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.UnableToSpawnCCompiler, + }; + defer { + allocator.free(run_res.stdout); + allocator.free(run_res.stderr); + } + switch (run_res.term) { + .Exited => |code| if (code != 0) { + printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr); + return error.CCompilerExitCode; + }, + else => { + printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr); + return error.CCompilerCrashed; + }, + } + + var it = std.mem.tokenizeAny(u8, run_res.stdout, "\n\r"); + const line = it.next() orelse return error.LibCRuntimeNotFound; + // When this command fails, it returns exit code 0 and duplicates the input file name. + // So we detect failure by checking if the output matches exactly the input. + if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound; + switch (args.want_dirname) { + .full_path => return allocator.dupeZ(u8, line), + .only_dir => { + const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound; + return allocator.dupeZ(u8, dirname); + }, + } +} + +fn printVerboseInvocation( + argv: []const []const u8, + search_basename: ?[]const u8, + verbose: bool, + stderr: ?[]const u8, +) void { + if (!verbose) return; + + if (search_basename) |s| { + std.debug.print("Zig attempted to find the file '{s}' by executing this command:\n", .{s}); + } else { + std.debug.print("Zig attempted to find the path to native system libc headers by executing this command:\n", .{}); + } + for (argv, 0..) |arg, i| { + if (i != 0) std.debug.print(" ", .{}); + std.debug.print("{s}", .{arg}); + } + std.debug.print("\n", .{}); + if (stderr) |s| { + std.debug.print("Output:\n==========\n{s}\n==========\n", .{s}); + } +} + +const Search = struct { + path: []const u8, + version: []const u8, +}; + +fn fillSearch(search_buf: *[2]Search, sdk: *std.zig.WindowsSdk) []Search { + var search_end: usize = 0; + if (sdk.windows10sdk) |windows10sdk| { + search_buf[search_end] = .{ + .path = windows10sdk.path, + .version = windows10sdk.version, + }; + search_end += 1; + } + if (sdk.windows81sdk) |windows81sdk| { + search_buf[search_end] = .{ + .path = windows81sdk.path, + .version = windows81sdk.version, + }; + search_end += 1; + } + return search_buf[0..search_end]; +} + +const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS"; + +fn appendCcExe(args: *std.ArrayList([]const u8), skip_cc_env_var: bool) !void { + const default_cc_exe = if (is_windows) "cc.exe" else "cc"; + try args.ensureUnusedCapacity(1); + if (skip_cc_env_var) { + args.appendAssumeCapacity(default_cc_exe); + return; + } + const cc_env_var = std.zig.EnvVar.CC.getPosix() orelse { + args.appendAssumeCapacity(default_cc_exe); + return; + }; + // Respect space-separated flags to the C compiler. + var it = std.mem.tokenizeScalar(u8, cc_env_var, ' '); + while (it.next()) |arg| { + try args.append(arg); + } +} + +const LibCInstallation = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const Target = std.Target; +const fs = std.fs; +const Allocator = std.mem.Allocator; + +const is_darwin = builtin.target.isDarwin(); +const is_windows = builtin.target.os.tag == .windows; +const is_haiku = builtin.target.os.tag == .haiku; + +const log = std.log.scoped(.libc_installation); diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig @@ -0,0 +1,964 @@ +windows10sdk: ?Windows10Sdk, +windows81sdk: ?Windows81Sdk, +msvc_lib_dir: ?[]const u8, + +const WindowsSdk = @This(); +const std = @import("std"); +const builtin = @import("builtin"); + +const windows = std.os.windows; +const RRF = windows.advapi32.RRF; + +const WINDOWS_KIT_REG_KEY = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots"; + +// https://learn.microsoft.com/en-us/windows/win32/msi/productversion +const version_major_minor_max_length = "255.255".len; +// note(bratishkaerik): i think ProductVersion in registry (created by Visual Studio installer) also follows this rule +const product_version_max_length = version_major_minor_max_length + ".65535".len; + +/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. +/// Caller owns the result's fields. +/// After finishing work, call `free(allocator)`. +pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { + if (builtin.os.tag != .windows) return error.NotFound; + + //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed + const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) { + error.KeyNotFound => return error.NotFound, + }; + defer roots_key.closeKey(); + + const windows10sdk: ?Windows10Sdk = blk: { + const windows10sdk = Windows10Sdk.find(allocator) catch |err| switch (err) { + error.Windows10SdkNotFound, + error.PathTooLong, + error.VersionTooLong, + => break :blk null, + error.OutOfMemory => return error.OutOfMemory, + }; + const is_valid_version = windows10sdk.isValidVersion(); + if (!is_valid_version) break :blk null; + break :blk windows10sdk; + }; + errdefer if (windows10sdk) |*w| w.free(allocator); + + const windows81sdk: ?Windows81Sdk = blk: { + const windows81sdk = Windows81Sdk.find(allocator, &roots_key) catch |err| switch (err) { + error.Windows81SdkNotFound => break :blk null, + error.PathTooLong => break :blk null, + error.VersionTooLong => break :blk null, + error.OutOfMemory => return error.OutOfMemory, + }; + // no check + break :blk windows81sdk; + }; + errdefer if (windows81sdk) |*w| w.free(allocator); + + const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) { + error.MsvcLibDirNotFound => null, + error.OutOfMemory => return error.OutOfMemory, + }; + errdefer allocator.free(msvc_lib_dir); + + return WindowsSdk{ + .windows10sdk = windows10sdk, + .windows81sdk = windows81sdk, + .msvc_lib_dir = msvc_lib_dir, + }; +} + +pub fn free(self: *const WindowsSdk, allocator: std.mem.Allocator) void { + if (self.windows10sdk) |*w10sdk| { + w10sdk.free(allocator); + } + if (self.windows81sdk) |*w81sdk| { + w81sdk.free(allocator); + } + if (self.msvc_lib_dir) |msvc_lib_dir| { + allocator.free(msvc_lib_dir); + } +} + +/// Iterates via `iterator` and collects all folders with names starting with `optional_prefix` +/// and similar to SemVer. Returns slice of folder names sorted in descending order. +/// Caller owns result. +fn iterateAndFilterBySemVer( + iterator: *std.fs.Dir.Iterator, + allocator: std.mem.Allocator, + comptime optional_prefix: ?[]const u8, +) error{ OutOfMemory, VersionNotFound }![][]const u8 { + var dirs_filtered_list = std.ArrayList([]const u8).init(allocator); + errdefer { + for (dirs_filtered_list.items) |filtered_dir| allocator.free(filtered_dir); + dirs_filtered_list.deinit(); + } + + var normalized_name_buf: [std.fs.MAX_NAME_BYTES + ".0+build.0".len]u8 = undefined; + var normalized_name_fbs = std.io.fixedBufferStream(&normalized_name_buf); + const normalized_name_w = normalized_name_fbs.writer(); + iterate_folder: while (true) : (normalized_name_fbs.reset()) { + const maybe_entry = iterator.next() catch continue :iterate_folder; + const entry = maybe_entry orelse break :iterate_folder; + + if (entry.kind != .directory) + continue :iterate_folder; + + // invalidated on next iteration + const subfolder_name = blk: { + if (comptime optional_prefix) |prefix| { + if (!std.mem.startsWith(u8, entry.name, prefix)) continue :iterate_folder; + break :blk entry.name[prefix.len..]; + } else break :blk entry.name; + }; + + { // check if subfolder name looks similar to SemVer + switch (std.mem.count(u8, subfolder_name, ".")) { + 0 => normalized_name_w.print("{s}.0.0+build.0", .{subfolder_name}) catch unreachable, // 17 => 17.0.0+build.0 + 1 => if (std.mem.indexOfScalar(u8, subfolder_name, '_')) |underscore_pos| blk: { // 17.0_9e9cbb98 => 17.0.1+build.9e9cbb98 + var subfolder_name_tmp_copy_buf: [std.fs.MAX_NAME_BYTES]u8 = undefined; + const subfolder_name_tmp_copy = subfolder_name_tmp_copy_buf[0..subfolder_name.len]; + @memcpy(subfolder_name_tmp_copy, subfolder_name); + + subfolder_name_tmp_copy[underscore_pos] = '.'; // 17.0_9e9cbb98 => 17.0.9e9cbb98 + var subfolder_name_parts = std.mem.splitScalar(u8, subfolder_name_tmp_copy, '.'); // [ 17, 0, 9e9cbb98 ] + + const first = subfolder_name_parts.first(); // 17 + const second = subfolder_name_parts.next().?; // 0 + const third = subfolder_name_parts.rest(); // 9e9cbb98 + + break :blk normalized_name_w.print("{s}.{s}.1+build.{s}", .{ first, second, third }) catch unreachable; // [ 17, 0, 9e9cbb98 ] => 17.0.1+build.9e9cbb98 + } else normalized_name_w.print("{s}.0+build.0", .{subfolder_name}) catch unreachable, // 17.0 => 17.0.0+build.0 + else => normalized_name_w.print("{s}+build.0", .{subfolder_name}) catch unreachable, // 17.0.0 => 17.0.0+build.0 + } + const subfolder_name_normalized: []const u8 = normalized_name_fbs.getWritten(); + const sem_ver = std.SemanticVersion.parse(subfolder_name_normalized); + _ = sem_ver catch continue :iterate_folder; + } + // entry.name passed check + + const subfolder_name_allocated = try allocator.dupe(u8, subfolder_name); + errdefer allocator.free(subfolder_name_allocated); + try dirs_filtered_list.append(subfolder_name_allocated); + } + + const dirs_filtered_slice = try dirs_filtered_list.toOwnedSlice(); + // Keep in mind that order of these names is not guaranteed by Windows, + // so we cannot just reverse or "while (popOrNull())" this ArrayList. + std.mem.sortUnstable([]const u8, dirs_filtered_slice, {}, struct { + fn desc(_: void, lhs: []const u8, rhs: []const u8) bool { + return std.mem.order(u8, lhs, rhs) == .gt; + } + }.desc); + return dirs_filtered_slice; +} + +const RegistryWtf8 = struct { + key: windows.HKEY, + + /// Assert that `key` is valid WTF-8 string + pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 { + const key_wtf16le: [:0]const u16 = key_wtf16le: { + var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; + const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) { + error.InvalidWtf8 => unreachable, + }; + key_wtf16le_buf[key_wtf16le_len] = 0; + break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0]; + }; + + const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le); + return RegistryWtf8{ .key = registry_wtf16le.key }; + } + + /// Closes key, after that usage is invalid + pub fn closeKey(self: *const RegistryWtf8) void { + const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key); + const return_code: windows.Win32Error = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => {}, + else => {}, + } + } + + /// Get string from registry. + /// Caller owns result. + pub fn getString(self: *const RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { + const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { + var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; + const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; + subkey_wtf16le_buf[subkey_wtf16le_len] = 0; + break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0]; + }; + + const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: { + var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; + const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable; + value_name_wtf16le_buf[value_name_wtf16le_len] = 0; + break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0]; + }; + + const registry_wtf16le = RegistryWtf16Le{ .key = self.key }; + const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le); + defer allocator.free(value_wtf16le); + + const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le); + errdefer allocator.free(value_wtf8); + + return value_wtf8; + } + + /// Get DWORD (u32) from registry. + pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { + const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { + var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; + const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; + subkey_wtf16le_buf[subkey_wtf16le_len] = 0; + break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0]; + }; + + const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: { + var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; + const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable; + value_name_wtf16le_buf[value_name_wtf16le_len] = 0; + break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0]; + }; + + const registry_wtf16le = RegistryWtf16Le{ .key = self.key }; + return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le); + } + + /// Under private space with flags: + /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS. + /// After finishing work, call `closeKey`. + pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 { + const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: { + var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; + const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable; + absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0; + break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0]; + }; + + const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le); + return RegistryWtf8{ .key = registry_wtf16le.key }; + } +}; + +const RegistryWtf16Le = struct { + key: windows.HKEY, + + /// Includes root key (f.e. HKEY_LOCAL_MACHINE). + /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits + pub const key_name_max_len = 255; + /// In Unicode characters. + /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits + pub const value_name_max_len = 16_383; + + /// Under HKEY_LOCAL_MACHINE with flags: + /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS. + /// After finishing work, call `closeKey`. + fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { + var key: windows.HKEY = undefined; + const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW( + hkey, + key_wtf16le, + 0, + windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS, + &key, + ); + const return_code: windows.Win32Error = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => {}, + .FILE_NOT_FOUND => return error.KeyNotFound, + + else => return error.KeyNotFound, + } + return RegistryWtf16Le{ .key = key }; + } + + /// Closes key, after that usage is invalid + fn closeKey(self: *const RegistryWtf16Le) void { + const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key); + const return_code: windows.Win32Error = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => {}, + else => {}, + } + } + + /// Get string ([:0]const u16) from registry. + fn getString(self: *const RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { + var actual_type: windows.ULONG = undefined; + + // Calculating length to allocate + var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters. + var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW( + self.key, + subkey_wtf16le, + value_name_wtf16le, + RRF.RT_REG_SZ, + &actual_type, + null, + &value_wtf16le_buf_size, + ); + + // Check returned code and type + var return_code: windows.Win32Error = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0), + .MORE_DATA => unreachable, // We are only reading length + .FILE_NOT_FOUND => return error.ValueNameNotFound, + .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY + else => return error.StringNotFound, + } + switch (actual_type) { + windows.REG.SZ => {}, + else => return error.NotAString, + } + + const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable); + errdefer allocator.free(value_wtf16le_buf); + + return_code_int = windows.advapi32.RegGetValueW( + self.key, + subkey_wtf16le, + value_name_wtf16le, + RRF.RT_REG_SZ, + &actual_type, + value_wtf16le_buf.ptr, + &value_wtf16le_buf_size, + ); + + // Check returned code and (just in case) type again. + return_code = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => {}, + .MORE_DATA => unreachable, // Calculated first time length should be enough, even overestimated + .FILE_NOT_FOUND => return error.ValueNameNotFound, + .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY + else => return error.StringNotFound, + } + switch (actual_type) { + windows.REG.SZ => {}, + else => return error.NotAString, + } + + const value_wtf16le: []const u16 = value_wtf16le: { + // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space + // we will just search for zero termination and forget length + // Windows sure is strange + const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr); + break :value_wtf16le std.mem.span(value_wtf16le_overestimated); + }; + + _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len); + return value_wtf16le; + } + + /// Get DWORD (u32) from registry. + fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { + var actual_type: windows.ULONG = undefined; + var reg_size: u32 = @sizeOf(u32); + var reg_value: u32 = 0; + + const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW( + self.key, + subkey_wtf16le, + value_name_wtf16le, + RRF.RT_REG_DWORD, + &actual_type, + &reg_value, + &reg_size, + ); + const return_code: windows.Win32Error = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => {}, + .MORE_DATA => return error.DwordTooLong, + .FILE_NOT_FOUND => return error.ValueNameNotFound, + .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY + else => return error.DwordNotFound, + } + + switch (actual_type) { + windows.REG.DWORD => {}, + else => return error.NotADword, + } + + return reg_value; + } + + /// Under private space with flags: + /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS. + /// After finishing work, call `closeKey`. + fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { + var key: windows.HKEY = undefined; + + const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW( + absolute_path_as_wtf16le, + &key, + windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS, + 0, + 0, + ); + const return_code: windows.Win32Error = @enumFromInt(return_code_int); + switch (return_code) { + .SUCCESS => {}, + else => return error.KeyNotFound, + } + + return RegistryWtf16Le{ .key = key }; + } +}; + +pub const Windows10Sdk = struct { + path: []const u8, + version: []const u8, + + /// Find path and version of Windows 10 SDK. + /// Caller owns the result's fields. + /// After finishing work, call `free(allocator)`. + fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk { + const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) { + error.KeyNotFound => return error.Windows10SdkNotFound, + }; + defer v10_key.closeKey(); + + const path: []const u8 = path10: { + const path_maybe_with_trailing_slash = v10_key.getString(allocator, "", "InstallationFolder") catch |err| switch (err) { + error.NotAString => return error.Windows10SdkNotFound, + error.ValueNameNotFound => return error.Windows10SdkNotFound, + error.StringNotFound => return error.Windows10SdkNotFound, + + error.OutOfMemory => return error.OutOfMemory, + }; + + if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { + allocator.free(path_maybe_with_trailing_slash); + return error.PathTooLong; + } + + var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); + errdefer path.deinit(); + + // String might contain trailing slash, so trim it here + if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); + + const path_without_trailing_slash = try path.toOwnedSlice(); + break :path10 path_without_trailing_slash; + }; + errdefer allocator.free(path); + + const version: []const u8 = version10: { + + // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key.... + const version_without_0 = v10_key.getString(allocator, "", "ProductVersion") catch |err| switch (err) { + error.NotAString => return error.Windows10SdkNotFound, + error.ValueNameNotFound => return error.Windows10SdkNotFound, + error.StringNotFound => return error.Windows10SdkNotFound, + + error.OutOfMemory => return error.OutOfMemory, + }; + if (version_without_0.len + ".0".len > product_version_max_length) { + allocator.free(version_without_0); + return error.VersionTooLong; + } + + var version = std.ArrayList(u8).fromOwnedSlice(allocator, version_without_0); + errdefer version.deinit(); + + try version.appendSlice(".0"); + + const version_with_0 = try version.toOwnedSlice(); + break :version10 version_with_0; + }; + errdefer allocator.free(version); + + return Windows10Sdk{ .path = path, .version = version }; + } + + /// Check whether this version is enumerated in registry. + fn isValidVersion(windows10sdk: *const Windows10Sdk) bool { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) { + error.NoSpaceLeft => return false, + }; + + const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) { + error.KeyNotFound => return false, + }; + defer options_key.closeKey(); + + const option_name = comptime switch (builtin.target.cpu.arch) { + .arm, .armeb => "OptionId.DesktopCPParm", + .aarch64 => "OptionId.DesktopCPParm64", + .x86_64 => "OptionId.DesktopCPPx64", + .x86 => "OptionId.DesktopCPPx86", + else => |tag| @compileError("Windows 10 SDK cannot be detected on architecture " ++ tag), + }; + + const reg_value = options_key.getDword("", option_name) catch return false; + return (reg_value == 1); + } + + fn free(self: *const Windows10Sdk, allocator: std.mem.Allocator) void { + allocator.free(self.path); + allocator.free(self.version); + } +}; + +pub const Windows81Sdk = struct { + path: []const u8, + version: []const u8, + + /// Find path and version of Windows 8.1 SDK. + /// Caller owns the result's fields. + /// After finishing work, call `free(allocator)`. + fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk { + const path: []const u8 = path81: { + const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) { + error.NotAString => return error.Windows81SdkNotFound, + error.ValueNameNotFound => return error.Windows81SdkNotFound, + error.StringNotFound => return error.Windows81SdkNotFound, + + error.OutOfMemory => return error.OutOfMemory, + }; + if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { + allocator.free(path_maybe_with_trailing_slash); + return error.PathTooLong; + } + + var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); + errdefer path.deinit(); + + // String might contain trailing slash, so trim it here + if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); + + const path_without_trailing_slash = try path.toOwnedSlice(); + break :path81 path_without_trailing_slash; + }; + errdefer allocator.free(path); + + const version: []const u8 = version81: { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) { + error.NoSpaceLeft => return error.PathTooLong, + }; + if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.Windows81SdkNotFound; + + // enumerate files in sdk path looking for latest version + var sdk_lib_dir = std.fs.openDirAbsolute(sdk_lib_dir_path, .{ + .iterate = true, + }) catch |err| switch (err) { + error.NameTooLong => return error.PathTooLong, + else => return error.Windows81SdkNotFound, + }; + defer sdk_lib_dir.close(); + + var iterator = sdk_lib_dir.iterate(); + const versions = iterateAndFilterBySemVer(&iterator, allocator, "winv") catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.VersionNotFound => return error.Windows81SdkNotFound, + }; + defer { + for (versions) |version| allocator.free(version); + allocator.free(versions); + } + const latest_version = try allocator.dupe(u8, versions[0]); + break :version81 latest_version; + }; + errdefer allocator.free(version); + + return Windows81Sdk{ .path = path, .version = version }; + } + + fn free(self: *const Windows81Sdk, allocator: std.mem.Allocator) void { + allocator.free(self.path); + allocator.free(self.version); + } +}; + +const MsvcLibDir = struct { + fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; + const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) { + error.KeyNotFound => return error.PathNotFound, + }; + defer setup_config_key.closeKey(); + + const dll_path = setup_config_key.getString(allocator, "InprocServer32", "") catch |err| switch (err) { + error.NotAString, + error.ValueNameNotFound, + error.StringNotFound, + => return error.PathNotFound, + + error.OutOfMemory => return error.OutOfMemory, + }; + defer allocator.free(dll_path); + + var path_it = std.fs.path.componentIterator(dll_path) catch return error.PathNotFound; + // the .dll filename + _ = path_it.last(); + const root_path = while (path_it.previous()) |dir_component| { + if (std.ascii.eqlIgnoreCase(dir_component.name, "VisualStudio")) { + break dir_component.path; + } + } else { + return error.PathNotFound; + }; + + const instances_path = try std.fs.path.join(allocator, &.{ root_path, "Packages", "_Instances" }); + defer allocator.free(instances_path); + + return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; + } + + fn findInstancesDir(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + // First try to get the path from the .dll that would have been + // loaded via COM for SetupConfiguration. + return findInstancesDirViaCLSID(allocator) catch |orig_err| { + // If that can't be found, fall back to manually appending + // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% + const program_data = std.process.getEnvVarOwned(allocator, "PROGRAMDATA") catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => return orig_err, + }; + defer allocator.free(program_data); + + const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); + defer allocator.free(instances_path); + + return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return orig_err; + }; + } + + /// Intended to be equivalent to `ISetupHelper.ParseVersion` + /// Example: 17.4.33205.214 -> 0x0011000481b500d6 + fn parseVersionQuad(version: []const u8) error{InvalidVersion}!u64 { + var it = std.mem.splitScalar(u8, version, '.'); + const a = it.next() orelse return error.InvalidVersion; + const b = it.next() orelse return error.InvalidVersion; + const c = it.next() orelse return error.InvalidVersion; + const d = it.next() orelse return error.InvalidVersion; + if (it.next()) |_| return error.InvalidVersion; + var result: u64 = undefined; + var result_bytes = std.mem.asBytes(&result); + + std.mem.writeInt( + u16, + result_bytes[0..2], + std.fmt.parseUnsigned(u16, d, 10) catch return error.InvalidVersion, + .little, + ); + std.mem.writeInt( + u16, + result_bytes[2..4], + std.fmt.parseUnsigned(u16, c, 10) catch return error.InvalidVersion, + .little, + ); + std.mem.writeInt( + u16, + result_bytes[4..6], + std.fmt.parseUnsigned(u16, b, 10) catch return error.InvalidVersion, + .little, + ); + std.mem.writeInt( + u16, + result_bytes[6..8], + std.fmt.parseUnsigned(u16, a, 10) catch return error.InvalidVersion, + .little, + ); + + return result; + } + + /// Intended to be equivalent to ISetupConfiguration.EnumInstances: + /// https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration + /// but without the use of COM in order to avoid a dependency on ole32.dll + /// + /// The logic in this function is intended to match what ISetupConfiguration does + /// under-the-hood, as verified using Procmon. + fn findViaCOM(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { + // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` + // This will contain directories with names of instance IDs like 80a758ca, + // which will contain `state.json` files that have the version and + // installation directory. + var instances_dir = try findInstancesDir(allocator); + defer instances_dir.close(); + + var state_subpath_buf: [std.fs.MAX_NAME_BYTES + 32]u8 = undefined; + var latest_version_lib_dir = std.ArrayListUnmanaged(u8){}; + errdefer latest_version_lib_dir.deinit(allocator); + + var latest_version: u64 = 0; + var instances_dir_it = instances_dir.iterateAssumeFirstIteration(); + while (instances_dir_it.next() catch return error.PathNotFound) |entry| { + if (entry.kind != .directory) continue; + + var fbs = std.io.fixedBufferStream(&state_subpath_buf); + const writer = fbs.writer(); + + writer.writeAll(entry.name) catch unreachable; + writer.writeByte(std.fs.path.sep) catch unreachable; + writer.writeAll("state.json") catch unreachable; + + const json_contents = instances_dir.readFileAlloc(allocator, fbs.getWritten(), std.math.maxInt(usize)) catch continue; + defer allocator.free(json_contents); + + var parsed = std.json.parseFromSlice(std.json.Value, allocator, json_contents, .{}) catch continue; + defer parsed.deinit(); + + if (parsed.value != .object) continue; + const catalog_info = parsed.value.object.get("catalogInfo") orelse continue; + if (catalog_info != .object) continue; + const product_version_value = catalog_info.object.get("buildVersion") orelse continue; + if (product_version_value != .string) continue; + const product_version_text = product_version_value.string; + const parsed_version = parseVersionQuad(product_version_text) catch continue; + + // We want to end up with the most recent version installed + if (parsed_version <= latest_version) continue; + + const installation_path = parsed.value.object.get("installationPath") orelse continue; + if (installation_path != .string) continue; + + const lib_dir_path = libDirFromInstallationPath(allocator, installation_path.string) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.PathNotFound => continue, + }; + defer allocator.free(lib_dir_path); + + latest_version_lib_dir.clearRetainingCapacity(); + try latest_version_lib_dir.appendSlice(allocator, lib_dir_path); + latest_version = parsed_version; + } + + if (latest_version_lib_dir.items.len == 0) return error.PathNotFound; + return latest_version_lib_dir.toOwnedSlice(allocator); + } + + fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path: []const u8) error{ OutOfMemory, PathNotFound }![]const u8 { + var lib_dir_buf = try std.ArrayList(u8).initCapacity(allocator, installation_path.len + 64); + errdefer lib_dir_buf.deinit(); + + lib_dir_buf.appendSliceAssumeCapacity(installation_path); + + if (!std.fs.path.isSep(lib_dir_buf.getLast())) { + try lib_dir_buf.append('\\'); + } + const installation_path_with_trailing_sep_len = lib_dir_buf.items.len; + + try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); + var default_tools_version_buf: [512]u8 = undefined; + const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { + return error.PathNotFound; + }; + var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); + const default_tools_version = tokenizer.next() orelse return error.PathNotFound; + + lib_dir_buf.shrinkRetainingCapacity(installation_path_with_trailing_sep_len); + try lib_dir_buf.appendSlice("VC\\Tools\\MSVC\\"); + try lib_dir_buf.appendSlice(default_tools_version); + const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) { + .x86 => "x86", + .x86_64 => "x64", + .arm, .armeb => "arm", + .aarch64 => "arm64", + else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), + }; + try lib_dir_buf.appendSlice(folder_with_arch); + + if (!verifyLibDir(lib_dir_buf.items)) { + return error.PathNotFound; + } + + return lib_dir_buf.toOwnedSlice(); + } + + // https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance + fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { + + // %localappdata%\Microsoft\VisualStudio\ + // %appdata%\Local\Microsoft\VisualStudio\ + const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound; + defer allocator.free(visualstudio_folder_path); + + const vs_versions: []const []const u8 = vs_versions: { + if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound; + // enumerate folders that contain `privateregistry.bin`, looking for all versions + // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\ + var visualstudio_folder = std.fs.openDirAbsolute(visualstudio_folder_path, .{ + .iterate = true, + }) catch return error.PathNotFound; + defer visualstudio_folder.close(); + + var iterator = visualstudio_folder.iterate(); + const versions = iterateAndFilterBySemVer(&iterator, allocator, null) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.VersionNotFound => return error.PathNotFound, + }; + break :vs_versions versions; + }; + defer { + for (vs_versions) |vs_version| allocator.free(vs_version); + allocator.free(vs_versions); + } + var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined; + const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| { + const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue; + defer allocator.free(privateregistry_absolute_path); + if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue; + + const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue; + defer visualstudio_registry.closeKey(); + + const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable; + + const source_directories_value = visualstudio_registry.getString(allocator, config_subkey, "Source Directories") catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => continue, + }; + if (source_directories_value.len > (std.fs.MAX_PATH_BYTES * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 pathes and at least some of them are not of max length + allocator.free(source_directories_value); + continue; + } + + break :source_directories source_directories_value; + } else return error.PathNotFound; + defer allocator.free(source_directories); + + var source_directories_splitted = std.mem.splitScalar(u8, source_directories, ';'); + + const msvc_dir: []const u8 = msvc_dir: { + const msvc_include_dir_maybe_with_trailing_slash = try allocator.dupe(u8, source_directories_splitted.first()); + + if (msvc_include_dir_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) { + allocator.free(msvc_include_dir_maybe_with_trailing_slash); + return error.PathNotFound; + } + + var msvc_dir = std.ArrayList(u8).fromOwnedSlice(allocator, msvc_include_dir_maybe_with_trailing_slash); + errdefer msvc_dir.deinit(); + + // String might contain trailing slash, so trim it here + if (msvc_dir.items.len > "C:\\".len and msvc_dir.getLast() == '\\') _ = msvc_dir.pop(); + + // Remove `\include` at the end of path + if (std.mem.endsWith(u8, msvc_dir.items, "\\include")) { + msvc_dir.shrinkRetainingCapacity(msvc_dir.items.len - "\\include".len); + } + + const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) { + .x86 => "x86", + .x86_64 => "x64", + .arm, .armeb => "arm", + .aarch64 => "arm64", + else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), + }; + + try msvc_dir.appendSlice(folder_with_arch); + const msvc_dir_with_arch = try msvc_dir.toOwnedSlice(); + break :msvc_dir msvc_dir_with_arch; + }; + errdefer allocator.free(msvc_dir); + + if (!verifyLibDir(msvc_dir)) { + return error.PathNotFound; + } + + return msvc_dir; + } + + fn findViaVs7Key(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { + var base_path: std.ArrayList(u8) = base_path: { + try_env: { + var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => break :try_env, + }; + defer env_map.deinit(); + + if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { + if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env; + if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env; + var list = std.ArrayList(u8).init(allocator); + errdefer list.deinit(); + + try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools + // String might contain trailing slash, so trim it here + if (list.items.len > "C:\\".len and list.getLast() == '\\') _ = list.pop(); + list.shrinkRetainingCapacity(list.items.len - "\\Common7\\Tools".len); // C:\Program Files (x86)\Microsoft Visual Studio 14.0 + break :base_path list; + } + } + + const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound; + defer vs7_key.closeKey(); + try_vs7_key: { + const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => break :try_vs7_key, + }; + + if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { + allocator.free(path_maybe_with_trailing_slash); + break :try_vs7_key; + } + + var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); + errdefer path.deinit(); + + // String might contain trailing slash, so trim it here + if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); + break :base_path path; + } + return error.PathNotFound; + }; + errdefer base_path.deinit(); + + const folder_with_arch = "\\VC\\lib\\" ++ comptime switch (builtin.target.cpu.arch) { + .x86 => "", //x86 is in the root of the Lib folder + .x86_64 => "amd64", + .arm, .armeb => "arm", + .aarch64 => "arm64", + else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), + }; + try base_path.appendSlice(folder_with_arch); + + if (!verifyLibDir(base_path.items)) { + return error.PathNotFound; + } + + const full_path = try base_path.toOwnedSlice(); + return full_path; + } + + fn verifyLibDir(lib_dir_path: []const u8) bool { + std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` + + var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; + defer dir.close(); + + const stat = dir.statFile("vcruntime.lib") catch return false; + if (stat.kind != .file) + return false; + + return true; + } + + /// Find path to MSVC's `lib/` directory. + /// Caller owns the result. + pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { + const full_path = MsvcLibDir.findViaCOM(allocator) catch |err1| switch (err1) { + error.OutOfMemory => return error.OutOfMemory, + error.PathNotFound => MsvcLibDir.findViaRegistry(allocator) catch |err2| switch (err2) { + error.OutOfMemory => return error.OutOfMemory, + error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err3| switch (err3) { + error.OutOfMemory => return error.OutOfMemory, + error.PathNotFound => return error.MsvcLibDirNotFound, + }, + }, + }; + errdefer allocator.free(full_path); + + return full_path; + } +}; diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig @@ -0,0 +1,117 @@ +pub const ArchOsAbi = struct { + arch: std.Target.Cpu.Arch, + os: std.Target.Os.Tag, + abi: std.Target.Abi, + os_ver: ?std.SemanticVersion = null, + + // Minimum glibc version that provides support for the arch/os when ABI is GNU. + glibc_min: ?std.SemanticVersion = null, +}; + +pub const available_libcs = [_]ArchOsAbi{ + .{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } }, + .{ .arch = .aarch64_be, .os = .linux, .abi = .musl }, + .{ .arch = .aarch64_be, .os = .windows, .abi = .gnu }, + .{ .arch = .aarch64, .os = .linux, .abi = .gnu }, + .{ .arch = .aarch64, .os = .linux, .abi = .musl }, + .{ .arch = .aarch64, .os = .windows, .abi = .gnu }, + .{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } }, + .{ .arch = .armeb, .os = .linux, .abi = .gnueabi }, + .{ .arch = .armeb, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .armeb, .os = .linux, .abi = .musleabi }, + .{ .arch = .armeb, .os = .linux, .abi = .musleabihf }, + .{ .arch = .armeb, .os = .windows, .abi = .gnu }, + .{ .arch = .arm, .os = .linux, .abi = .gnueabi }, + .{ .arch = .arm, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .arm, .os = .linux, .abi = .musleabi }, + .{ .arch = .arm, .os = .linux, .abi = .musleabihf }, + .{ .arch = .thumb, .os = .linux, .abi = .gnueabi }, + .{ .arch = .thumb, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .thumb, .os = .linux, .abi = .musleabi }, + .{ .arch = .thumb, .os = .linux, .abi = .musleabihf }, + .{ .arch = .arm, .os = .windows, .abi = .gnu }, + .{ .arch = .csky, .os = .linux, .abi = .gnueabi }, + .{ .arch = .csky, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .x86, .os = .linux, .abi = .gnu }, + .{ .arch = .x86, .os = .linux, .abi = .musl }, + .{ .arch = .x86, .os = .windows, .abi = .gnu }, + .{ .arch = .m68k, .os = .linux, .abi = .gnu }, + .{ .arch = .m68k, .os = .linux, .abi = .musl }, + .{ .arch = .mips64el, .os = .linux, .abi = .gnuabi64 }, + .{ .arch = .mips64el, .os = .linux, .abi = .gnuabin32 }, + .{ .arch = .mips64el, .os = .linux, .abi = .musl }, + .{ .arch = .mips64, .os = .linux, .abi = .gnuabi64 }, + .{ .arch = .mips64, .os = .linux, .abi = .gnuabin32 }, + .{ .arch = .mips64, .os = .linux, .abi = .musl }, + .{ .arch = .mipsel, .os = .linux, .abi = .gnueabi }, + .{ .arch = .mipsel, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .mipsel, .os = .linux, .abi = .musl }, + .{ .arch = .mips, .os = .linux, .abi = .gnueabi }, + .{ .arch = .mips, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .mips, .os = .linux, .abi = .musl }, + .{ .arch = .powerpc64le, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 19, .patch = 0 } }, + .{ .arch = .powerpc64le, .os = .linux, .abi = .musl }, + .{ .arch = .powerpc64, .os = .linux, .abi = .gnu }, + .{ .arch = .powerpc64, .os = .linux, .abi = .musl }, + .{ .arch = .powerpc, .os = .linux, .abi = .gnueabi }, + .{ .arch = .powerpc, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .powerpc, .os = .linux, .abi = .musl }, + .{ .arch = .riscv64, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 27, .patch = 0 } }, + .{ .arch = .riscv64, .os = .linux, .abi = .musl }, + .{ .arch = .s390x, .os = .linux, .abi = .gnu }, + .{ .arch = .s390x, .os = .linux, .abi = .musl }, + .{ .arch = .sparc, .os = .linux, .abi = .gnu }, + .{ .arch = .sparc64, .os = .linux, .abi = .gnu }, + .{ .arch = .wasm32, .os = .freestanding, .abi = .musl }, + .{ .arch = .wasm32, .os = .wasi, .abi = .musl }, + .{ .arch = .x86_64, .os = .linux, .abi = .gnu }, + .{ .arch = .x86_64, .os = .linux, .abi = .gnux32 }, + .{ .arch = .x86_64, .os = .linux, .abi = .musl }, + .{ .arch = .x86_64, .os = .windows, .abi = .gnu }, + .{ .arch = .x86_64, .os = .macos, .abi = .none, .os_ver = .{ .major = 10, .minor = 7, .patch = 0 } }, +}; + +pub fn canBuildLibC(target: std.Target) bool { + for (available_libcs) |libc| { + if (target.cpu.arch == libc.arch and target.os.tag == libc.os and target.abi == libc.abi) { + if (target.os.tag == .macos) { + const ver = target.os.version_range.semver; + return ver.min.order(libc.os_ver.?) != .lt; + } + // Ensure glibc (aka *-linux-gnu) version is supported + if (target.isGnuLibC()) { + const min_glibc_ver = libc.glibc_min orelse return true; + const target_glibc_ver = target.os.version_range.linux.glibc; + return target_glibc_ver.order(min_glibc_ver) != .lt; + } + return true; + } + } + return false; +} + +pub fn muslArchNameHeaders(arch: std.Target.Cpu.Arch) [:0]const u8 { + return switch (arch) { + .x86 => return "x86", + else => muslArchName(arch), + }; +} + +pub fn muslArchName(arch: std.Target.Cpu.Arch) [:0]const u8 { + switch (arch) { + .aarch64, .aarch64_be => return "aarch64", + .arm, .armeb, .thumb, .thumbeb => return "arm", + .x86 => return "i386", + .mips, .mipsel => return "mips", + .mips64el, .mips64 => return "mips64", + .powerpc => return "powerpc", + .powerpc64, .powerpc64le => return "powerpc64", + .riscv64 => return "riscv64", + .s390x => return "s390x", + .wasm32, .wasm64 => return "wasm", + .x86_64 => return "x86_64", + else => unreachable, + } +} + +const std = @import("std"); diff --git a/src/Compilation.zig b/src/Compilation.zig @@ -19,7 +19,7 @@ const link = @import("link.zig"); const tracy = @import("tracy.zig"); const trace = tracy.trace; const build_options = @import("build_options"); -const LibCInstallation = @import("libc_installation.zig").LibCInstallation; +const LibCInstallation = std.zig.LibCInstallation; const glibc = @import("glibc.zig"); const musl = @import("musl.zig"); const mingw = @import("mingw.zig"); @@ -1232,7 +1232,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const link_libc = options.config.link_libc; - const libc_dirs = try detectLibCIncludeDirs( + const libc_dirs = try std.zig.LibCDirs.detect( arena, options.zig_lib_directory.path.?, options.root_mod.resolved_target.result, @@ -1250,7 +1250,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // only relevant differences would be things like `#define` constants being // different in the MinGW headers vs the MSVC headers, but any such // differences would likely be a MinGW bug. - const rc_dirs = b: { + const rc_dirs: std.zig.LibCDirs = b: { // Set the includes to .none here when there are no rc files to compile var includes = if (options.rc_source_files.len > 0) options.rc_includes else .none; const target = options.root_mod.resolved_target.result; @@ -1265,7 +1265,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } } while (true) switch (includes) { - .any, .msvc => break :b detectLibCIncludeDirs( + .any, .msvc => break :b std.zig.LibCDirs.detect( arena, options.zig_lib_directory.path.?, .{ @@ -1287,13 +1287,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } return err; }, - .gnu => break :b try detectLibCFromBuilding(arena, options.zig_lib_directory.path.?, .{ + .gnu => break :b try std.zig.LibCDirs.detectFromBuilding(arena, options.zig_lib_directory.path.?, .{ .cpu = target.cpu, .os = target.os, .abi = .gnu, .ofmt = target.ofmt, }), - .none => break :b LibCDirs{ + .none => break :b .{ .libc_include_dir_list = &[0][]u8{}, .libc_installation = null, .libc_framework_dir_list = &.{}, @@ -1772,7 +1772,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // If we need to build glibc for the target, add work items for it. // We go through the work queue so that building can be done in parallel. if (comp.wantBuildGLibCFromSource()) { - if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (glibc.needsCrtiCrtn(target)) { try comp.work_queue.write(&[_]Job{ @@ -1787,7 +1787,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }); } if (comp.wantBuildMuslFromSource()) { - if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; try comp.work_queue.ensureUnusedCapacity(6); if (musl.needsCrtiCrtn(target)) { @@ -1808,7 +1808,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } if (comp.wantBuildWasiLibcFromSource()) { - if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; // worst-case we need all components try comp.work_queue.ensureUnusedCapacity(comp.wasi_emulated_libs.len + 2); @@ -1825,7 +1825,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } if (comp.wantBuildMinGWFromSource()) { - if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; const crt_job: Job = .{ .mingw_crt_file = if (is_dyn_lib) .dllcrt2_o else .crt2_o }; try comp.work_queue.ensureUnusedCapacity(2); @@ -5830,224 +5830,6 @@ test "classifyFileExt" { try std.testing.expectEqual(FileExt.zig, classifyFileExt("foo.zig")); } -const LibCDirs = struct { - libc_include_dir_list: []const []const u8, - libc_installation: ?*const LibCInstallation, - libc_framework_dir_list: []const []const u8, - sysroot: ?[]const u8, - darwin_sdk_layout: ?link.File.MachO.SdkLayout, -}; - -fn getZigShippedLibCIncludeDirsDarwin(arena: Allocator, zig_lib_dir: []const u8) !LibCDirs { - const s = std.fs.path.sep_str; - const list = try arena.alloc([]const u8, 1); - list[0] = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-macos-any", - .{zig_lib_dir}, - ); - return LibCDirs{ - .libc_include_dir_list = list, - .libc_installation = null, - .libc_framework_dir_list = &.{}, - .sysroot = null, - .darwin_sdk_layout = .vendored, - }; -} - -pub fn detectLibCIncludeDirs( - arena: Allocator, - zig_lib_dir: []const u8, - target: Target, - is_native_abi: bool, - link_libc: bool, - libc_installation: ?*const LibCInstallation, -) !LibCDirs { - if (!link_libc) { - return LibCDirs{ - .libc_include_dir_list = &[0][]u8{}, - .libc_installation = null, - .libc_framework_dir_list = &.{}, - .sysroot = null, - .darwin_sdk_layout = null, - }; - } - - if (libc_installation) |lci| { - return detectLibCFromLibCInstallation(arena, target, lci); - } - - // If linking system libraries and targeting the native abi, default to - // using the system libc installation. - if (is_native_abi and !target.isMinGW()) { - const libc = try arena.create(LibCInstallation); - libc.* = LibCInstallation.findNative(.{ .allocator = arena, .target = target }) catch |err| switch (err) { - error.CCompilerExitCode, - error.CCompilerCrashed, - error.CCompilerCannotFindHeaders, - error.UnableToSpawnCCompiler, - error.DarwinSdkNotFound, - => |e| { - // We tried to integrate with the native system C compiler, - // however, it is not installed. So we must rely on our bundled - // libc files. - if (target_util.canBuildLibC(target)) { - return detectLibCFromBuilding(arena, zig_lib_dir, target); - } - return e; - }, - else => |e| return e, - }; - return detectLibCFromLibCInstallation(arena, target, libc); - } - - // If not linking system libraries, build and provide our own libc by - // default if possible. - if (target_util.canBuildLibC(target)) { - return detectLibCFromBuilding(arena, zig_lib_dir, target); - } - - // If zig can't build the libc for the target and we are targeting the - // native abi, fall back to using the system libc installation. - // On windows, instead of the native (mingw) abi, we want to check - // for the MSVC abi as a fallback. - const use_system_abi = if (builtin.target.os.tag == .windows) - target.abi == .msvc - else - is_native_abi; - - if (use_system_abi) { - const libc = try arena.create(LibCInstallation); - libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true, .target = target }); - return detectLibCFromLibCInstallation(arena, target, libc); - } - - return LibCDirs{ - .libc_include_dir_list = &[0][]u8{}, - .libc_installation = null, - .libc_framework_dir_list = &.{}, - .sysroot = null, - .darwin_sdk_layout = null, - }; -} - -fn detectLibCFromLibCInstallation(arena: Allocator, target: Target, lci: *const LibCInstallation) !LibCDirs { - var list = try std.ArrayList([]const u8).initCapacity(arena, 5); - var framework_list = std.ArrayList([]const u8).init(arena); - - list.appendAssumeCapacity(lci.include_dir.?); - - const is_redundant = mem.eql(u8, lci.sys_include_dir.?, lci.include_dir.?); - if (!is_redundant) list.appendAssumeCapacity(lci.sys_include_dir.?); - - if (target.os.tag == .windows) { - if (std.fs.path.dirname(lci.sys_include_dir.?)) |sys_include_dir_parent| { - // This include path will only exist when the optional "Desktop development with C++" - // is installed. It contains headers, .rc files, and resources. It is especially - // necessary when working with Windows resources. - const atlmfc_dir = try std.fs.path.join(arena, &[_][]const u8{ sys_include_dir_parent, "atlmfc", "include" }); - list.appendAssumeCapacity(atlmfc_dir); - } - if (std.fs.path.dirname(lci.include_dir.?)) |include_dir_parent| { - const um_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "um" }); - list.appendAssumeCapacity(um_dir); - - const shared_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "shared" }); - list.appendAssumeCapacity(shared_dir); - } - } - if (target.os.tag == .haiku) { - const include_dir_path = lci.include_dir orelse return error.LibCInstallationNotAvailable; - const os_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_path, "os" }); - list.appendAssumeCapacity(os_dir); - // Errors.h - const os_support_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_path, "os/support" }); - list.appendAssumeCapacity(os_support_dir); - - const config_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_path, "config" }); - list.appendAssumeCapacity(config_dir); - } - - var sysroot: ?[]const u8 = null; - - if (target.isDarwin()) d: { - const down1 = std.fs.path.dirname(lci.sys_include_dir.?) orelse break :d; - const down2 = std.fs.path.dirname(down1) orelse break :d; - try framework_list.append(try std.fs.path.join(arena, &.{ down2, "System", "Library", "Frameworks" })); - sysroot = down2; - } - - return LibCDirs{ - .libc_include_dir_list = list.items, - .libc_installation = lci, - .libc_framework_dir_list = framework_list.items, - .sysroot = sysroot, - .darwin_sdk_layout = if (sysroot == null) null else .sdk, - }; -} - -fn detectLibCFromBuilding( - arena: Allocator, - zig_lib_dir: []const u8, - target: std.Target, -) !LibCDirs { - if (target.isDarwin()) - return getZigShippedLibCIncludeDirsDarwin(arena, zig_lib_dir); - - const generic_name = target_util.libCGenericName(target); - // Some architectures are handled by the same set of headers. - const arch_name = if (target.abi.isMusl()) - musl.archNameHeaders(target.cpu.arch) - else if (target.cpu.arch.isThumb()) - // ARM headers are valid for Thumb too. - switch (target.cpu.arch) { - .thumb => "arm", - .thumbeb => "armeb", - else => unreachable, - } - else - @tagName(target.cpu.arch); - const os_name = @tagName(target.os.tag); - // Musl's headers are ABI-agnostic and so they all have the "musl" ABI name. - const abi_name = if (target.abi.isMusl()) "musl" else @tagName(target.abi); - const s = std.fs.path.sep_str; - const arch_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", - .{ zig_lib_dir, arch_name, os_name, abi_name }, - ); - const generic_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{s}", - .{ zig_lib_dir, generic_name }, - ); - const generic_arch_name = target_util.osArchName(target); - const arch_os_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-any", - .{ zig_lib_dir, generic_arch_name, os_name }, - ); - const generic_os_include_dir = try std.fmt.allocPrint( - arena, - "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any", - .{ zig_lib_dir, os_name }, - ); - - const list = try arena.alloc([]const u8, 4); - list[0] = arch_include_dir; - list[1] = generic_include_dir; - list[2] = arch_os_include_dir; - list[3] = generic_os_include_dir; - - return LibCDirs{ - .libc_include_dir_list = list, - .libc_installation = null, - .libc_framework_dir_list = &.{}, - .sysroot = null, - .darwin_sdk_layout = .vendored, - }; -} - pub fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) ![]const u8 { if (comp.wantBuildGLibCFromSource() or comp.wantBuildMuslFromSource() or diff --git a/src/glibc.zig b/src/glibc.zig @@ -7,7 +7,6 @@ const path = fs.path; const assert = std.debug.assert; const Version = std.SemanticVersion; -const target_util = @import("target.zig"); const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); const trace = @import("tracy.zig").trace; @@ -21,7 +20,7 @@ pub const Lib = struct { pub const ABI = struct { all_versions: []const Version, // all defined versions (one abilist from v2.0.0 up to current) - all_targets: []const target_util.ArchOsAbi, + all_targets: []const std.zig.target.ArchOsAbi, /// The bytes from the file verbatim, starting from the u16 number /// of function inclusions. inclusions: []const u8, @@ -103,7 +102,7 @@ pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI const targets_len = contents[index]; index += 1; - const targets = try arena.alloc(target_util.ArchOsAbi, targets_len); + const targets = try arena.alloc(std.zig.target.ArchOsAbi, targets_len); var i: u8 = 0; while (i < targets.len) : (i += 1) { const target_name = mem.sliceTo(contents[index..], 0); @@ -512,7 +511,7 @@ fn add_include_dirs(comp: *Compilation, arena: Allocator, args: *std.ArrayList([ try args.append("-I"); try args.append(try lib_path(comp, arena, lib_libc ++ "include" ++ s ++ "generic-glibc")); - const arch_name = target_util.osArchName(target); + const arch_name = target.osArchName(); try args.append("-I"); try args.append(try std.fmt.allocPrint(arena, "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-linux-any", .{ comp.zig_lib_directory.path.?, arch_name, @@ -726,7 +725,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: *std.Progress.Node) !vo break i; } } else { - unreachable; // target_util.available_libcs prevents us from getting here + unreachable; // std.zig.target.available_libcs prevents us from getting here }; const target_ver_index = for (metadata.all_versions, 0..) |ver, i| { diff --git a/src/introspect.zig b/src/introspect.zig @@ -84,14 +84,14 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 { if (builtin.os.tag == .wasi) @compileError("on WASI the global cache dir must be resolved with preopens"); - if (try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(allocator)) |value| return value; + if (try std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(allocator)) |value| return value; const appname = "zig"; if (builtin.os.tag != .windows) { - if (EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { + if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { return fs.path.join(allocator, &[_][]const u8{ cache_root, appname }); - } else if (EnvVar.HOME.getPosix()) |home| { + } else if (std.zig.EnvVar.HOME.getPosix()) |home| { return fs.path.join(allocator, &[_][]const u8{ home, ".cache", appname }); } } @@ -141,41 +141,3 @@ pub fn resolvePath( pub fn isUpDir(p: []const u8) bool { return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == fs.path.sep); } - -/// 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_LIB_DIR, - ZIG_LIBC, - ZIG_BUILD_RUNNER, - ZIG_VERBOSE_LINK, - ZIG_VERBOSE_CC, - ZIG_BTRFS_WORKAROUND, - ZIG_DEBUG_CMD, - CC, - NO_COLOR, - XDG_CACHE_HOME, - HOME, - - pub fn isSet(comptime ev: EnvVar) bool { - return std.process.hasEnvVarConstant(@tagName(ev)); - } - - pub fn get(ev: EnvVar, arena: mem.Allocator) !?[]u8 { - // Env vars aren't used in the bootstrap stage. - if (build_options.only_c) return null; - - if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| { - return value; - } else |err| switch (err) { - error.EnvironmentVariableNotFound => return null, - else => |e| return e, - } - } - - pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 { - return std.os.getenvZ(@tagName(ev)); - } -}; diff --git a/src/libc_installation.zig b/src/libc_installation.zig @@ -1,712 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const Target = std.Target; -const fs = std.fs; -const Allocator = std.mem.Allocator; - -const is_darwin = builtin.target.isDarwin(); -const is_windows = builtin.target.os.tag == .windows; -const is_haiku = builtin.target.os.tag == .haiku; - -const log = std.log.scoped(.libc_installation); - -const ZigWindowsSDK = @import("windows_sdk.zig").ZigWindowsSDK; -const EnvVar = @import("introspect.zig").EnvVar; - -/// See the render function implementation for documentation of the fields. -pub const LibCInstallation = struct { - include_dir: ?[]const u8 = null, - sys_include_dir: ?[]const u8 = null, - crt_dir: ?[]const u8 = null, - msvc_lib_dir: ?[]const u8 = null, - kernel32_lib_dir: ?[]const u8 = null, - gcc_dir: ?[]const u8 = null, - - pub const FindError = error{ - OutOfMemory, - FileSystem, - UnableToSpawnCCompiler, - CCompilerExitCode, - CCompilerCrashed, - CCompilerCannotFindHeaders, - LibCRuntimeNotFound, - LibCStdLibHeaderNotFound, - LibCKernel32LibNotFound, - UnsupportedArchitecture, - WindowsSdkNotFound, - DarwinSdkNotFound, - ZigIsTheCCompiler, - }; - - pub fn parse( - allocator: Allocator, - libc_file: []const u8, - target: std.Target, - ) !LibCInstallation { - var self: LibCInstallation = .{}; - - const fields = std.meta.fields(LibCInstallation); - const FoundKey = struct { - found: bool, - allocated: ?[:0]u8, - }; - var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** fields.len; - errdefer { - self = .{}; - for (found_keys) |found_key| { - if (found_key.allocated) |s| allocator.free(s); - } - } - - const contents = try std.fs.cwd().readFileAlloc(allocator, libc_file, std.math.maxInt(usize)); - defer allocator.free(contents); - - var it = std.mem.tokenizeScalar(u8, contents, '\n'); - while (it.next()) |line| { - if (line.len == 0 or line[0] == '#') continue; - var line_it = std.mem.splitScalar(u8, line, '='); - const name = line_it.first(); - const value = line_it.rest(); - inline for (fields, 0..) |field, i| { - if (std.mem.eql(u8, name, field.name)) { - found_keys[i].found = true; - if (value.len == 0) { - @field(self, field.name) = null; - } else { - found_keys[i].allocated = try allocator.dupeZ(u8, value); - @field(self, field.name) = found_keys[i].allocated; - } - break; - } - } - } - inline for (fields, 0..) |field, i| { - if (!found_keys[i].found) { - log.err("missing field: {s}\n", .{field.name}); - return error.ParseError; - } - } - if (self.include_dir == null) { - log.err("include_dir may not be empty\n", .{}); - return error.ParseError; - } - if (self.sys_include_dir == null) { - log.err("sys_include_dir may not be empty\n", .{}); - return error.ParseError; - } - - const os_tag = target.os.tag; - if (self.crt_dir == null and !target.isDarwin()) { - log.err("crt_dir may not be empty for {s}\n", .{@tagName(os_tag)}); - return error.ParseError; - } - - if (self.msvc_lib_dir == null and os_tag == .windows and target.abi == .msvc) { - log.err("msvc_lib_dir may not be empty for {s}-{s}\n", .{ - @tagName(os_tag), - @tagName(target.abi), - }); - return error.ParseError; - } - if (self.kernel32_lib_dir == null and os_tag == .windows and target.abi == .msvc) { - log.err("kernel32_lib_dir may not be empty for {s}-{s}\n", .{ - @tagName(os_tag), - @tagName(target.abi), - }); - return error.ParseError; - } - - if (self.gcc_dir == null and os_tag == .haiku) { - log.err("gcc_dir may not be empty for {s}\n", .{@tagName(os_tag)}); - return error.ParseError; - } - - return self; - } - - pub fn render(self: LibCInstallation, out: anytype) !void { - @setEvalBranchQuota(4000); - const include_dir = self.include_dir orelse ""; - const sys_include_dir = self.sys_include_dir orelse ""; - const crt_dir = self.crt_dir orelse ""; - const msvc_lib_dir = self.msvc_lib_dir orelse ""; - const kernel32_lib_dir = self.kernel32_lib_dir orelse ""; - const gcc_dir = self.gcc_dir orelse ""; - - try out.print( - \\# The directory that contains `stdlib.h`. - \\# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null` - \\include_dir={s} - \\ - \\# The system-specific include directory. May be the same as `include_dir`. - \\# On Windows it's the directory that includes `vcruntime.h`. - \\# On POSIX it's the directory that includes `sys/errno.h`. - \\sys_include_dir={s} - \\ - \\# The directory that contains `crt1.o` or `crt2.o`. - \\# On POSIX, can be found with `cc -print-file-name=crt1.o`. - \\# Not needed when targeting MacOS. - \\crt_dir={s} - \\ - \\# The directory that contains `vcruntime.lib`. - \\# Only needed when targeting MSVC on Windows. - \\msvc_lib_dir={s} - \\ - \\# The directory that contains `kernel32.lib`. - \\# Only needed when targeting MSVC on Windows. - \\kernel32_lib_dir={s} - \\ - \\# The directory that contains `crtbeginS.o` and `crtendS.o` - \\# Only needed when targeting Haiku. - \\gcc_dir={s} - \\ - , .{ - include_dir, - sys_include_dir, - crt_dir, - msvc_lib_dir, - kernel32_lib_dir, - gcc_dir, - }); - } - - pub const FindNativeOptions = struct { - allocator: Allocator, - target: std.Target, - - /// If enabled, will print human-friendly errors to stderr. - verbose: bool = false, - }; - - /// Finds the default, native libc. - pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { - var self: LibCInstallation = .{}; - - if (is_darwin) { - if (!std.zig.system.darwin.isSdkInstalled(args.allocator)) - return error.DarwinSdkNotFound; - const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse - return error.DarwinSdkNotFound; - defer args.allocator.free(sdk); - - self.include_dir = try fs.path.join(args.allocator, &.{ - sdk, "usr/include", - }); - self.sys_include_dir = try fs.path.join(args.allocator, &.{ - sdk, "usr/include", - }); - return self; - } else if (is_windows) { - var sdk: ZigWindowsSDK = ZigWindowsSDK.find(args.allocator) catch |err| switch (err) { - error.NotFound => return error.WindowsSdkNotFound, - error.PathTooLong => return error.WindowsSdkNotFound, - error.OutOfMemory => return error.OutOfMemory, - }; - defer sdk.free(args.allocator); - - try self.findNativeMsvcIncludeDir(args, &sdk); - try self.findNativeMsvcLibDir(args, &sdk); - try self.findNativeKernel32LibDir(args, &sdk); - try self.findNativeIncludeDirWindows(args, &sdk); - try self.findNativeCrtDirWindows(args, &sdk); - } else if (is_haiku) { - try self.findNativeIncludeDirPosix(args); - try self.findNativeCrtBeginDirHaiku(args); - self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib"); - } else if (builtin.target.os.tag.isSolarish()) { - // There is only one libc, and its headers/libraries are always in the same spot. - self.include_dir = try args.allocator.dupeZ(u8, "/usr/include"); - self.sys_include_dir = try args.allocator.dupeZ(u8, "/usr/include"); - self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib/64"); - } else if (std.process.can_spawn) { - try self.findNativeIncludeDirPosix(args); - switch (builtin.target.os.tag) { - .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib"), - .linux => try self.findNativeCrtDirPosix(args), - else => {}, - } - } else { - return error.LibCRuntimeNotFound; - } - return self; - } - - /// Must be the same allocator passed to `parse` or `findNative`. - pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { - const fields = std.meta.fields(LibCInstallation); - inline for (fields) |field| { - if (@field(self, field.name)) |payload| { - allocator.free(payload); - } - } - self.* = undefined; - } - - fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { - const allocator = args.allocator; - - // Detect infinite loops. - var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { - error.Unexpected => unreachable, // WASI-only - else => |e| return e, - }; - defer env_map.deinit(); - const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { - if (std.mem.eql(u8, phase, "1")) { - try env_map.put(inf_loop_env_key, "2"); - break :blk true; - } else { - return error.ZigIsTheCCompiler; - } - } else blk: { - try env_map.put(inf_loop_env_key, "1"); - break :blk false; - }; - - const dev_null = if (is_windows) "nul" else "/dev/null"; - - var argv = std.ArrayList([]const u8).init(allocator); - defer argv.deinit(); - - try appendCcExe(&argv, skip_cc_env_var); - try argv.appendSlice(&.{ - "-E", - "-Wp,-v", - "-xc", - dev_null, - }); - - const run_res = std.ChildProcess.run(.{ - .allocator = allocator, - .argv = argv.items, - .max_output_bytes = 1024 * 1024, - .env_map = &env_map, - // Some C compilers, such as Clang, are known to rely on argv[0] to find the path - // to their own executable, without even bothering to resolve PATH. This results in the message: - // error: unable to execute command: Executable "" doesn't exist! - // So we use the expandArg0 variant of ChildProcess to give them a helping hand. - .expand_arg0 = .expand, - }) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - printVerboseInvocation(argv.items, null, args.verbose, null); - return error.UnableToSpawnCCompiler; - }, - }; - defer { - allocator.free(run_res.stdout); - allocator.free(run_res.stderr); - } - switch (run_res.term) { - .Exited => |code| if (code != 0) { - printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr); - return error.CCompilerExitCode; - }, - else => { - printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr); - return error.CCompilerCrashed; - }, - } - - var it = std.mem.tokenizeAny(u8, run_res.stderr, "\n\r"); - var search_paths = std.ArrayList([]const u8).init(allocator); - defer search_paths.deinit(); - while (it.next()) |line| { - if (line.len != 0 and line[0] == ' ') { - try search_paths.append(line); - } - } - if (search_paths.items.len == 0) { - return error.CCompilerCannotFindHeaders; - } - - const include_dir_example_file = if (is_haiku) "posix/stdlib.h" else "stdlib.h"; - const sys_include_dir_example_file = if (is_windows) - "sys\\types.h" - else if (is_haiku) - "errno.h" - else - "sys/errno.h"; - - var path_i: usize = 0; - while (path_i < search_paths.items.len) : (path_i += 1) { - // search in reverse order - const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1]; - const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " "); - var search_dir = fs.cwd().openDir(search_path, .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.NoDevice, - => continue, - - else => return error.FileSystem, - }; - defer search_dir.close(); - - if (self.include_dir == null) { - if (search_dir.accessZ(include_dir_example_file, .{})) |_| { - self.include_dir = try allocator.dupeZ(u8, search_path); - } else |err| switch (err) { - error.FileNotFound => {}, - else => return error.FileSystem, - } - } - - if (self.sys_include_dir == null) { - if (search_dir.accessZ(sys_include_dir_example_file, .{})) |_| { - self.sys_include_dir = try allocator.dupeZ(u8, search_path); - } else |err| switch (err) { - error.FileNotFound => {}, - else => return error.FileSystem, - } - } - - if (self.include_dir != null and self.sys_include_dir != null) { - // Success. - return; - } - } - - return error.LibCStdLibHeaderNotFound; - } - - fn findNativeIncludeDirWindows( - self: *LibCInstallation, - args: FindNativeOptions, - sdk: *ZigWindowsSDK, - ) FindError!void { - const allocator = args.allocator; - - var search_buf: [2]Search = undefined; - const searches = fillSearch(&search_buf, sdk); - - var result_buf = std.ArrayList(u8).init(allocator); - defer result_buf.deinit(); - - for (searches) |search| { - result_buf.shrinkAndFree(0); - try result_buf.writer().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version }); - - var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.NoDevice, - => continue, - - else => return error.FileSystem, - }; - defer dir.close(); - - dir.accessZ("stdlib.h", .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => return error.FileSystem, - }; - - self.include_dir = try result_buf.toOwnedSlice(); - return; - } - - return error.LibCStdLibHeaderNotFound; - } - - fn findNativeCrtDirWindows( - self: *LibCInstallation, - args: FindNativeOptions, - sdk: *ZigWindowsSDK, - ) FindError!void { - const allocator = args.allocator; - - var search_buf: [2]Search = undefined; - const searches = fillSearch(&search_buf, sdk); - - var result_buf = std.ArrayList(u8).init(allocator); - defer result_buf.deinit(); - - const arch_sub_dir = switch (builtin.target.cpu.arch) { - .x86 => "x86", - .x86_64 => "x64", - .arm, .armeb => "arm", - .aarch64 => "arm64", - else => return error.UnsupportedArchitecture, - }; - - for (searches) |search| { - result_buf.shrinkAndFree(0); - try result_buf.writer().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir }); - - var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.NoDevice, - => continue, - - else => return error.FileSystem, - }; - defer dir.close(); - - dir.accessZ("ucrt.lib", .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => return error.FileSystem, - }; - - self.crt_dir = try result_buf.toOwnedSlice(); - return; - } - return error.LibCRuntimeNotFound; - } - - fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { - self.crt_dir = try ccPrintFileName(.{ - .allocator = args.allocator, - .search_basename = "crt1.o", - .want_dirname = .only_dir, - .verbose = args.verbose, - }); - } - - fn findNativeCrtBeginDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindError!void { - self.gcc_dir = try ccPrintFileName(.{ - .allocator = args.allocator, - .search_basename = "crtbeginS.o", - .want_dirname = .only_dir, - .verbose = args.verbose, - }); - } - - fn findNativeKernel32LibDir( - self: *LibCInstallation, - args: FindNativeOptions, - sdk: *ZigWindowsSDK, - ) FindError!void { - const allocator = args.allocator; - - var search_buf: [2]Search = undefined; - const searches = fillSearch(&search_buf, sdk); - - var result_buf = std.ArrayList(u8).init(allocator); - defer result_buf.deinit(); - - const arch_sub_dir = switch (builtin.target.cpu.arch) { - .x86 => "x86", - .x86_64 => "x64", - .arm, .armeb => "arm", - .aarch64 => "arm64", - else => return error.UnsupportedArchitecture, - }; - - for (searches) |search| { - result_buf.shrinkAndFree(0); - const stream = result_buf.writer(); - try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ search.path, search.version, arch_sub_dir }); - - var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.NoDevice, - => continue, - - else => return error.FileSystem, - }; - defer dir.close(); - - dir.accessZ("kernel32.lib", .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => return error.FileSystem, - }; - - self.kernel32_lib_dir = try result_buf.toOwnedSlice(); - return; - } - return error.LibCKernel32LibNotFound; - } - - fn findNativeMsvcIncludeDir( - self: *LibCInstallation, - args: FindNativeOptions, - sdk: *ZigWindowsSDK, - ) FindError!void { - const allocator = args.allocator; - - const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound; - const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound; - const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound; - - const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" }); - errdefer allocator.free(dir_path); - - var dir = fs.cwd().openDir(dir_path, .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.NoDevice, - => return error.LibCStdLibHeaderNotFound, - - else => return error.FileSystem, - }; - defer dir.close(); - - dir.accessZ("vcruntime.h", .{}) catch |err| switch (err) { - error.FileNotFound => return error.LibCStdLibHeaderNotFound, - else => return error.FileSystem, - }; - - self.sys_include_dir = dir_path; - } - - fn findNativeMsvcLibDir( - self: *LibCInstallation, - args: FindNativeOptions, - sdk: *ZigWindowsSDK, - ) FindError!void { - const allocator = args.allocator; - const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCRuntimeNotFound; - self.msvc_lib_dir = try allocator.dupe(u8, msvc_lib_dir); - } -}; - -pub const CCPrintFileNameOptions = struct { - allocator: Allocator, - search_basename: []const u8, - want_dirname: enum { full_path, only_dir }, - verbose: bool = false, -}; - -/// caller owns returned memory -fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { - const allocator = args.allocator; - - // Detect infinite loops. - var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { - error.Unexpected => unreachable, // WASI-only - else => |e| return e, - }; - defer env_map.deinit(); - const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { - if (std.mem.eql(u8, phase, "1")) { - try env_map.put(inf_loop_env_key, "2"); - break :blk true; - } else { - return error.ZigIsTheCCompiler; - } - } else blk: { - try env_map.put(inf_loop_env_key, "1"); - break :blk false; - }; - - var argv = std.ArrayList([]const u8).init(allocator); - defer argv.deinit(); - - const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename}); - defer allocator.free(arg1); - - try appendCcExe(&argv, skip_cc_env_var); - try argv.append(arg1); - - const run_res = std.ChildProcess.run(.{ - .allocator = allocator, - .argv = argv.items, - .max_output_bytes = 1024 * 1024, - .env_map = &env_map, - // Some C compilers, such as Clang, are known to rely on argv[0] to find the path - // to their own executable, without even bothering to resolve PATH. This results in the message: - // error: unable to execute command: Executable "" doesn't exist! - // So we use the expandArg0 variant of ChildProcess to give them a helping hand. - .expand_arg0 = .expand, - }) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => return error.UnableToSpawnCCompiler, - }; - defer { - allocator.free(run_res.stdout); - allocator.free(run_res.stderr); - } - switch (run_res.term) { - .Exited => |code| if (code != 0) { - printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr); - return error.CCompilerExitCode; - }, - else => { - printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr); - return error.CCompilerCrashed; - }, - } - - var it = std.mem.tokenizeAny(u8, run_res.stdout, "\n\r"); - const line = it.next() orelse return error.LibCRuntimeNotFound; - // When this command fails, it returns exit code 0 and duplicates the input file name. - // So we detect failure by checking if the output matches exactly the input. - if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound; - switch (args.want_dirname) { - .full_path => return allocator.dupeZ(u8, line), - .only_dir => { - const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound; - return allocator.dupeZ(u8, dirname); - }, - } -} - -fn printVerboseInvocation( - argv: []const []const u8, - search_basename: ?[]const u8, - verbose: bool, - stderr: ?[]const u8, -) void { - if (!verbose) return; - - if (search_basename) |s| { - std.debug.print("Zig attempted to find the file '{s}' by executing this command:\n", .{s}); - } else { - std.debug.print("Zig attempted to find the path to native system libc headers by executing this command:\n", .{}); - } - for (argv, 0..) |arg, i| { - if (i != 0) std.debug.print(" ", .{}); - std.debug.print("{s}", .{arg}); - } - std.debug.print("\n", .{}); - if (stderr) |s| { - std.debug.print("Output:\n==========\n{s}\n==========\n", .{s}); - } -} - -const Search = struct { - path: []const u8, - version: []const u8, -}; - -fn fillSearch(search_buf: *[2]Search, sdk: *ZigWindowsSDK) []Search { - var search_end: usize = 0; - if (sdk.windows10sdk) |windows10sdk| { - search_buf[search_end] = .{ - .path = windows10sdk.path, - .version = windows10sdk.version, - }; - search_end += 1; - } - if (sdk.windows81sdk) |windows81sdk| { - search_buf[search_end] = .{ - .path = windows81sdk.path, - .version = windows81sdk.version, - }; - search_end += 1; - } - return search_buf[0..search_end]; -} - -const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS"; - -fn appendCcExe(args: *std.ArrayList([]const u8), skip_cc_env_var: bool) !void { - const default_cc_exe = if (is_windows) "cc.exe" else "cc"; - try args.ensureUnusedCapacity(1); - if (skip_cc_env_var) { - args.appendAssumeCapacity(default_cc_exe); - return; - } - const cc_env_var = EnvVar.CC.getPosix() orelse { - args.appendAssumeCapacity(default_cc_exe); - return; - }; - // Respect space-separated flags to the C compiler. - var it = std.mem.tokenizeScalar(u8, cc_env_var, ' '); - while (it.next()) |arg| { - try args.append(arg); - } -} diff --git a/src/link.zig b/src/link.zig @@ -12,7 +12,7 @@ const Air = @import("Air.zig"); const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const Compilation = @import("Compilation.zig"); -const LibCInstallation = @import("libc_installation.zig").LibCInstallation; +const LibCInstallation = std.zig.LibCInstallation; const Liveness = @import("Liveness.zig"); const Module = @import("Module.zig"); const InternPool = @import("InternPool.zig"); diff --git a/src/link/MachO.zig b/src/link/MachO.zig @@ -4616,13 +4616,7 @@ const SystemLib = struct { must_link: bool = false, }; -/// The filesystem layout of darwin SDK elements. -pub const SdkLayout = enum { - /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }. - sdk, - /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }. - vendored, -}; +pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout; const UndefinedTreatment = enum { @"error", diff --git a/src/main.zig b/src/main.zig @@ -19,8 +19,8 @@ const link = @import("link.zig"); const Package = @import("Package.zig"); const build_options = @import("build_options"); const introspect = @import("introspect.zig"); -const EnvVar = introspect.EnvVar; -const LibCInstallation = @import("libc_installation.zig").LibCInstallation; +const EnvVar = std.zig.EnvVar; +const LibCInstallation = std.zig.LibCInstallation; const wasi_libc = @import("wasi_libc.zig"); const Cache = std.Build.Cache; const target_util = @import("target.zig"); @@ -294,17 +294,17 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, cmd, "rc")) { return cmdRc(gpa, arena, args[1..]); } else if (mem.eql(u8, cmd, "fmt")) { - return jitCmd(gpa, arena, cmd_args, "fmt", "fmt.zig"); + return jitCmd(gpa, arena, cmd_args, "fmt", "fmt.zig", false); } else if (mem.eql(u8, cmd, "objcopy")) { return @import("objcopy.zig").cmdObjCopy(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "fetch")) { return cmdFetch(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "libc")) { - return cmdLibC(gpa, cmd_args); + return jitCmd(gpa, arena, cmd_args, "libc", "libc.zig", true); } else if (mem.eql(u8, cmd, "init")) { return cmdInit(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { - const host = resolveTargetQueryOrFatal(.{}); + const host = std.zig.resolveTargetQueryOrFatal(.{}); const stdout = io.getStdOut().writer(); return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, host); } else if (mem.eql(u8, cmd, "version")) { @@ -317,7 +317,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { verifyLibcxxCorrectlyLinked(); return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer()); } else if (mem.eql(u8, cmd, "reduce")) { - return jitCmd(gpa, arena, cmd_args, "reduce", "reduce.zig"); + return jitCmd(gpa, arena, cmd_args, "reduce", "reduce.zig", false); } else if (mem.eql(u8, cmd, "zen")) { return io.getStdOut().writeAll(info_zen); } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { @@ -3259,7 +3259,7 @@ fn buildOutputType( const triple_name = try target.zigTriple(arena); std.log.err("unable to find or provide libc for target '{s}'", .{triple_name}); - for (target_util.available_libcs) |t| { + for (std.zig.target.available_libcs) |t| { if (t.arch == target.cpu.arch and t.os == target.os.tag) { if (t.os_ver) |os_ver| { std.log.info("zig can provide libc for related target {s}-{s}.{d}-{s}", .{ @@ -3530,16 +3530,16 @@ fn createModule( } } - const target_query = parseTargetQueryOrReportFatalError(arena, target_parse_options); + const target_query = std.zig.parseTargetQueryOrReportFatalError(arena, target_parse_options); const adjusted_target_query = a: { if (!target_query.isNative()) break :a target_query; if (create_module.host_triple) |triple| target_parse_options.arch_os_abi = triple; if (create_module.host_cpu) |cpu| target_parse_options.cpu_features = cpu; if (create_module.host_dynamic_linker) |dl| target_parse_options.dynamic_linker = dl; - break :a parseTargetQueryOrReportFatalError(arena, target_parse_options); + break :a std.zig.parseTargetQueryOrReportFatalError(arena, target_parse_options); }; - const target = resolveTargetQueryOrFatal(adjusted_target_query); + const target = std.zig.resolveTargetQueryOrFatal(adjusted_target_query); break :t .{ .result = target, .is_native_os = target_query.isNativeOs(), @@ -4210,59 +4210,6 @@ fn serveUpdateResults(s: *Server, comp: *Compilation) !void { } } -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).init(allocator); - defer help_text.deinit(); - for (diags.arch.?.allCpuModels()) |cpu| { - help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help; - } - std.log.info("available CPUs for architecture '{s}':\n{s}", .{ - @tagName(diags.arch.?), help_text.items, - }); - } - fatal("unknown CPU: '{s}'", .{diags.cpu_name.?}); - }, - error.UnknownCpuFeature => { - help: { - var help_text = std.ArrayList(u8).init(allocator); - defer help_text.deinit(); - for (diags.arch.?.allFeaturesList()) |feature| { - help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; - } - std.log.info("available CPU features for architecture '{s}':\n{s}", .{ - @tagName(diags.arch.?), help_text.items, - }); - } - fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?}); - }, - error.UnknownObjectFormat => { - help: { - var help_text = std.ArrayList(u8).init(allocator); - defer help_text.deinit(); - inline for (@typeInfo(std.Target.ObjectFormat).Enum.fields) |field| { - help_text.writer().print(" {s}\n", .{field.name}) catch break :help; - } - std.log.info("available object formats:\n{s}", .{help_text.items}); - } - fatal("unknown object format: '{s}'", .{opts.object_format.?}); - }, - else => |e| fatal("unable to parse target query '{s}': {s}", .{ - opts.arch_os_abi, @errorName(e), - }), - }; -} - fn runOrTest( comp: *Compilation, gpa: Allocator, @@ -4871,9 +4818,9 @@ fn detectRcIncludeDirs(arena: Allocator, zig_lib_dir: []const u8, auto_includes: .os_tag = .windows, .abi = .msvc, }; - const target = resolveTargetQueryOrFatal(target_query); + const target = std.zig.resolveTargetQueryOrFatal(target_query); const is_native_abi = target_query.isNativeAbi(); - const detected_libc = Compilation.detectLibCIncludeDirs(arena, zig_lib_dir, target, is_native_abi, true, null) catch |err| { + const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, target, is_native_abi, true, null) catch |err| { if (cur_includes == .any) { // fall back to mingw cur_includes = .gnu; @@ -4899,9 +4846,9 @@ fn detectRcIncludeDirs(arena: Allocator, zig_lib_dir: []const u8, auto_includes: .os_tag = .windows, .abi = .gnu, }; - const target = resolveTargetQueryOrFatal(target_query); + const target = std.zig.resolveTargetQueryOrFatal(target_query); const is_native_abi = target_query.isNativeAbi(); - const detected_libc = try Compilation.detectLibCIncludeDirs(arena, zig_lib_dir, target, is_native_abi, true, null); + const detected_libc = try std.zig.LibCDirs.detect(arena, zig_lib_dir, target, is_native_abi, true, null); return .{ .include_paths = detected_libc.libc_include_dir_list, .target_abi = "gnu", @@ -4912,136 +4859,6 @@ fn detectRcIncludeDirs(arena: Allocator, zig_lib_dir: []const u8, auto_includes: } } -const usage_libc = - \\Usage: zig libc - \\ - \\ Detect the native libc installation and print the resulting - \\ paths to stdout. You can save this into a file and then edit - \\ the paths to create a cross compilation libc kit. Then you - \\ can pass `--libc [file]` for Zig to use it. - \\ - \\Usage: zig libc [paths_file] - \\ - \\ Parse a libc installation text file and validate it. - \\ - \\Options: - \\ -h, --help Print this help and exit - \\ -target [name] <arch><sub>-<os>-<abi> see the targets command - \\ -includes Print the libc include directories for the target - \\ -; - -fn cmdLibC(gpa: Allocator, args: []const []const u8) !void { - var input_file: ?[]const u8 = null; - var target_arch_os_abi: []const u8 = "native"; - var print_includes: bool = false; - { - var i: usize = 0; - while (i < args.len) : (i += 1) { - const arg = args[i]; - if (mem.startsWith(u8, arg, "-")) { - if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - const stdout = io.getStdOut().writer(); - try stdout.writeAll(usage_libc); - return cleanExit(); - } else if (mem.eql(u8, arg, "-target")) { - if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); - i += 1; - target_arch_os_abi = args[i]; - } else if (mem.eql(u8, arg, "-includes")) { - print_includes = true; - } else { - fatal("unrecognized parameter: '{s}'", .{arg}); - } - } else if (input_file != null) { - fatal("unexpected extra parameter: '{s}'", .{arg}); - } else { - input_file = arg; - } - } - } - - const target_query = parseTargetQueryOrReportFatalError(gpa, .{ - .arch_os_abi = target_arch_os_abi, - }); - const target = resolveTargetQueryOrFatal(target_query); - - if (print_includes) { - var arena_state = std.heap.ArenaAllocator.init(gpa); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - const libc_installation: ?*LibCInstallation = libc: { - if (input_file) |libc_file| { - const libc = try arena.create(LibCInstallation); - libc.* = LibCInstallation.parse(arena, libc_file, target) catch |err| { - fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) }); - }; - break :libc libc; - } else { - break :libc null; - } - }; - - const self_exe_path = try introspect.findZigExePath(arena); - var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { - fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); - }; - defer zig_lib_directory.handle.close(); - - const is_native_abi = target_query.isNativeAbi(); - - const libc_dirs = Compilation.detectLibCIncludeDirs( - arena, - zig_lib_directory.path.?, - target, - is_native_abi, - true, - libc_installation, - ) catch |err| { - const zig_target = try target.zigTriple(arena); - fatal("unable to detect libc for target {s}: {s}", .{ zig_target, @errorName(err) }); - }; - - if (libc_dirs.libc_include_dir_list.len == 0) { - const zig_target = try target.zigTriple(arena); - fatal("no include dirs detected for target {s}", .{zig_target}); - } - - var bw = io.bufferedWriter(io.getStdOut().writer()); - var writer = bw.writer(); - for (libc_dirs.libc_include_dir_list) |include_dir| { - try writer.writeAll(include_dir); - try writer.writeByte('\n'); - } - try bw.flush(); - return cleanExit(); - } - - if (input_file) |libc_file| { - var libc = LibCInstallation.parse(gpa, libc_file, target) catch |err| { - fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) }); - }; - defer libc.deinit(gpa); - } else { - if (!target_query.isNative()) { - fatal("unable to detect libc for non-native target", .{}); - } - var libc = LibCInstallation.findNative(.{ - .allocator = gpa, - .verbose = true, - .target = target, - }) catch |err| { - fatal("unable to detect native libc: {s}", .{@errorName(err)}); - }; - defer libc.deinit(gpa); - - var bw = io.bufferedWriter(io.getStdOut().writer()); - try libc.render(bw.writer()); - try bw.flush(); - } -} - const usage_init = \\Usage: zig init \\ @@ -5293,7 +5110,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { const target_query: std.Target.Query = .{}; const resolved_target: Package.Module.ResolvedTarget = .{ - .result = resolveTargetQueryOrFatal(target_query), + .result = std.zig.resolveTargetQueryOrFatal(target_query), .is_native_os = true, .is_native_abi = true, }; @@ -5711,12 +5528,13 @@ fn jitCmd( args: []const []const u8, cmd_name: []const u8, root_src_path: []const u8, + prepend_zig_lib_dir_path: bool, ) !void { const color: Color = .auto; const target_query: std.Target.Query = .{}; const resolved_target: Package.Module.ResolvedTarget = .{ - .result = resolveTargetQueryOrFatal(target_query), + .result = std.zig.resolveTargetQueryOrFatal(target_query), .is_native_os = true, .is_native_abi = true, }; @@ -5766,7 +5584,7 @@ fn jitCmd( defer thread_pool.deinit(); var child_argv: std.ArrayListUnmanaged([]const u8) = .{}; - try child_argv.ensureUnusedCapacity(arena, args.len + 1); + try child_argv.ensureUnusedCapacity(arena, args.len + 2); // We want to release all the locks before executing the child process, so we make a nice // big block here to ensure the cleanup gets run when we extract out our argv. @@ -5829,6 +5647,9 @@ fn jitCmd( child_argv.appendAssumeCapacity(exe_path); } + if (prepend_zig_lib_dir_path) + child_argv.appendAssumeCapacity(zig_lib_directory.path.?); + child_argv.appendSliceAssumeCapacity(args); if (process.can_execv) { @@ -6703,7 +6524,7 @@ fn warnAboutForeignBinaries( link_libc: bool, ) !void { const host_query: std.Target.Query = .{}; - const host_target = resolveTargetQueryOrFatal(host_query); + const host_target = std.zig.resolveTargetQueryOrFatal(host_query); switch (std.zig.system.getExternalExecutor(host_target, target, .{ .link_libc = link_libc })) { .native => return, @@ -7559,11 +7380,6 @@ fn parseWasiExecModel(s: []const u8) std.builtin.WasiExecModel { fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{s}); } -fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target { - return std.zig.system.resolveTargetQuery(target_query) catch |err| - fatal("unable to resolve target: {s}", .{@errorName(err)}); -} - fn parseStackSize(s: []const u8) u64 { return std.fmt.parseUnsigned(u64, s, 0) catch |err| fatal("unable to parse stack size '{s}': {s}", .{ s, @errorName(err) }); diff --git a/src/musl.zig b/src/musl.zig @@ -4,6 +4,7 @@ const mem = std.mem; const path = std.fs.path; const assert = std.debug.assert; const Module = @import("Package/Module.zig"); +const archName = std.zig.target.muslArchName; const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); @@ -294,30 +295,6 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progr } } -fn archName(arch: std.Target.Cpu.Arch) [:0]const u8 { - switch (arch) { - .aarch64, .aarch64_be => return "aarch64", - .arm, .armeb, .thumb, .thumbeb => return "arm", - .x86 => return "i386", - .mips, .mipsel => return "mips", - .mips64el, .mips64 => return "mips64", - .powerpc => return "powerpc", - .powerpc64, .powerpc64le => return "powerpc64", - .riscv64 => return "riscv64", - .s390x => return "s390x", - .wasm32, .wasm64 => return "wasm", - .x86_64 => return "x86_64", - else => unreachable, - } -} - -pub fn archNameHeaders(arch: std.Target.Cpu.Arch) [:0]const u8 { - return switch (arch) { - .x86 => return "x86", - else => archName(arch), - }; -} - // Return true if musl has arch-specific crti/crtn sources. // See lib/libc/musl/crt/ARCH/crt?.s . pub fn needsCrtiCrtn(target: std.Target) bool { @@ -405,7 +382,7 @@ fn addCcArgs( const arch_name = archName(target.cpu.arch); const os_name = @tagName(target.os.tag); const triple = try std.fmt.allocPrint(arena, "{s}-{s}-musl", .{ - archNameHeaders(target.cpu.arch), os_name, + std.zig.target.muslArchNameHeaders(target.cpu.arch), os_name, }); const o_arg = if (want_O3) "-O3" else "-Os"; diff --git a/src/print_env.zig b/src/print_env.zig @@ -47,9 +47,9 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8, stdout: std.fs.File.Wr try jws.objectField("env"); try jws.beginObject(); - inline for (@typeInfo(introspect.EnvVar).Enum.fields) |field| { + inline for (@typeInfo(std.zig.EnvVar).Enum.fields) |field| { try jws.objectField(field.name); - try jws.write(try @field(introspect.EnvVar, field.name).get(arena)); + try jws.write(try @field(std.zig.EnvVar, field.name).get(arena)); } try jws.endObject(); diff --git a/src/print_targets.zig b/src/print_targets.zig @@ -67,7 +67,7 @@ pub fn cmdTargets( try jws.objectField("libc"); try jws.beginArray(); - for (target.available_libcs) |libc| { + for (std.zig.target.available_libcs) |libc| { const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), }); diff --git a/src/target.zig b/src/target.zig @@ -6,169 +6,6 @@ const Feature = @import("Module.zig").Feature; pub const default_stack_protector_buffer_size = 4; -pub const ArchOsAbi = struct { - arch: std.Target.Cpu.Arch, - os: std.Target.Os.Tag, - abi: std.Target.Abi, - os_ver: ?std.SemanticVersion = null, - - // Minimum glibc version that provides support for the arch/os when ABI is GNU. - glibc_min: ?std.SemanticVersion = null, -}; - -pub const available_libcs = [_]ArchOsAbi{ - .{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } }, - .{ .arch = .aarch64_be, .os = .linux, .abi = .musl }, - .{ .arch = .aarch64_be, .os = .windows, .abi = .gnu }, - .{ .arch = .aarch64, .os = .linux, .abi = .gnu }, - .{ .arch = .aarch64, .os = .linux, .abi = .musl }, - .{ .arch = .aarch64, .os = .windows, .abi = .gnu }, - .{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } }, - .{ .arch = .armeb, .os = .linux, .abi = .gnueabi }, - .{ .arch = .armeb, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .armeb, .os = .linux, .abi = .musleabi }, - .{ .arch = .armeb, .os = .linux, .abi = .musleabihf }, - .{ .arch = .armeb, .os = .windows, .abi = .gnu }, - .{ .arch = .arm, .os = .linux, .abi = .gnueabi }, - .{ .arch = .arm, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .arm, .os = .linux, .abi = .musleabi }, - .{ .arch = .arm, .os = .linux, .abi = .musleabihf }, - .{ .arch = .thumb, .os = .linux, .abi = .gnueabi }, - .{ .arch = .thumb, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .thumb, .os = .linux, .abi = .musleabi }, - .{ .arch = .thumb, .os = .linux, .abi = .musleabihf }, - .{ .arch = .arm, .os = .windows, .abi = .gnu }, - .{ .arch = .csky, .os = .linux, .abi = .gnueabi }, - .{ .arch = .csky, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .x86, .os = .linux, .abi = .gnu }, - .{ .arch = .x86, .os = .linux, .abi = .musl }, - .{ .arch = .x86, .os = .windows, .abi = .gnu }, - .{ .arch = .m68k, .os = .linux, .abi = .gnu }, - .{ .arch = .m68k, .os = .linux, .abi = .musl }, - .{ .arch = .mips64el, .os = .linux, .abi = .gnuabi64 }, - .{ .arch = .mips64el, .os = .linux, .abi = .gnuabin32 }, - .{ .arch = .mips64el, .os = .linux, .abi = .musl }, - .{ .arch = .mips64, .os = .linux, .abi = .gnuabi64 }, - .{ .arch = .mips64, .os = .linux, .abi = .gnuabin32 }, - .{ .arch = .mips64, .os = .linux, .abi = .musl }, - .{ .arch = .mipsel, .os = .linux, .abi = .gnueabi }, - .{ .arch = .mipsel, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .mipsel, .os = .linux, .abi = .musl }, - .{ .arch = .mips, .os = .linux, .abi = .gnueabi }, - .{ .arch = .mips, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .mips, .os = .linux, .abi = .musl }, - .{ .arch = .powerpc64le, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 19, .patch = 0 } }, - .{ .arch = .powerpc64le, .os = .linux, .abi = .musl }, - .{ .arch = .powerpc64, .os = .linux, .abi = .gnu }, - .{ .arch = .powerpc64, .os = .linux, .abi = .musl }, - .{ .arch = .powerpc, .os = .linux, .abi = .gnueabi }, - .{ .arch = .powerpc, .os = .linux, .abi = .gnueabihf }, - .{ .arch = .powerpc, .os = .linux, .abi = .musl }, - .{ .arch = .riscv64, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 27, .patch = 0 } }, - .{ .arch = .riscv64, .os = .linux, .abi = .musl }, - .{ .arch = .s390x, .os = .linux, .abi = .gnu }, - .{ .arch = .s390x, .os = .linux, .abi = .musl }, - .{ .arch = .sparc, .os = .linux, .abi = .gnu }, - .{ .arch = .sparc64, .os = .linux, .abi = .gnu }, - .{ .arch = .wasm32, .os = .freestanding, .abi = .musl }, - .{ .arch = .wasm32, .os = .wasi, .abi = .musl }, - .{ .arch = .x86_64, .os = .linux, .abi = .gnu }, - .{ .arch = .x86_64, .os = .linux, .abi = .gnux32 }, - .{ .arch = .x86_64, .os = .linux, .abi = .musl }, - .{ .arch = .x86_64, .os = .windows, .abi = .gnu }, - .{ .arch = .x86_64, .os = .macos, .abi = .none, .os_ver = .{ .major = 10, .minor = 7, .patch = 0 } }, -}; - -pub fn libCGenericName(target: std.Target) [:0]const u8 { - switch (target.os.tag) { - .windows => return "mingw", - .macos, .ios, .tvos, .watchos => return "darwin", - else => {}, - } - switch (target.abi) { - .gnu, - .gnuabin32, - .gnuabi64, - .gnueabi, - .gnueabihf, - .gnuf32, - .gnuf64, - .gnusf, - .gnux32, - .gnuilp32, - => return "glibc", - .musl, - .musleabi, - .musleabihf, - .muslx32, - .none, - => return "musl", - .code16, - .eabi, - .eabihf, - .android, - .msvc, - .itanium, - .cygnus, - .coreclr, - .simulator, - .macabi, - => unreachable, - - .pixel, - .vertex, - .geometry, - .hull, - .domain, - .compute, - .library, - .raygeneration, - .intersection, - .anyhit, - .closesthit, - .miss, - .callable, - .mesh, - .amplification, - => unreachable, - } -} - -pub fn osArchName(target: std.Target) [:0]const u8 { - return switch (target.os.tag) { - .linux => switch (target.cpu.arch) { - .arm, .armeb, .thumb, .thumbeb => "arm", - .aarch64, .aarch64_be, .aarch64_32 => "aarch64", - .mips, .mipsel, .mips64, .mips64el => "mips", - .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", - .riscv32, .riscv64 => "riscv", - .sparc, .sparcel, .sparc64 => "sparc", - .x86, .x86_64 => "x86", - else => @tagName(target.cpu.arch), - }, - else => @tagName(target.cpu.arch), - }; -} - -pub fn canBuildLibC(target: std.Target) bool { - for (available_libcs) |libc| { - if (target.cpu.arch == libc.arch and target.os.tag == libc.os and target.abi == libc.abi) { - if (target.os.tag == .macos) { - const ver = target.os.version_range.semver; - return ver.min.order(libc.os_ver.?) != .lt; - } - // Ensure glibc (aka *-linux-gnu) version is supported - if (target.isGnuLibC()) { - const min_glibc_ver = libc.glibc_min orelse return true; - const target_glibc_ver = target.os.version_range.linux.glibc; - return target_glibc_ver.order(min_glibc_ver) != .lt; - } - return true; - } - } - return false; -} - pub fn cannotDynamicLink(target: std.Target) bool { return switch (target.os.tag) { .freestanding, .other => true, diff --git a/src/wasi_libc.zig b/src/wasi_libc.zig @@ -5,8 +5,6 @@ const path = std.fs.path; const Allocator = std.mem.Allocator; const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); -const target_util = @import("target.zig"); -const musl = @import("musl.zig"); pub const CRTFile = enum { crt1_reactor_o, @@ -273,7 +271,7 @@ fn addCCArgs( options: CCOptions, ) error{OutOfMemory}!void { const target = comp.getTarget(); - const arch_name = musl.archNameHeaders(target.cpu.arch); + const arch_name = std.zig.target.muslArchNameHeaders(target.cpu.arch); const os_name = @tagName(target.os.tag); const triple = try std.fmt.allocPrint(arena, "{s}-{s}-musl", .{ arch_name, os_name }); const o_arg = if (options.want_O3) "-O3" else "-Os"; diff --git a/src/windows_sdk.zig b/src/windows_sdk.zig @@ -1,965 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const windows = std.os.windows; -const RRF = windows.advapi32.RRF; - -const WINDOWS_KIT_REG_KEY = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots"; - -// https://learn.microsoft.com/en-us/windows/win32/msi/productversion -const version_major_minor_max_length = "255.255".len; -// note(bratishkaerik): i think ProductVersion in registry (created by Visual Studio installer) also follows this rule -const product_version_max_length = version_major_minor_max_length + ".65535".len; - -/// Iterates via `iterator` and collects all folders with names starting with `optional_prefix` -/// and similar to SemVer. Returns slice of folder names sorted in descending order. -/// Caller owns result. -fn iterateAndFilterBySemVer( - iterator: *std.fs.Dir.Iterator, - allocator: std.mem.Allocator, - comptime optional_prefix: ?[]const u8, -) error{ OutOfMemory, VersionNotFound }![][]const u8 { - var dirs_filtered_list = std.ArrayList([]const u8).init(allocator); - errdefer { - for (dirs_filtered_list.items) |filtered_dir| allocator.free(filtered_dir); - dirs_filtered_list.deinit(); - } - - var normalized_name_buf: [std.fs.MAX_NAME_BYTES + ".0+build.0".len]u8 = undefined; - var normalized_name_fbs = std.io.fixedBufferStream(&normalized_name_buf); - const normalized_name_w = normalized_name_fbs.writer(); - iterate_folder: while (true) : (normalized_name_fbs.reset()) { - const maybe_entry = iterator.next() catch continue :iterate_folder; - const entry = maybe_entry orelse break :iterate_folder; - - if (entry.kind != .directory) - continue :iterate_folder; - - // invalidated on next iteration - const subfolder_name = blk: { - if (comptime optional_prefix) |prefix| { - if (!std.mem.startsWith(u8, entry.name, prefix)) continue :iterate_folder; - break :blk entry.name[prefix.len..]; - } else break :blk entry.name; - }; - - { // check if subfolder name looks similar to SemVer - switch (std.mem.count(u8, subfolder_name, ".")) { - 0 => normalized_name_w.print("{s}.0.0+build.0", .{subfolder_name}) catch unreachable, // 17 => 17.0.0+build.0 - 1 => if (std.mem.indexOfScalar(u8, subfolder_name, '_')) |underscore_pos| blk: { // 17.0_9e9cbb98 => 17.0.1+build.9e9cbb98 - var subfolder_name_tmp_copy_buf: [std.fs.MAX_NAME_BYTES]u8 = undefined; - const subfolder_name_tmp_copy = subfolder_name_tmp_copy_buf[0..subfolder_name.len]; - @memcpy(subfolder_name_tmp_copy, subfolder_name); - - subfolder_name_tmp_copy[underscore_pos] = '.'; // 17.0_9e9cbb98 => 17.0.9e9cbb98 - var subfolder_name_parts = std.mem.splitScalar(u8, subfolder_name_tmp_copy, '.'); // [ 17, 0, 9e9cbb98 ] - - const first = subfolder_name_parts.first(); // 17 - const second = subfolder_name_parts.next().?; // 0 - const third = subfolder_name_parts.rest(); // 9e9cbb98 - - break :blk normalized_name_w.print("{s}.{s}.1+build.{s}", .{ first, second, third }) catch unreachable; // [ 17, 0, 9e9cbb98 ] => 17.0.1+build.9e9cbb98 - } else normalized_name_w.print("{s}.0+build.0", .{subfolder_name}) catch unreachable, // 17.0 => 17.0.0+build.0 - else => normalized_name_w.print("{s}+build.0", .{subfolder_name}) catch unreachable, // 17.0.0 => 17.0.0+build.0 - } - const subfolder_name_normalized: []const u8 = normalized_name_fbs.getWritten(); - const sem_ver = std.SemanticVersion.parse(subfolder_name_normalized); - _ = sem_ver catch continue :iterate_folder; - } - // entry.name passed check - - const subfolder_name_allocated = try allocator.dupe(u8, subfolder_name); - errdefer allocator.free(subfolder_name_allocated); - try dirs_filtered_list.append(subfolder_name_allocated); - } - - const dirs_filtered_slice = try dirs_filtered_list.toOwnedSlice(); - // Keep in mind that order of these names is not guaranteed by Windows, - // so we cannot just reverse or "while (popOrNull())" this ArrayList. - std.mem.sortUnstable([]const u8, dirs_filtered_slice, {}, struct { - fn desc(_: void, lhs: []const u8, rhs: []const u8) bool { - return std.mem.order(u8, lhs, rhs) == .gt; - } - }.desc); - return dirs_filtered_slice; -} - -const RegistryWtf8 = struct { - key: windows.HKEY, - - /// Assert that `key` is valid WTF-8 string - pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 { - const key_wtf16le: [:0]const u16 = key_wtf16le: { - var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; - const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) { - error.InvalidWtf8 => unreachable, - }; - key_wtf16le_buf[key_wtf16le_len] = 0; - break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0]; - }; - - const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le); - return RegistryWtf8{ .key = registry_wtf16le.key }; - } - - /// Closes key, after that usage is invalid - pub fn closeKey(self: *const RegistryWtf8) void { - const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key); - const return_code: windows.Win32Error = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => {}, - else => {}, - } - } - - /// Get string from registry. - /// Caller owns result. - pub fn getString(self: *const RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { - const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { - var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; - const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; - subkey_wtf16le_buf[subkey_wtf16le_len] = 0; - break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0]; - }; - - const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: { - var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; - const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable; - value_name_wtf16le_buf[value_name_wtf16le_len] = 0; - break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0]; - }; - - const registry_wtf16le = RegistryWtf16Le{ .key = self.key }; - const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le); - defer allocator.free(value_wtf16le); - - const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le); - errdefer allocator.free(value_wtf8); - - return value_wtf8; - } - - /// Get DWORD (u32) from registry. - pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { - const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { - var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; - const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; - subkey_wtf16le_buf[subkey_wtf16le_len] = 0; - break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0]; - }; - - const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: { - var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; - const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable; - value_name_wtf16le_buf[value_name_wtf16le_len] = 0; - break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0]; - }; - - const registry_wtf16le = RegistryWtf16Le{ .key = self.key }; - return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le); - } - - /// Under private space with flags: - /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS. - /// After finishing work, call `closeKey`. - pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 { - const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: { - var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; - const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable; - absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0; - break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0]; - }; - - const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le); - return RegistryWtf8{ .key = registry_wtf16le.key }; - } -}; - -const RegistryWtf16Le = struct { - key: windows.HKEY, - - /// Includes root key (f.e. HKEY_LOCAL_MACHINE). - /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits - pub const key_name_max_len = 255; - /// In Unicode characters. - /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits - pub const value_name_max_len = 16_383; - - /// Under HKEY_LOCAL_MACHINE with flags: - /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS. - /// After finishing work, call `closeKey`. - fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { - var key: windows.HKEY = undefined; - const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW( - hkey, - key_wtf16le, - 0, - windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS, - &key, - ); - const return_code: windows.Win32Error = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => {}, - .FILE_NOT_FOUND => return error.KeyNotFound, - - else => return error.KeyNotFound, - } - return RegistryWtf16Le{ .key = key }; - } - - /// Closes key, after that usage is invalid - fn closeKey(self: *const RegistryWtf16Le) void { - const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key); - const return_code: windows.Win32Error = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => {}, - else => {}, - } - } - - /// Get string ([:0]const u16) from registry. - fn getString(self: *const RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { - var actual_type: windows.ULONG = undefined; - - // Calculating length to allocate - var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters. - var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW( - self.key, - subkey_wtf16le, - value_name_wtf16le, - RRF.RT_REG_SZ, - &actual_type, - null, - &value_wtf16le_buf_size, - ); - - // Check returned code and type - var return_code: windows.Win32Error = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0), - .MORE_DATA => unreachable, // We are only reading length - .FILE_NOT_FOUND => return error.ValueNameNotFound, - .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY - else => return error.StringNotFound, - } - switch (actual_type) { - windows.REG.SZ => {}, - else => return error.NotAString, - } - - const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable); - errdefer allocator.free(value_wtf16le_buf); - - return_code_int = windows.advapi32.RegGetValueW( - self.key, - subkey_wtf16le, - value_name_wtf16le, - RRF.RT_REG_SZ, - &actual_type, - value_wtf16le_buf.ptr, - &value_wtf16le_buf_size, - ); - - // Check returned code and (just in case) type again. - return_code = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => {}, - .MORE_DATA => unreachable, // Calculated first time length should be enough, even overestimated - .FILE_NOT_FOUND => return error.ValueNameNotFound, - .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY - else => return error.StringNotFound, - } - switch (actual_type) { - windows.REG.SZ => {}, - else => return error.NotAString, - } - - const value_wtf16le: []const u16 = value_wtf16le: { - // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space - // we will just search for zero termination and forget length - // Windows sure is strange - const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr); - break :value_wtf16le std.mem.span(value_wtf16le_overestimated); - }; - - _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len); - return value_wtf16le; - } - - /// Get DWORD (u32) from registry. - fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { - var actual_type: windows.ULONG = undefined; - var reg_size: u32 = @sizeOf(u32); - var reg_value: u32 = 0; - - const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW( - self.key, - subkey_wtf16le, - value_name_wtf16le, - RRF.RT_REG_DWORD, - &actual_type, - &reg_value, - &reg_size, - ); - const return_code: windows.Win32Error = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => {}, - .MORE_DATA => return error.DwordTooLong, - .FILE_NOT_FOUND => return error.ValueNameNotFound, - .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY - else => return error.DwordNotFound, - } - - switch (actual_type) { - windows.REG.DWORD => {}, - else => return error.NotADword, - } - - return reg_value; - } - - /// Under private space with flags: - /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS. - /// After finishing work, call `closeKey`. - fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { - var key: windows.HKEY = undefined; - - const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW( - absolute_path_as_wtf16le, - &key, - windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS, - 0, - 0, - ); - const return_code: windows.Win32Error = @enumFromInt(return_code_int); - switch (return_code) { - .SUCCESS => {}, - else => return error.KeyNotFound, - } - - return RegistryWtf16Le{ .key = key }; - } -}; - -pub const Windows10Sdk = struct { - path: []const u8, - version: []const u8, - - /// Find path and version of Windows 10 SDK. - /// Caller owns the result's fields. - /// After finishing work, call `free(allocator)`. - fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk { - const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) { - error.KeyNotFound => return error.Windows10SdkNotFound, - }; - defer v10_key.closeKey(); - - const path: []const u8 = path10: { - const path_maybe_with_trailing_slash = v10_key.getString(allocator, "", "InstallationFolder") catch |err| switch (err) { - error.NotAString => return error.Windows10SdkNotFound, - error.ValueNameNotFound => return error.Windows10SdkNotFound, - error.StringNotFound => return error.Windows10SdkNotFound, - - error.OutOfMemory => return error.OutOfMemory, - }; - - if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { - allocator.free(path_maybe_with_trailing_slash); - return error.PathTooLong; - } - - var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); - errdefer path.deinit(); - - // String might contain trailing slash, so trim it here - if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); - - const path_without_trailing_slash = try path.toOwnedSlice(); - break :path10 path_without_trailing_slash; - }; - errdefer allocator.free(path); - - const version: []const u8 = version10: { - - // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key.... - const version_without_0 = v10_key.getString(allocator, "", "ProductVersion") catch |err| switch (err) { - error.NotAString => return error.Windows10SdkNotFound, - error.ValueNameNotFound => return error.Windows10SdkNotFound, - error.StringNotFound => return error.Windows10SdkNotFound, - - error.OutOfMemory => return error.OutOfMemory, - }; - if (version_without_0.len + ".0".len > product_version_max_length) { - allocator.free(version_without_0); - return error.VersionTooLong; - } - - var version = std.ArrayList(u8).fromOwnedSlice(allocator, version_without_0); - errdefer version.deinit(); - - try version.appendSlice(".0"); - - const version_with_0 = try version.toOwnedSlice(); - break :version10 version_with_0; - }; - errdefer allocator.free(version); - - return Windows10Sdk{ .path = path, .version = version }; - } - - /// Check whether this version is enumerated in registry. - fn isValidVersion(windows10sdk: *const Windows10Sdk) bool { - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) { - error.NoSpaceLeft => return false, - }; - - const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) { - error.KeyNotFound => return false, - }; - defer options_key.closeKey(); - - const option_name = comptime switch (builtin.target.cpu.arch) { - .arm, .armeb => "OptionId.DesktopCPParm", - .aarch64 => "OptionId.DesktopCPParm64", - .x86_64 => "OptionId.DesktopCPPx64", - .x86 => "OptionId.DesktopCPPx86", - else => |tag| @compileError("Windows 10 SDK cannot be detected on architecture " ++ tag), - }; - - const reg_value = options_key.getDword("", option_name) catch return false; - return (reg_value == 1); - } - - fn free(self: *const Windows10Sdk, allocator: std.mem.Allocator) void { - allocator.free(self.path); - allocator.free(self.version); - } -}; - -pub const Windows81Sdk = struct { - path: []const u8, - version: []const u8, - - /// Find path and version of Windows 8.1 SDK. - /// Caller owns the result's fields. - /// After finishing work, call `free(allocator)`. - fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk { - const path: []const u8 = path81: { - const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) { - error.NotAString => return error.Windows81SdkNotFound, - error.ValueNameNotFound => return error.Windows81SdkNotFound, - error.StringNotFound => return error.Windows81SdkNotFound, - - error.OutOfMemory => return error.OutOfMemory, - }; - if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { - allocator.free(path_maybe_with_trailing_slash); - return error.PathTooLong; - } - - var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); - errdefer path.deinit(); - - // String might contain trailing slash, so trim it here - if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); - - const path_without_trailing_slash = try path.toOwnedSlice(); - break :path81 path_without_trailing_slash; - }; - errdefer allocator.free(path); - - const version: []const u8 = version81: { - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) { - error.NoSpaceLeft => return error.PathTooLong, - }; - if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.Windows81SdkNotFound; - - // enumerate files in sdk path looking for latest version - var sdk_lib_dir = std.fs.openDirAbsolute(sdk_lib_dir_path, .{ - .iterate = true, - }) catch |err| switch (err) { - error.NameTooLong => return error.PathTooLong, - else => return error.Windows81SdkNotFound, - }; - defer sdk_lib_dir.close(); - - var iterator = sdk_lib_dir.iterate(); - const versions = iterateAndFilterBySemVer(&iterator, allocator, "winv") catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.VersionNotFound => return error.Windows81SdkNotFound, - }; - defer { - for (versions) |version| allocator.free(version); - allocator.free(versions); - } - const latest_version = try allocator.dupe(u8, versions[0]); - break :version81 latest_version; - }; - errdefer allocator.free(version); - - return Windows81Sdk{ .path = path, .version = version }; - } - - fn free(self: *const Windows81Sdk, allocator: std.mem.Allocator) void { - allocator.free(self.path); - allocator.free(self.version); - } -}; - -pub const ZigWindowsSDK = struct { - windows10sdk: ?Windows10Sdk, - windows81sdk: ?Windows81Sdk, - msvc_lib_dir: ?[]const u8, - - /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. - /// Caller owns the result's fields. - /// After finishing work, call `free(allocator)`. - pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, NotFound, PathTooLong }!ZigWindowsSDK { - if (builtin.os.tag != .windows) return error.NotFound; - - //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed - const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) { - error.KeyNotFound => return error.NotFound, - }; - defer roots_key.closeKey(); - - const windows10sdk: ?Windows10Sdk = blk: { - const windows10sdk = Windows10Sdk.find(allocator) catch |err| switch (err) { - error.Windows10SdkNotFound, - error.PathTooLong, - error.VersionTooLong, - => break :blk null, - error.OutOfMemory => return error.OutOfMemory, - }; - const is_valid_version = windows10sdk.isValidVersion(); - if (!is_valid_version) break :blk null; - break :blk windows10sdk; - }; - errdefer if (windows10sdk) |*w| w.free(allocator); - - const windows81sdk: ?Windows81Sdk = blk: { - const windows81sdk = Windows81Sdk.find(allocator, &roots_key) catch |err| switch (err) { - error.Windows81SdkNotFound => break :blk null, - error.PathTooLong => break :blk null, - error.VersionTooLong => break :blk null, - error.OutOfMemory => return error.OutOfMemory, - }; - // no check - break :blk windows81sdk; - }; - errdefer if (windows81sdk) |*w| w.free(allocator); - - const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) { - error.MsvcLibDirNotFound => null, - error.OutOfMemory => return error.OutOfMemory, - }; - errdefer allocator.free(msvc_lib_dir); - - return ZigWindowsSDK{ - .windows10sdk = windows10sdk, - .windows81sdk = windows81sdk, - .msvc_lib_dir = msvc_lib_dir, - }; - } - - pub fn free(self: *const ZigWindowsSDK, allocator: std.mem.Allocator) void { - if (self.windows10sdk) |*w10sdk| { - w10sdk.free(allocator); - } - if (self.windows81sdk) |*w81sdk| { - w81sdk.free(allocator); - } - if (self.msvc_lib_dir) |msvc_lib_dir| { - allocator.free(msvc_lib_dir); - } - } -}; - -const MsvcLibDir = struct { - fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { - const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; - const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) { - error.KeyNotFound => return error.PathNotFound, - }; - defer setup_config_key.closeKey(); - - const dll_path = setup_config_key.getString(allocator, "InprocServer32", "") catch |err| switch (err) { - error.NotAString, - error.ValueNameNotFound, - error.StringNotFound, - => return error.PathNotFound, - - error.OutOfMemory => return error.OutOfMemory, - }; - defer allocator.free(dll_path); - - var path_it = std.fs.path.componentIterator(dll_path) catch return error.PathNotFound; - // the .dll filename - _ = path_it.last(); - const root_path = while (path_it.previous()) |dir_component| { - if (std.ascii.eqlIgnoreCase(dir_component.name, "VisualStudio")) { - break dir_component.path; - } - } else { - return error.PathNotFound; - }; - - const instances_path = try std.fs.path.join(allocator, &.{ root_path, "Packages", "_Instances" }); - defer allocator.free(instances_path); - - return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; - } - - fn findInstancesDir(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { - // First try to get the path from the .dll that would have been - // loaded via COM for SetupConfiguration. - return findInstancesDirViaCLSID(allocator) catch |orig_err| { - // If that can't be found, fall back to manually appending - // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% - const program_data = std.process.getEnvVarOwned(allocator, "PROGRAMDATA") catch |err| switch (err) { - error.OutOfMemory => |e| return e, - else => return orig_err, - }; - defer allocator.free(program_data); - - const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); - defer allocator.free(instances_path); - - return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return orig_err; - }; - } - - /// Intended to be equivalent to `ISetupHelper.ParseVersion` - /// Example: 17.4.33205.214 -> 0x0011000481b500d6 - fn parseVersionQuad(version: []const u8) error{InvalidVersion}!u64 { - var it = std.mem.splitScalar(u8, version, '.'); - const a = it.next() orelse return error.InvalidVersion; - const b = it.next() orelse return error.InvalidVersion; - const c = it.next() orelse return error.InvalidVersion; - const d = it.next() orelse return error.InvalidVersion; - if (it.next()) |_| return error.InvalidVersion; - var result: u64 = undefined; - var result_bytes = std.mem.asBytes(&result); - - std.mem.writeInt( - u16, - result_bytes[0..2], - std.fmt.parseUnsigned(u16, d, 10) catch return error.InvalidVersion, - .little, - ); - std.mem.writeInt( - u16, - result_bytes[2..4], - std.fmt.parseUnsigned(u16, c, 10) catch return error.InvalidVersion, - .little, - ); - std.mem.writeInt( - u16, - result_bytes[4..6], - std.fmt.parseUnsigned(u16, b, 10) catch return error.InvalidVersion, - .little, - ); - std.mem.writeInt( - u16, - result_bytes[6..8], - std.fmt.parseUnsigned(u16, a, 10) catch return error.InvalidVersion, - .little, - ); - - return result; - } - - /// Intended to be equivalent to ISetupConfiguration.EnumInstances: - /// https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration - /// but without the use of COM in order to avoid a dependency on ole32.dll - /// - /// The logic in this function is intended to match what ISetupConfiguration does - /// under-the-hood, as verified using Procmon. - fn findViaCOM(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { - // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` - // This will contain directories with names of instance IDs like 80a758ca, - // which will contain `state.json` files that have the version and - // installation directory. - var instances_dir = try findInstancesDir(allocator); - defer instances_dir.close(); - - var state_subpath_buf: [std.fs.MAX_NAME_BYTES + 32]u8 = undefined; - var latest_version_lib_dir = std.ArrayListUnmanaged(u8){}; - errdefer latest_version_lib_dir.deinit(allocator); - - var latest_version: u64 = 0; - var instances_dir_it = instances_dir.iterateAssumeFirstIteration(); - while (instances_dir_it.next() catch return error.PathNotFound) |entry| { - if (entry.kind != .directory) continue; - - var fbs = std.io.fixedBufferStream(&state_subpath_buf); - const writer = fbs.writer(); - - writer.writeAll(entry.name) catch unreachable; - writer.writeByte(std.fs.path.sep) catch unreachable; - writer.writeAll("state.json") catch unreachable; - - const json_contents = instances_dir.readFileAlloc(allocator, fbs.getWritten(), std.math.maxInt(usize)) catch continue; - defer allocator.free(json_contents); - - var parsed = std.json.parseFromSlice(std.json.Value, allocator, json_contents, .{}) catch continue; - defer parsed.deinit(); - - if (parsed.value != .object) continue; - const catalog_info = parsed.value.object.get("catalogInfo") orelse continue; - if (catalog_info != .object) continue; - const product_version_value = catalog_info.object.get("buildVersion") orelse continue; - if (product_version_value != .string) continue; - const product_version_text = product_version_value.string; - const parsed_version = parseVersionQuad(product_version_text) catch continue; - - // We want to end up with the most recent version installed - if (parsed_version <= latest_version) continue; - - const installation_path = parsed.value.object.get("installationPath") orelse continue; - if (installation_path != .string) continue; - - const lib_dir_path = libDirFromInstallationPath(allocator, installation_path.string) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.PathNotFound => continue, - }; - defer allocator.free(lib_dir_path); - - latest_version_lib_dir.clearRetainingCapacity(); - try latest_version_lib_dir.appendSlice(allocator, lib_dir_path); - latest_version = parsed_version; - } - - if (latest_version_lib_dir.items.len == 0) return error.PathNotFound; - return latest_version_lib_dir.toOwnedSlice(allocator); - } - - fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path: []const u8) error{ OutOfMemory, PathNotFound }![]const u8 { - var lib_dir_buf = try std.ArrayList(u8).initCapacity(allocator, installation_path.len + 64); - errdefer lib_dir_buf.deinit(); - - lib_dir_buf.appendSliceAssumeCapacity(installation_path); - - if (!std.fs.path.isSep(lib_dir_buf.getLast())) { - try lib_dir_buf.append('\\'); - } - const installation_path_with_trailing_sep_len = lib_dir_buf.items.len; - - try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); - var default_tools_version_buf: [512]u8 = undefined; - const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { - return error.PathNotFound; - }; - var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); - const default_tools_version = tokenizer.next() orelse return error.PathNotFound; - - lib_dir_buf.shrinkRetainingCapacity(installation_path_with_trailing_sep_len); - try lib_dir_buf.appendSlice("VC\\Tools\\MSVC\\"); - try lib_dir_buf.appendSlice(default_tools_version); - const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) { - .x86 => "x86", - .x86_64 => "x64", - .arm, .armeb => "arm", - .aarch64 => "arm64", - else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), - }; - try lib_dir_buf.appendSlice(folder_with_arch); - - if (!verifyLibDir(lib_dir_buf.items)) { - return error.PathNotFound; - } - - return lib_dir_buf.toOwnedSlice(); - } - - // https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance - fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { - - // %localappdata%\Microsoft\VisualStudio\ - // %appdata%\Local\Microsoft\VisualStudio\ - const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound; - defer allocator.free(visualstudio_folder_path); - - const vs_versions: []const []const u8 = vs_versions: { - if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound; - // enumerate folders that contain `privateregistry.bin`, looking for all versions - // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\ - var visualstudio_folder = std.fs.openDirAbsolute(visualstudio_folder_path, .{ - .iterate = true, - }) catch return error.PathNotFound; - defer visualstudio_folder.close(); - - var iterator = visualstudio_folder.iterate(); - const versions = iterateAndFilterBySemVer(&iterator, allocator, null) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.VersionNotFound => return error.PathNotFound, - }; - break :vs_versions versions; - }; - defer { - for (vs_versions) |vs_version| allocator.free(vs_version); - allocator.free(vs_versions); - } - var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined; - const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| { - const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue; - defer allocator.free(privateregistry_absolute_path); - if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue; - - const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue; - defer visualstudio_registry.closeKey(); - - const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable; - - const source_directories_value = visualstudio_registry.getString(allocator, config_subkey, "Source Directories") catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => continue, - }; - if (source_directories_value.len > (std.fs.MAX_PATH_BYTES * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 pathes and at least some of them are not of max length - allocator.free(source_directories_value); - continue; - } - - break :source_directories source_directories_value; - } else return error.PathNotFound; - defer allocator.free(source_directories); - - var source_directories_splitted = std.mem.splitScalar(u8, source_directories, ';'); - - const msvc_dir: []const u8 = msvc_dir: { - const msvc_include_dir_maybe_with_trailing_slash = try allocator.dupe(u8, source_directories_splitted.first()); - - if (msvc_include_dir_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) { - allocator.free(msvc_include_dir_maybe_with_trailing_slash); - return error.PathNotFound; - } - - var msvc_dir = std.ArrayList(u8).fromOwnedSlice(allocator, msvc_include_dir_maybe_with_trailing_slash); - errdefer msvc_dir.deinit(); - - // String might contain trailing slash, so trim it here - if (msvc_dir.items.len > "C:\\".len and msvc_dir.getLast() == '\\') _ = msvc_dir.pop(); - - // Remove `\include` at the end of path - if (std.mem.endsWith(u8, msvc_dir.items, "\\include")) { - msvc_dir.shrinkRetainingCapacity(msvc_dir.items.len - "\\include".len); - } - - const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) { - .x86 => "x86", - .x86_64 => "x64", - .arm, .armeb => "arm", - .aarch64 => "arm64", - else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), - }; - - try msvc_dir.appendSlice(folder_with_arch); - const msvc_dir_with_arch = try msvc_dir.toOwnedSlice(); - break :msvc_dir msvc_dir_with_arch; - }; - errdefer allocator.free(msvc_dir); - - if (!verifyLibDir(msvc_dir)) { - return error.PathNotFound; - } - - return msvc_dir; - } - - fn findViaVs7Key(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { - var base_path: std.ArrayList(u8) = base_path: { - try_env: { - var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => break :try_env, - }; - defer env_map.deinit(); - - if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { - if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env; - if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env; - var list = std.ArrayList(u8).init(allocator); - errdefer list.deinit(); - - try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools - // String might contain trailing slash, so trim it here - if (list.items.len > "C:\\".len and list.getLast() == '\\') _ = list.pop(); - list.shrinkRetainingCapacity(list.items.len - "\\Common7\\Tools".len); // C:\Program Files (x86)\Microsoft Visual Studio 14.0 - break :base_path list; - } - } - - const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound; - defer vs7_key.closeKey(); - try_vs7_key: { - const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => break :try_vs7_key, - }; - - if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { - allocator.free(path_maybe_with_trailing_slash); - break :try_vs7_key; - } - - var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); - errdefer path.deinit(); - - // String might contain trailing slash, so trim it here - if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); - break :base_path path; - } - return error.PathNotFound; - }; - errdefer base_path.deinit(); - - const folder_with_arch = "\\VC\\lib\\" ++ comptime switch (builtin.target.cpu.arch) { - .x86 => "", //x86 is in the root of the Lib folder - .x86_64 => "amd64", - .arm, .armeb => "arm", - .aarch64 => "arm64", - else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), - }; - try base_path.appendSlice(folder_with_arch); - - if (!verifyLibDir(base_path.items)) { - return error.PathNotFound; - } - - const full_path = try base_path.toOwnedSlice(); - return full_path; - } - - fn verifyLibDir(lib_dir_path: []const u8) bool { - std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` - - var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; - defer dir.close(); - - const stat = dir.statFile("vcruntime.lib") catch return false; - if (stat.kind != .file) - return false; - - return true; - } - - /// Find path to MSVC's `lib/` directory. - /// Caller owns the result. - pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { - const full_path = MsvcLibDir.findViaCOM(allocator) catch |err1| switch (err1) { - error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaRegistry(allocator) catch |err2| switch (err2) { - error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err3| switch (err3) { - error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => return error.MsvcLibDirNotFound, - }, - }, - }; - errdefer allocator.free(full_path); - - return full_path; - } -};