diff --git a/CMakeLists.txt b/CMakeLists.txt index fa3a10dc4c..b3004d3254 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -516,7 +516,6 @@ set(ZIG_STAGE2_SOURCES "${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/NativeTargetInfo.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig" "${CMAKE_SOURCE_DIR}/src/Air.zig" diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 8be6b61425..b485469a38 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -46,11 +46,9 @@ pub fn main() !void { return error.InvalidArgs; }; - const detected = try std.zig.system.NativeTargetInfo.detect(.{}); const host: std.Build.ResolvedTarget = .{ .query = .{}, - .target = detected.target, - .dynamic_linker = detected.dynamic_linker, + .target = try std.zig.system.resolveTargetQuery(.{}), }; const build_root_directory: std.Build.Cache.Directory = .{ diff --git a/lib/std/Build.zig b/lib/std/Build.zig index db7e3493da..cb5a503593 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2129,14 +2129,6 @@ pub fn hex64(x: u64) [16]u8 { pub const ResolvedTarget = struct { query: Target.Query, target: Target, - dynamic_linker: Target.DynamicLinker, - - pub fn toNativeTargetInfo(self: ResolvedTarget) std.zig.system.NativeTargetInfo { - return .{ - .target = self.target, - .dynamic_linker = self.dynamic_linker, - }; - } }; /// Converts a target query into a fully resolved target that can be passed to @@ -2146,13 +2138,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget { // resolved via a WASI API or via the build protocol. _ = b; - const result = std.zig.system.NativeTargetInfo.detect(query) catch - @panic("unable to resolve target query"); - return .{ .query = query, - .target = result.target, - .dynamic_linker = result.dynamic_linker, + .target = std.zig.system.resolveTargetQuery(query) catch + @panic("unable to resolve target query"), }; } diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index f17f296e64..5482ca25ec 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -746,5 +746,4 @@ const Module = @This(); const std = @import("std"); const assert = std.debug.assert; const LazyPath = std.Build.LazyPath; -const NativeTargetInfo = std.zig.system.NativeTargetInfo; const Step = std.Build.Step; diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index cd59866176..b51304c910 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -9,7 +9,6 @@ const StringHashMap = std.StringHashMap; const Sha256 = std.crypto.hash.sha2.Sha256; const Allocator = mem.Allocator; const Step = std.Build.Step; -const NativeTargetInfo = std.zig.system.NativeTargetInfo; const LazyPath = std.Build.LazyPath; const PkgConfigPkg = std.Build.PkgConfigPkg; const PkgConfigError = std.Build.PkgConfigError; diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 9ed936fe7e..938649d83f 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -294,11 +294,9 @@ test Options { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); - const detected = try std.zig.system.NativeTargetInfo.detect(.{}); const host: std.Build.ResolvedTarget = .{ .query = .{}, - .target = detected.target, - .dynamic_linker = detected.dynamic_linker, + .target = try std.zig.system.resolveTargetQuery(.{}), }; var cache: std.Build.Cache = .{ diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 36b2eb82eb..ad68dce648 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -678,8 +678,8 @@ fn runCommand( const need_cross_glibc = exe.rootModuleTarget().isGnuLibC() and exe.is_linking_libc; - const other_target_info = exe.root_module.target.?.toNativeTargetInfo(); - switch (b.host.toNativeTargetInfo().getExternalExecutor(&other_target_info, .{ + const other_target = exe.root_module.target.?.target; + switch (std.zig.system.getExternalExecutor(b.host.target, &other_target, .{ .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, .link_libc = exe.is_linking_libc, })) { @@ -752,7 +752,7 @@ fn runCommand( .bad_dl => |foreign_dl| { if (allow_skip) return error.MakeSkipped; - const host_dl = b.host.dynamic_linker.get() orelse "(none)"; + const host_dl = b.host.target.dynamic_linker.get() orelse "(none)"; return step.fail( \\the host system is unable to execute binaries from the target diff --git a/lib/std/Target.zig b/lib/std/Target.zig index f5a2074264..6f30aa75b1 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1,7 +1,13 @@ +//! All the details about the machine that will be executing code. +//! Unlike `Query` which might leave some things as "default" or "host", this +//! data is fully resolved into a concrete set of OS versions, CPU features, +//! etc. + cpu: Cpu, os: Os, abi: Abi, ofmt: ObjectFormat, +dynamic_linker: DynamicLinker = DynamicLinker.none, pub const Query = @import("Target/Query.zig"); @@ -1529,13 +1535,19 @@ pub inline fn hasDynamicLinker(self: Target) bool { } pub const DynamicLinker = struct { - /// Contains the memory used to store the dynamic linker path. This field should - /// not be used directly. See `get` and `set`. This field exists so that this API requires no allocator. - buffer: [255]u8 = undefined, + /// Contains the memory used to store the dynamic linker path. This field + /// should not be used directly. See `get` and `set`. This field exists so + /// that this API requires no allocator. + buffer: [255]u8, /// Used to construct the dynamic linker path. This field should not be used /// directly. See `get` and `set`. - max_byte: ?u8 = null, + max_byte: ?u8, + + pub const none: DynamicLinker = .{ + .buffer = undefined, + .max_byte = null, + }; /// Asserts that the length is less than or equal to 255 bytes. pub fn init(dl_or_null: ?[]const u8) DynamicLinker { @@ -1561,8 +1573,12 @@ pub const DynamicLinker = struct { } }; -pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { - var result: DynamicLinker = .{}; +pub fn standardDynamicLinkerPath(target: Target) DynamicLinker { + return standardDynamicLinkerPath_cpu_os_abi(target.cpu, target.os.tag, target.abi); +} + +pub fn standardDynamicLinkerPath_cpu_os_abi(cpu: Cpu, os_tag: Os.Tag, abi: Abi) DynamicLinker { + var result = DynamicLinker.none; const S = struct { fn print(r: *DynamicLinker, comptime fmt: []const u8, args: anytype) DynamicLinker { r.max_byte = @as(u8, @intCast((std.fmt.bufPrint(&r.buffer, fmt, args) catch unreachable).len - 1)); @@ -1577,32 +1593,32 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { const print = S.print; const copy = S.copy; - if (self.abi == .android) { - const suffix = if (self.ptrBitWidth() == 64) "64" else ""; + if (abi == .android) { + const suffix = if (ptrBitWidth_cpu_abi(cpu, abi) == 64) "64" else ""; return print(&result, "/system/bin/linker{s}", .{suffix}); } - if (self.abi.isMusl()) { - const is_arm = switch (self.cpu.arch) { + if (abi.isMusl()) { + const is_arm = switch (cpu.arch) { .arm, .armeb, .thumb, .thumbeb => true, else => false, }; - const arch_part = switch (self.cpu.arch) { + const arch_part = switch (cpu.arch) { .arm, .thumb => "arm", .armeb, .thumbeb => "armeb", else => |arch| @tagName(arch), }; - const arch_suffix = if (is_arm and self.abi.floatAbi() == .hard) "hf" else ""; + const arch_suffix = if (is_arm and abi.floatAbi() == .hard) "hf" else ""; return print(&result, "/lib/ld-musl-{s}{s}.so.1", .{ arch_part, arch_suffix }); } - switch (self.os.tag) { + switch (os_tag) { .freebsd => return copy(&result, "/libexec/ld-elf.so.1"), .netbsd => return copy(&result, "/libexec/ld.elf_so"), .openbsd => return copy(&result, "/usr/libexec/ld.so"), .dragonfly => return copy(&result, "/libexec/ld-elf.so.2"), .solaris, .illumos => return copy(&result, "/lib/64/ld.so.1"), - .linux => switch (self.cpu.arch) { + .linux => switch (cpu.arch) { .x86, .sparc, .sparcel, @@ -1616,7 +1632,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { .armeb, .thumb, .thumbeb, - => return copy(&result, switch (self.abi.floatAbi()) { + => return copy(&result, switch (abi.floatAbi()) { .hard => "/lib/ld-linux-armhf.so.3", else => "/lib/ld-linux.so.3", }), @@ -1626,12 +1642,12 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { .mips64, .mips64el, => { - const lib_suffix = switch (self.abi) { + const lib_suffix = switch (abi) { .gnuabin32, .gnux32 => "32", .gnuabi64 => "64", else => "", }; - const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008); + const is_nan_2008 = mips.featureSetHas(cpu.features, .nan2008); const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1"; return print(&result, "/lib{s}/{s}", .{ lib_suffix, loader }); }, @@ -1640,7 +1656,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { .powerpc64, .powerpc64le => return copy(&result, "/lib64/ld64.so.2"), .s390x => return copy(&result, "/lib64/ld64.so.1"), .sparc64 => return copy(&result, "/lib64/ld-linux.so.2"), - .x86_64 => return copy(&result, switch (self.abi) { + .x86_64 => return copy(&result, switch (abi) { .gnux32 => "/libx32/ld-linux-x32.so.2", else => "/lib64/ld-linux-x86-64.so.2", }), @@ -1862,17 +1878,17 @@ pub fn maxIntAlignment(target: Target) u16 { }; } -pub fn ptrBitWidth(target: Target) u16 { - switch (target.abi) { +pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { + switch (abi) { .gnux32, .muslx32, .gnuabin32, .gnuilp32 => return 32, .gnuabi64 => return 64, else => {}, } - switch (target.cpu.arch) { + return switch (cpu.arch) { .avr, .msp430, .spu_2, - => return 16, + => 16, .arc, .arm, @@ -1908,7 +1924,7 @@ pub fn ptrBitWidth(target: Target) u16 { .loongarch32, .dxil, .xtensa, - => return 32, + => 32, .aarch64, .aarch64_be, @@ -1933,10 +1949,14 @@ pub fn ptrBitWidth(target: Target) u16 { .ve, .spirv64, .loongarch64, - => return 64, + => 64, - .sparc => return if (std.Target.sparc.featureSetHas(target.cpu.features, .v9)) 64 else 32, - } + .sparc => if (std.Target.sparc.featureSetHas(cpu.features, .v9)) 64 else 32, + }; +} + +pub fn ptrBitWidth(target: Target) u16 { + return ptrBitWidth_cpu_abi(target.cpu, target.abi); } pub fn stackAlignment(target: Target) u16 { diff --git a/lib/std/Target/Query.zig b/lib/std/Target/Query.zig index 617d141d39..bb5949d597 100644 --- a/lib/std/Target/Query.zig +++ b/lib/std/Target/Query.zig @@ -34,7 +34,7 @@ abi: ?Target.Abi = null, /// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path /// based on the `os_tag`. -dynamic_linker: DynamicLinker = DynamicLinker{}, +dynamic_linker: Target.DynamicLinker = Target.DynamicLinker.none, /// `null` means default for the cpu/arch/os combo. ofmt: ?Target.ObjectFormat = null, @@ -61,8 +61,6 @@ pub const OsVersion = union(enum) { pub const SemanticVersion = std.SemanticVersion; -pub const DynamicLinker = Target.DynamicLinker; - pub fn fromTarget(target: Target) Query { var result: Query = .{ .cpu_arch = target.cpu.arch, @@ -164,7 +162,7 @@ fn updateOsVersionRange(self: *Query, os: Target.Os) void { } } -/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. +/// TODO deprecated, use `std.zig.system.resolveTargetQuery`. pub fn toTarget(self: Query) Target { return .{ .cpu = self.getCpu(), @@ -232,7 +230,7 @@ pub fn parse(args: ParseOptions) !Query { const diags = args.diagnostics orelse &dummy_diags; var result: Query = .{ - .dynamic_linker = DynamicLinker.init(args.dynamic_linker), + .dynamic_linker = Target.DynamicLinker.init(args.dynamic_linker), }; var it = mem.splitScalar(u8, args.arch_os_abi, '-'); @@ -379,13 +377,13 @@ test parseVersion { try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3.4")); } -/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. +/// TODO deprecated, use `std.zig.system.resolveTargetQuery`. pub fn getCpu(self: Query) Target.Cpu { switch (self.cpu_model) { .native => { // This works when doing `zig build` because Zig generates a build executable using // native CPU model & features. However this will not be accurate otherwise, and - // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + // will need to be integrated with `std.zig.system.resolveTargetQuery`. return builtin.cpu; }, .baseline => { @@ -396,7 +394,7 @@ pub fn getCpu(self: Query) Target.Cpu { .determined_by_cpu_arch => if (self.cpu_arch == null) { // This works when doing `zig build` because Zig generates a build executable using // native CPU model & features. However this will not be accurate otherwise, and - // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + // will need to be integrated with `std.zig.system.resolveTargetQuery`. return builtin.cpu; } else { var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); @@ -426,11 +424,11 @@ pub fn getCpuFeatures(self: Query) Target.Cpu.Feature.Set { return self.getCpu().features; } -/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. +/// TODO deprecated, use `std.zig.system.resolveTargetQuery`. pub fn getOs(self: Query) Target.Os { // `builtin.os` works when doing `zig build` because Zig generates a build executable using // native OS version range. However this will not be accurate otherwise, and - // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + // will need to be integrated with `std.zig.system.resolveTargetQuery`. var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange(self.getCpuArch()) else builtin.os; if (self.os_version_min) |min| switch (min) { @@ -463,7 +461,7 @@ pub fn getOsTag(self: Query) Target.Os.Tag { return self.os_tag orelse builtin.os.tag; } -/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. +/// TODO deprecated, use `std.zig.system.resolveTargetQuery`. pub fn getOsVersionMin(self: Query) OsVersion { if (self.os_version_min) |version_min| return version_min; var tmp: Query = undefined; @@ -471,7 +469,7 @@ pub fn getOsVersionMin(self: Query) OsVersion { return tmp.os_version_min.?; } -/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. +/// TODO deprecated, use `std.zig.system.resolveTargetQuery`. pub fn getOsVersionMax(self: Query) OsVersion { if (self.os_version_max) |version_max| return version_max; var tmp: Query = undefined; @@ -479,14 +477,14 @@ pub fn getOsVersionMax(self: Query) OsVersion { return tmp.os_version_max.?; } -/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. +/// TODO deprecated, use `std.zig.system.resolveTargetQuery`. pub fn getAbi(self: Query) Target.Abi { if (self.abi) |abi| return abi; if (self.os_tag == null) { // This works when doing `zig build` because Zig generates a build executable using // native CPU model & features. However this will not be accurate otherwise, and - // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + // will need to be integrated with `std.zig.system.resolveTargetQuery`. return builtin.abi; } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 3ec92c8274..6920999ef0 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -1,13 +1,1127 @@ pub const NativePaths = @import("system/NativePaths.zig"); -pub const NativeTargetInfo = @import("system/NativeTargetInfo.zig"); pub const windows = @import("system/windows.zig"); pub const darwin = @import("system/darwin.zig"); pub const linux = @import("system/linux.zig"); +pub const Executor = union(enum) { + native, + rosetta, + qemu: []const u8, + wine: []const u8, + wasmtime: []const u8, + darling: []const u8, + bad_dl: []const u8, + bad_os_or_cpu, +}; + +pub const GetExternalExecutorOptions = struct { + allow_darling: bool = true, + allow_qemu: bool = true, + allow_rosetta: bool = true, + allow_wasmtime: bool = true, + allow_wine: bool = true, + qemu_fixes_dl: bool = false, + link_libc: bool = false, +}; + +/// Return whether or not the given host is capable of running executables of +/// the other target. +pub fn getExternalExecutor( + host: std.Target, + candidate: *const std.Target, + options: GetExternalExecutorOptions, +) Executor { + const os_match = host.os.tag == candidate.os.tag; + const cpu_ok = cpu_ok: { + if (host.cpu.arch == candidate.cpu.arch) + break :cpu_ok true; + + if (host.cpu.arch == .x86_64 and candidate.cpu.arch == .x86) + break :cpu_ok true; + + if (host.cpu.arch == .aarch64 and candidate.cpu.arch == .arm) + break :cpu_ok true; + + if (host.cpu.arch == .aarch64_be and candidate.cpu.arch == .armeb) + break :cpu_ok true; + + // TODO additionally detect incompatible CPU features. + // Note that in some cases the OS kernel will emulate missing CPU features + // when an illegal instruction is encountered. + + break :cpu_ok false; + }; + + var bad_result: Executor = .bad_os_or_cpu; + + if (os_match and cpu_ok) native: { + if (options.link_libc) { + if (candidate.dynamic_linker.get()) |candidate_dl| { + fs.cwd().access(candidate_dl, .{}) catch { + bad_result = .{ .bad_dl = candidate_dl }; + break :native; + }; + } + } + return .native; + } + + // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 + // to emulate the foreign architecture. + if (options.allow_rosetta and os_match and + host.os.tag == .macos and host.cpu.arch == .aarch64) + { + switch (candidate.cpu.arch) { + .x86_64 => return .rosetta, + else => return bad_result, + } + } + + // If the OS matches, we can use QEMU to emulate a foreign architecture. + if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { + return switch (candidate.cpu.arch) { + .aarch64 => Executor{ .qemu = "qemu-aarch64" }, + .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, + .arm => Executor{ .qemu = "qemu-arm" }, + .armeb => Executor{ .qemu = "qemu-armeb" }, + .hexagon => Executor{ .qemu = "qemu-hexagon" }, + .x86 => Executor{ .qemu = "qemu-i386" }, + .m68k => Executor{ .qemu = "qemu-m68k" }, + .mips => Executor{ .qemu = "qemu-mips" }, + .mipsel => Executor{ .qemu = "qemu-mipsel" }, + .mips64 => Executor{ .qemu = "qemu-mips64" }, + .mips64el => Executor{ .qemu = "qemu-mips64el" }, + .powerpc => Executor{ .qemu = "qemu-ppc" }, + .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, + .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, + .riscv32 => Executor{ .qemu = "qemu-riscv32" }, + .riscv64 => Executor{ .qemu = "qemu-riscv64" }, + .s390x => Executor{ .qemu = "qemu-s390x" }, + .sparc => Executor{ .qemu = "qemu-sparc" }, + .sparc64 => Executor{ .qemu = "qemu-sparc64" }, + .x86_64 => Executor{ .qemu = "qemu-x86_64" }, + else => return bad_result, + }; + } + + switch (candidate.os.tag) { + .windows => { + if (options.allow_wine) { + // x86_64 wine does not support emulating aarch64-windows and + // vice versa. + if (candidate.cpu.arch != builtin.cpu.arch) { + return bad_result; + } + switch (candidate.ptrBitWidth()) { + 32 => return Executor{ .wine = "wine" }, + 64 => return Executor{ .wine = "wine64" }, + else => return bad_result, + } + } + return bad_result; + }, + .wasi => { + if (options.allow_wasmtime) { + switch (candidate.ptrBitWidth()) { + 32 => return Executor{ .wasmtime = "wasmtime" }, + else => return bad_result, + } + } + return bad_result; + }, + .macos => { + if (options.allow_darling) { + // This check can be loosened once darling adds a QEMU-based emulation + // layer for non-host architectures: + // https://github.com/darlinghq/darling/issues/863 + if (candidate.cpu.arch != builtin.cpu.arch) { + return bad_result; + } + return Executor{ .darling = "darling" }; + } + return bad_result; + }, + else => return bad_result, + } +} + +pub const DetectError = error{ + FileSystem, + SystemResources, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + DeviceBusy, + OSVersionDetectionFail, + Unexpected, +}; + +/// Given a `Target.Query`, which specifies in detail which parts of the +/// target should be detected natively, which should be standard or default, +/// and which are provided explicitly, this function resolves the native +/// components by detecting the native system, and then resolves +/// standard/default parts relative to that. +pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { + var os = query.getOsTag().defaultVersionRange(query.getCpuArch()); + if (query.os_tag == null) { + switch (builtin.target.os.tag) { + .linux => { + const uts = std.os.uname(); + const release = mem.sliceTo(&uts.release, 0); + // The release field sometimes has a weird format, + // `Version.parse` will attempt to find some meaningful interpretation. + if (std.SemanticVersion.parse(release)) |ver| { + os.version_range.linux.range.min = ver; + os.version_range.linux.range.max = ver; + } else |err| switch (err) { + error.Overflow => {}, + error.InvalidVersion => {}, + } + }, + .solaris, .illumos => { + const uts = std.os.uname(); + const release = mem.sliceTo(&uts.release, 0); + if (std.SemanticVersion.parse(release)) |ver| { + os.version_range.semver.min = ver; + os.version_range.semver.max = ver; + } else |err| switch (err) { + error.Overflow => {}, + error.InvalidVersion => {}, + } + }, + .windows => { + const detected_version = windows.detectRuntimeVersion(); + os.version_range.windows.min = detected_version; + os.version_range.windows.max = detected_version; + }, + .macos => try darwin.macos.detect(&os), + .freebsd, .netbsd, .dragonfly => { + const key = switch (builtin.target.os.tag) { + .freebsd => "kern.osreldate", + .netbsd, .dragonfly => "kern.osrevision", + else => unreachable, + }; + var value: u32 = undefined; + var len: usize = @sizeOf(@TypeOf(value)); + + std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { + error.NameTooLong => unreachable, // constant, known good value + error.PermissionDenied => unreachable, // only when setting values, + error.SystemResources => unreachable, // memory already on the stack + error.UnknownName => unreachable, // constant, known good value + error.Unexpected => return error.OSVersionDetectionFail, + }; + + switch (builtin.target.os.tag) { + .freebsd => { + // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html + // Major * 100,000 has been convention since FreeBSD 2.2 (1997) + // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997) + // e.g. 492101 = 4.11-STABLE = 4.(9+2) + const major = value / 100_000; + const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1 + const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since + const patch = value % 1_000; + os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch }; + os.version_range.semver.max = os.version_range.semver.min; + }, + .netbsd => { + // #define __NetBSD_Version__ MMmmrrpp00 + // + // M = major version + // m = minor version; a minor number of 99 indicates current. + // r = 0 (*) + // p = patchlevel + const major = value / 100_000_000; + const minor = value % 100_000_000 / 1_000_000; + const patch = value % 10_000 / 100; + os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; + os.version_range.semver.max = os.version_range.semver.min; + }, + .dragonfly => { + // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17 + // flat base10 format: Mmmmpp + // M = major + // m = minor; odd-numbers indicate current dev branch + // p = patch + const major = value / 100_000; + const minor = value % 100_000 / 100; + const patch = value % 100; + os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; + os.version_range.semver.max = os.version_range.semver.min; + }, + else => unreachable, + } + }, + .openbsd => { + const mib: [2]c_int = [_]c_int{ + std.os.CTL.KERN, + std.os.KERN.OSRELEASE, + }; + var buf: [64]u8 = undefined; + // consider that sysctl result includes null-termination + // reserve 1 byte to ensure we never overflow when appending ".0" + var len: usize = buf.len - 1; + + std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { + error.NameTooLong => unreachable, // constant, known good value + error.PermissionDenied => unreachable, // only when setting values, + error.SystemResources => unreachable, // memory already on the stack + error.UnknownName => unreachable, // constant, known good value + error.Unexpected => return error.OSVersionDetectionFail, + }; + + // append ".0" to satisfy semver + buf[len - 1] = '.'; + buf[len] = '0'; + len += 1; + + if (std.SemanticVersion.parse(buf[0..len])) |ver| { + os.version_range.semver.min = ver; + os.version_range.semver.max = ver; + } else |_| { + return error.OSVersionDetectionFail; + } + }, + else => { + // Unimplemented, fall back to default version range. + }, + } + } + + if (query.os_version_min) |min| switch (min) { + .none => {}, + .semver => |semver| switch (query.getOsTag()) { + .linux => os.version_range.linux.range.min = semver, + else => os.version_range.semver.min = semver, + }, + .windows => |win_ver| os.version_range.windows.min = win_ver, + }; + + if (query.os_version_max) |max| switch (max) { + .none => {}, + .semver => |semver| switch (query.getOsTag()) { + .linux => os.version_range.linux.range.max = semver, + else => os.version_range.semver.max = semver, + }, + .windows => |win_ver| os.version_range.windows.max = win_ver, + }; + + if (query.glibc_version) |glibc| { + assert(query.isGnuLibC()); + os.version_range.linux.glibc = glibc; + } + + // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the + // native CPU architecture as being different than the current target), we use this: + const cpu_arch = query.getCpuArch(); + + const cpu = switch (query.cpu_model) { + .native => detectNativeCpuAndFeatures(cpu_arch, os, query), + .baseline => Target.Cpu.baseline(cpu_arch), + .determined_by_cpu_arch => if (query.cpu_arch == null) + detectNativeCpuAndFeatures(cpu_arch, os, query) + else + Target.Cpu.baseline(cpu_arch), + .explicit => |model| model.toCpu(cpu_arch), + } orelse backup_cpu_detection: { + break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); + }; + var result = try detectAbiAndDynamicLinker(cpu, os, query); + // For x86, we need to populate some CPU feature flags depending on architecture + // and mode: + // * 16bit_mode => if the abi is code16 + // * 32bit_mode => if the arch is x86 + // However, the "mode" flags can be used as overrides, so if the user explicitly + // sets one of them, that takes precedence. + switch (cpu_arch) { + .x86 => { + if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{ + .@"16bit_mode", .@"32bit_mode", + })) { + switch (result.abi) { + .code16 => result.cpu.features.addFeature( + @intFromEnum(Target.x86.Feature.@"16bit_mode"), + ), + else => result.cpu.features.addFeature( + @intFromEnum(Target.x86.Feature.@"32bit_mode"), + ), + } + } + }, + .arm, .armeb => { + // XXX What do we do if the target has the noarm feature? + // What do we do if the user specifies +thumb_mode? + }, + .thumb, .thumbeb => { + result.cpu.features.addFeature( + @intFromEnum(Target.arm.Feature.thumb_mode), + ); + }, + else => {}, + } + query.updateCpuFeatures(&result.cpu.features); + return result; +} + +fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu { + // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`, + // although it is a runtime value, is guaranteed to be one of the architectures in the set + // of the respective switch prong. + switch (builtin.cpu.arch) { + .x86_64, .x86 => { + return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query); + }, + else => {}, + } + + switch (builtin.os.tag) { + .linux => return linux.detectNativeCpuAndFeatures(), + .macos => return darwin.macos.detectNativeCpuAndFeatures(), + .windows => return windows.detectNativeCpuAndFeatures(), + else => {}, + } + + // This architecture does not have CPU model & feature detection yet. + // See https://github.com/ziglang/zig/issues/4591 + return null; +} + +pub const AbiAndDynamicLinkerFromFileError = error{ + FileSystem, + SystemResources, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + UnableToReadElfFile, + InvalidElfClass, + InvalidElfVersion, + InvalidElfEndian, + InvalidElfFile, + InvalidElfMagic, + Unexpected, + UnexpectedEndOfFile, + NameTooLong, +}; + +pub fn abiAndDynamicLinkerFromFile( + file: fs.File, + cpu: Target.Cpu, + os: Target.Os, + ld_info_list: []const LdInfo, + query: Target.Query, +) AbiAndDynamicLinkerFromFileError!Target { + var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; + _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); + const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); + const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); + if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; + const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { + elf.ELFDATA2LSB => .little, + elf.ELFDATA2MSB => .big, + else => return error.InvalidElfEndian, + }; + const need_bswap = elf_endian != native_endian; + if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { + elf.ELFCLASS32 => false, + elf.ELFCLASS64 => true, + else => return error.InvalidElfClass, + }; + var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); + const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); + const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); + + var result: Target = .{ + .cpu = cpu, + .os = os, + .abi = query.abi orelse Target.Abi.default(cpu.arch, os), + .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), + .dynamic_linker = query.dynamic_linker, + }; + var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC + const look_for_ld = query.dynamic_linker.get() == null; + + var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; + if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; + + var ph_i: u16 = 0; + while (ph_i < phnum) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); + const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); + var ph_buf_i: usize = 0; + while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ + ph_i += 1; + phoff += phentsize; + ph_buf_i += phentsize; + }) { + const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); + const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); + const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); + switch (p_type) { + elf.PT_INTERP => if (look_for_ld) { + const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); + const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); + if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; + const filesz = @as(usize, @intCast(p_filesz)); + _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz); + // PT_INTERP includes a null byte in filesz. + const len = filesz - 1; + // dynamic_linker.max_byte is "max", not "len". + // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. + result.dynamic_linker.max_byte = @as(u8, @intCast(len - 1)); + + // Use it to determine ABI. + const full_ld_path = result.dynamic_linker.buffer[0..len]; + for (ld_info_list) |ld_info| { + const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); + if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { + result.abi = ld_info.abi; + break; + } + } + }, + // We only need this for detecting glibc version. + elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.isGnuLibC() and + query.glibc_version == null) + { + var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); + const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); + const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); + const dyn_num = p_filesz / dyn_size; + var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; + var dyn_i: usize = 0; + dyn: while (dyn_i < dyn_num) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); + const dyn_read_byte_len = try preadMin( + file, + dyn_buf[0 .. dyn_buf.len - dyn_reserve], + dyn_off, + dyn_size, + ); + var dyn_buf_i: usize = 0; + while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ + dyn_i += 1; + dyn_off += dyn_size; + dyn_buf_i += dyn_size; + }) { + const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); + const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); + const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); + const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); + if (tag == elf.DT_RUNPATH) { + rpath_offset = val; + break :dyn; + } + } + } + }, + else => continue, + } + } + } + + if (builtin.target.os.tag == .linux and result.isGnuLibC() and + query.glibc_version == null) + { + const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); + + var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); + const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); + const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); + + var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; + if (sh_buf.len < shentsize) return error.InvalidElfFile; + + _ = try preadMin(file, &sh_buf, str_section_off, shentsize); + const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); + const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); + const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); + const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); + var strtab_buf: [4096:0]u8 = undefined; + const shstrtab_len = @min(shstrtab_size, strtab_buf.len); + const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); + const shstrtab = strtab_buf[0..shstrtab_read_len]; + + const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); + var sh_i: u16 = 0; + const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); + const sh_read_byte_len = try preadMin( + file, + sh_buf[0 .. sh_buf.len - sh_reserve], + shoff, + shentsize, + ); + var sh_buf_i: usize = 0; + while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ + sh_i += 1; + shoff += shentsize; + sh_buf_i += shentsize; + }) { + const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); + const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); + const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); + const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); + if (mem.eql(u8, sh_name, ".dynstr")) { + break :find_dyn_str .{ + .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), + .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), + }; + } + } + } else null; + + if (dynstr) |ds| { + if (rpath_offset) |rpoff| { + if (rpoff > ds.size) return error.InvalidElfFile; + const rpoff_file = ds.offset + rpoff; + const rp_max_size = ds.size - rpoff; + + const strtab_len = @min(rp_max_size, strtab_buf.len); + const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len); + const strtab = strtab_buf[0..strtab_read_len]; + + const rpath_list = mem.sliceTo(strtab, 0); + var it = mem.tokenizeScalar(u8, rpath_list, ':'); + while (it.next()) |rpath| { + if (glibcVerFromRPath(rpath)) |ver| { + result.os.version_range.linux.glibc = ver; + return result; + } else |err| switch (err) { + error.GLibCNotFound => continue, + else => |e| return e, + } + } + } + } + + if (result.dynamic_linker.get()) |dl_path| glibc_ver: { + // There is no DT_RUNPATH so we try to find libc.so.6 inside the same + // directory as the dynamic linker. + if (fs.path.dirname(dl_path)) |rpath| { + if (glibcVerFromRPath(rpath)) |ver| { + result.os.version_range.linux.glibc = ver; + return result; + } else |err| switch (err) { + error.GLibCNotFound => {}, + else => |e| return e, + } + } + + // So far, no luck. Next we try to see if the information is + // present in the symlink data for the dynamic linker path. + var link_buf: [std.os.PATH_MAX]u8 = undefined; + const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { + error.NameTooLong => unreachable, + error.InvalidUtf8 => unreachable, // Windows only + error.BadPathName => unreachable, // Windows only + error.UnsupportedReparsePointType => unreachable, // Windows only + error.NetworkNotFound => unreachable, // Windows only + + error.AccessDenied, + error.FileNotFound, + error.NotLink, + error.NotDir, + => break :glibc_ver, + + error.SystemResources, + error.FileSystem, + error.SymLinkLoop, + error.Unexpected, + => |e| return e, + }; + result.os.version_range.linux.glibc = glibcVerFromLinkName( + fs.path.basename(link_name), + "ld-", + ) catch |err| switch (err) { + error.UnrecognizedGnuLibCFileName, + error.InvalidGnuLibCVersion, + => break :glibc_ver, + }; + return result; + } + + // Nothing worked so far. Finally we fall back to hard-coded search paths. + // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`. + var path_buf: [std.os.PATH_MAX]u8 = undefined; + var index: usize = 0; + const prefix = "/lib/"; + const cpu_arch = @tagName(result.cpu.arch); + const os_tag = @tagName(result.os.tag); + const abi = @tagName(result.abi); + @memcpy(path_buf[index..][0..prefix.len], prefix); + index += prefix.len; + @memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch); + index += cpu_arch.len; + path_buf[index] = '-'; + index += 1; + @memcpy(path_buf[index..][0..os_tag.len], os_tag); + index += os_tag.len; + path_buf[index] = '-'; + index += 1; + @memcpy(path_buf[index..][0..abi.len], abi); + index += abi.len; + const rpath = path_buf[0..index]; + if (glibcVerFromRPath(rpath)) |ver| { + result.os.version_range.linux.glibc = ver; + return result; + } else |err| switch (err) { + error.GLibCNotFound => {}, + else => |e| return e, + } + } + + return result; +} + +fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion { + // example: "libc-2.3.4.so" + // example: "libc-2.27.so" + // example: "ld-2.33.so" + const suffix = ".so"; + if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { + return error.UnrecognizedGnuLibCFileName; + } + // chop off "libc-" and ".so" + const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; + return Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) { + error.Overflow => return error.InvalidGnuLibCVersion, + error.InvalidVersion => return error.InvalidGnuLibCVersion, + }; +} + +test glibcVerFromLinkName { + try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist")); + try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-")); + + try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-")); + try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-")); + try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-")); + try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-")); + try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-")); +} + +fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { + var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { + error.NameTooLong => unreachable, + error.InvalidUtf8 => unreachable, + error.BadPathName => unreachable, + error.DeviceBusy => unreachable, + error.NetworkNotFound => unreachable, // Windows-only + + error.FileNotFound, + error.NotDir, + error.InvalidHandle, + error.AccessDenied, + error.NoDevice, + => return error.GLibCNotFound, + + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.SystemResources, + error.SymLinkLoop, + error.Unexpected, + => |e| return e, + }; + defer dir.close(); + + // Now we have a candidate for the path to libc shared object. In + // the past, we used readlink() here because the link name would + // reveal the glibc version. However, in more recent GNU/Linux + // installations, there is no symlink. Thus we instead use a more + // robust check of opening the libc shared object and looking at the + // .dynstr section, and finding the max version number of symbols + // that start with "GLIBC_2.". + const glibc_so_basename = "libc.so.6"; + var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { + error.NameTooLong => unreachable, + error.InvalidUtf8 => unreachable, // Windows only + error.BadPathName => unreachable, // Windows only + error.PipeBusy => unreachable, // Windows-only + error.SharingViolation => unreachable, // Windows-only + error.NetworkNotFound => unreachable, // Windows-only + error.FileLocksNotSupported => unreachable, // No lock requested. + error.NoSpaceLeft => unreachable, // read-only + error.PathAlreadyExists => unreachable, // read-only + error.DeviceBusy => unreachable, // read-only + error.FileBusy => unreachable, // read-only + error.InvalidHandle => unreachable, // should not be in the error set + error.WouldBlock => unreachable, // not using O_NONBLOCK + error.NoDevice => unreachable, // not asking for a special device + + error.AccessDenied, + error.FileNotFound, + error.NotDir, + error.IsDir, + => return error.GLibCNotFound, + + error.FileTooBig => return error.Unexpected, + + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.SystemResources, + error.SymLinkLoop, + error.Unexpected, + => |e| return e, + }; + defer f.close(); + + return glibcVerFromSoFile(f) catch |err| switch (err) { + error.InvalidElfMagic, + error.InvalidElfEndian, + error.InvalidElfClass, + error.InvalidElfFile, + error.InvalidElfVersion, + error.InvalidGnuLibCVersion, + error.UnexpectedEndOfFile, + => return error.GLibCNotFound, + + error.SystemResources, + error.UnableToReadElfFile, + error.Unexpected, + error.FileSystem, + => |e| return e, + }; +} + +fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { + var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; + _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); + const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); + const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); + if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; + const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { + elf.ELFDATA2LSB => .little, + elf.ELFDATA2MSB => .big, + else => return error.InvalidElfEndian, + }; + const need_bswap = elf_endian != native_endian; + if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { + elf.ELFCLASS32 => false, + elf.ELFCLASS64 => true, + else => return error.InvalidElfClass, + }; + const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); + var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); + const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); + const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); + var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; + if (sh_buf.len < shentsize) return error.InvalidElfFile; + + _ = try preadMin(file, &sh_buf, str_section_off, shentsize); + const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); + const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); + const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); + const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); + var strtab_buf: [4096:0]u8 = undefined; + const shstrtab_len = @min(shstrtab_size, strtab_buf.len); + const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); + const shstrtab = strtab_buf[0..shstrtab_read_len]; + const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); + var sh_i: u16 = 0; + const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); + const sh_read_byte_len = try preadMin( + file, + sh_buf[0 .. sh_buf.len - sh_reserve], + shoff, + shentsize, + ); + var sh_buf_i: usize = 0; + while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ + sh_i += 1; + shoff += shentsize; + sh_buf_i += shentsize; + }) { + const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); + const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); + const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); + const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); + if (mem.eql(u8, sh_name, ".dynstr")) { + break :find_dyn_str .{ + .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), + .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), + }; + } + } + } else return error.InvalidGnuLibCVersion; + + // Here we loop over all the strings in the dynstr string table, assuming that any + // strings that start with "GLIBC_2." indicate the existence of such a glibc version, + // and furthermore, that the system-installed glibc is at minimum that version. + + // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. + // Here I use double this value plus some headroom. This makes it only need + // a single read syscall here. + var buf: [80000]u8 = undefined; + if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion; + + const dynstr_size: usize = @intCast(dynstr.size); + const dynstr_bytes = buf[0..dynstr_size]; + _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len); + var it = mem.splitScalar(u8, dynstr_bytes, 0); + var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 }; + while (it.next()) |s| { + if (mem.startsWith(u8, s, "GLIBC_2.")) { + const chopped = s["GLIBC_".len..]; + const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) { + error.Overflow => return error.InvalidGnuLibCVersion, + error.InvalidVersion => return error.InvalidGnuLibCVersion, + }; + switch (ver.order(max_ver)) { + .gt => max_ver = ver, + .lt, .eq => continue, + } + } + } + return max_ver; +} + +/// In the past, this function attempted to use the executable's own binary if it was dynamically +/// linked to answer both the C ABI question and the dynamic linker question. However, this +/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking +/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc +/// version. The problem is that libc.so.6 glibc version will match that of the system while +/// the dynamic linker will match that of the compiler binary. Executables with these versions +/// mismatching will fail to run. +/// +/// Therefore, this function works the same regardless of whether the compiler binary is +/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the +/// answer to these questions, or if there is a shebang line, then it chases the referenced +/// file recursively. If that does not provide the answer, then the function falls back to +/// defaults. +fn detectAbiAndDynamicLinker( + cpu: Target.Cpu, + os: Target.Os, + query: Target.Query, +) DetectError!Target { + const native_target_has_ld = comptime builtin.target.hasDynamicLinker(); + const is_linux = builtin.target.os.tag == .linux; + const is_solarish = builtin.target.os.tag.isSolarish(); + const have_all_info = query.dynamic_linker.get() != null and + query.abi != null and (!is_linux or query.abi.?.isGnu()); + const os_is_non_native = query.os_tag != null; + // The Solaris/illumos environment is always the same. + if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) { + return defaultAbiAndDynamicLinker(cpu, os, query); + } + if (query.abi) |abi| { + if (abi.isMusl()) { + // musl implies static linking. + return defaultAbiAndDynamicLinker(cpu, os, query); + } + } + // The current target's ABI cannot be relied on for this. For example, we may build the zig + // compiler for target riscv64-linux-musl and provide a tarball for users to download. + // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined + // and supported by Zig. But that means that we must detect the system ABI here rather than + // relying on `builtin.target`. + const all_abis = comptime blk: { + assert(@intFromEnum(Target.Abi.none) == 0); + const fields = std.meta.fields(Target.Abi)[1..]; + var array: [fields.len]Target.Abi = undefined; + for (fields, 0..) |field, i| { + array[i] = @field(Target.Abi, field.name); + } + break :blk array; + }; + var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; + var ld_info_list_len: usize = 0; + const ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); + + for (all_abis) |abi| { + // This may be a nonsensical parameter. We detect this with + // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`. + const target: Target = .{ + .cpu = cpu, + .os = os, + .abi = abi, + .ofmt = ofmt, + }; + const ld = target.standardDynamicLinkerPath(); + if (ld.get() == null) continue; + + ld_info_list_buffer[ld_info_list_len] = .{ + .ld = ld, + .abi = abi, + }; + ld_info_list_len += 1; + } + const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; + + // Best case scenario: the executable is dynamically linked, and we can iterate + // over our own shared objects and find a dynamic linker. + const elf_file = blk: { + // This block looks for a shebang line in /usr/bin/env, + // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, + // doing the same logic recursively in case it finds another shebang line. + + // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a + // reasonably reliable path to start with. + var file_name: []const u8 = "/usr/bin/env"; + // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) + var buffer: [258]u8 = undefined; + while (true) { + const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + error.NameTooLong => unreachable, + error.PathAlreadyExists => unreachable, + error.SharingViolation => unreachable, + error.InvalidUtf8 => unreachable, + error.BadPathName => unreachable, + error.PipeBusy => unreachable, + error.FileLocksNotSupported => unreachable, + error.WouldBlock => unreachable, + error.FileBusy => unreachable, // opened without write permissions + + error.IsDir, + error.NotDir, + error.InvalidHandle, + error.AccessDenied, + error.NoDevice, + error.FileNotFound, + error.NetworkNotFound, + error.FileTooBig, + error.Unexpected, + => |e| { + std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); + return defaultAbiAndDynamicLinker(cpu, os, query); + }, + + else => |e| return e, + }; + errdefer file.close(); + + const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) { + error.UnexpectedEndOfFile, + error.UnableToReadElfFile, + => break :blk file, + + else => |e| return e, + }; + const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; + const line = buffer[0..newline]; + if (!mem.startsWith(u8, line, "#!")) break :blk file; + var it = mem.tokenizeScalar(u8, line[2..], ' '); + file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query); + file.close(); + } + }; + defer elf_file.close(); + + // If Zig is statically linked, such as via distributed binary static builds, the above + // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. + // TODO: inline this function and combine the buffer we already read above to find + // the possible shebang line with the buffer we use for the ELF header. + return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) { + error.FileSystem, + error.SystemResources, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + => |e| return e, + + error.UnableToReadElfFile, + error.InvalidElfClass, + error.InvalidElfVersion, + error.InvalidElfEndian, + error.InvalidElfFile, + error.InvalidElfMagic, + error.Unexpected, + error.UnexpectedEndOfFile, + error.NameTooLong, + // Finally, we fall back on the standard path. + => |e| { + std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); + return defaultAbiAndDynamicLinker(cpu, os, query); + }, + }; +} + +fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target { + const abi = query.abi orelse Target.Abi.default(cpu.arch, os); + return .{ + .cpu = cpu, + .os = os, + .abi = abi, + .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), + .dynamic_linker = if (query.dynamic_linker.get() == null) + Target.standardDynamicLinkerPath_cpu_os_abi(cpu, os.tag, abi) + else + query.dynamic_linker, + }; +} + +const LdInfo = struct { + ld: Target.DynamicLinker, + abi: Target.Abi, +}; + +fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { + var i: usize = 0; + while (i < min_read_len) { + const len = file.pread(buf[i..], offset + i) catch |err| switch (err) { + error.OperationAborted => unreachable, // Windows-only + error.WouldBlock => unreachable, // Did not request blocking mode + error.NotOpenForReading => unreachable, + error.SystemResources => return error.SystemResources, + error.IsDir => return error.UnableToReadElfFile, + error.BrokenPipe => return error.UnableToReadElfFile, + error.Unseekable => return error.UnableToReadElfFile, + error.ConnectionResetByPeer => return error.UnableToReadElfFile, + error.ConnectionTimedOut => return error.UnableToReadElfFile, + error.SocketNotConnected => return error.UnableToReadElfFile, + error.NetNameDeleted => return error.UnableToReadElfFile, + error.Unexpected => return error.Unexpected, + error.InputOutput => return error.FileSystem, + error.AccessDenied => return error.Unexpected, + }; + if (len == 0) return error.UnexpectedEndOfFile; + i += len; + } + return i; +} + +fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { + if (is_64) { + if (need_bswap) { + return @byteSwap(int_64); + } else { + return int_64; + } + } else { + if (need_bswap) { + return @byteSwap(int_32); + } else { + return int_32; + } + } +} + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const mem = std.mem; +const elf = std.elf; +const fs = std.fs; +const assert = std.debug.assert; +const Target = std.Target; +const native_endian = builtin.cpu.arch.endian(); + test { _ = NativePaths; - _ = NativeTargetInfo; _ = darwin; _ = linux; diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index 833d698d42..715c172c39 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -5,7 +5,6 @@ const process = std.process; const mem = std.mem; const NativePaths = @This(); -const NativeTargetInfo = std.zig.system.NativeTargetInfo; arena: Allocator, include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, @@ -14,8 +13,7 @@ framework_dirs: std.ArrayListUnmanaged([]const u8) = .{}, rpaths: std.ArrayListUnmanaged([]const u8) = .{}, warnings: std.ArrayListUnmanaged([]const u8) = .{}, -pub fn detect(arena: Allocator, native_info: NativeTargetInfo) !NativePaths { - const native_target = native_info.target; +pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths { var self: NativePaths = .{ .arena = arena }; var is_nix = false; if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig deleted file mode 100644 index c4562cf7d5..0000000000 --- a/lib/std/zig/system/NativeTargetInfo.zig +++ /dev/null @@ -1,1130 +0,0 @@ -const std = @import("../../std.zig"); -const builtin = @import("builtin"); -const mem = std.mem; -const assert = std.debug.assert; -const fs = std.fs; -const elf = std.elf; -const native_endian = builtin.cpu.arch.endian(); - -const NativeTargetInfo = @This(); -const Target = std.Target; -const Allocator = std.mem.Allocator; -const windows = std.zig.system.windows; -const darwin = std.zig.system.darwin; -const linux = std.zig.system.linux; - -target: Target, -dynamic_linker: DynamicLinker = DynamicLinker{}, - -pub const DynamicLinker = Target.DynamicLinker; - -pub const DetectError = error{ - FileSystem, - SystemResources, - SymLinkLoop, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - DeviceBusy, - OSVersionDetectionFail, - Unexpected, -}; - -/// Given a `Target.Query`, which specifies in detail which parts of the -/// target should be detected natively, which should be standard or default, -/// and which are provided explicitly, this function resolves the native -/// components by detecting the native system, and then resolves -/// standard/default parts relative to that. -pub fn detect(query: Target.Query) DetectError!NativeTargetInfo { - var os = query.getOsTag().defaultVersionRange(query.getCpuArch()); - if (query.os_tag == null) { - switch (builtin.target.os.tag) { - .linux => { - const uts = std.os.uname(); - const release = mem.sliceTo(&uts.release, 0); - // The release field sometimes has a weird format, - // `Version.parse` will attempt to find some meaningful interpretation. - if (std.SemanticVersion.parse(release)) |ver| { - os.version_range.linux.range.min = ver; - os.version_range.linux.range.max = ver; - } else |err| switch (err) { - error.Overflow => {}, - error.InvalidVersion => {}, - } - }, - .solaris, .illumos => { - const uts = std.os.uname(); - const release = mem.sliceTo(&uts.release, 0); - if (std.SemanticVersion.parse(release)) |ver| { - os.version_range.semver.min = ver; - os.version_range.semver.max = ver; - } else |err| switch (err) { - error.Overflow => {}, - error.InvalidVersion => {}, - } - }, - .windows => { - const detected_version = windows.detectRuntimeVersion(); - os.version_range.windows.min = detected_version; - os.version_range.windows.max = detected_version; - }, - .macos => try darwin.macos.detect(&os), - .freebsd, .netbsd, .dragonfly => { - const key = switch (builtin.target.os.tag) { - .freebsd => "kern.osreldate", - .netbsd, .dragonfly => "kern.osrevision", - else => unreachable, - }; - var value: u32 = undefined; - var len: usize = @sizeOf(@TypeOf(value)); - - std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { - error.NameTooLong => unreachable, // constant, known good value - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value - error.Unexpected => return error.OSVersionDetectionFail, - }; - - switch (builtin.target.os.tag) { - .freebsd => { - // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html - // Major * 100,000 has been convention since FreeBSD 2.2 (1997) - // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997) - // e.g. 492101 = 4.11-STABLE = 4.(9+2) - const major = value / 100_000; - const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1 - const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since - const patch = value % 1_000; - os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch }; - os.version_range.semver.max = os.version_range.semver.min; - }, - .netbsd => { - // #define __NetBSD_Version__ MMmmrrpp00 - // - // M = major version - // m = minor version; a minor number of 99 indicates current. - // r = 0 (*) - // p = patchlevel - const major = value / 100_000_000; - const minor = value % 100_000_000 / 1_000_000; - const patch = value % 10_000 / 100; - os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; - os.version_range.semver.max = os.version_range.semver.min; - }, - .dragonfly => { - // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17 - // flat base10 format: Mmmmpp - // M = major - // m = minor; odd-numbers indicate current dev branch - // p = patch - const major = value / 100_000; - const minor = value % 100_000 / 100; - const patch = value % 100; - os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; - os.version_range.semver.max = os.version_range.semver.min; - }, - else => unreachable, - } - }, - .openbsd => { - const mib: [2]c_int = [_]c_int{ - std.os.CTL.KERN, - std.os.KERN.OSRELEASE, - }; - var buf: [64]u8 = undefined; - // consider that sysctl result includes null-termination - // reserve 1 byte to ensure we never overflow when appending ".0" - var len: usize = buf.len - 1; - - std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { - error.NameTooLong => unreachable, // constant, known good value - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value - error.Unexpected => return error.OSVersionDetectionFail, - }; - - // append ".0" to satisfy semver - buf[len - 1] = '.'; - buf[len] = '0'; - len += 1; - - if (std.SemanticVersion.parse(buf[0..len])) |ver| { - os.version_range.semver.min = ver; - os.version_range.semver.max = ver; - } else |_| { - return error.OSVersionDetectionFail; - } - }, - else => { - // Unimplemented, fall back to default version range. - }, - } - } - - if (query.os_version_min) |min| switch (min) { - .none => {}, - .semver => |semver| switch (query.getOsTag()) { - .linux => os.version_range.linux.range.min = semver, - else => os.version_range.semver.min = semver, - }, - .windows => |win_ver| os.version_range.windows.min = win_ver, - }; - - if (query.os_version_max) |max| switch (max) { - .none => {}, - .semver => |semver| switch (query.getOsTag()) { - .linux => os.version_range.linux.range.max = semver, - else => os.version_range.semver.max = semver, - }, - .windows => |win_ver| os.version_range.windows.max = win_ver, - }; - - if (query.glibc_version) |glibc| { - assert(query.isGnuLibC()); - os.version_range.linux.glibc = glibc; - } - - // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the - // native CPU architecture as being different than the current target), we use this: - const cpu_arch = query.getCpuArch(); - - const cpu = switch (query.cpu_model) { - .native => detectNativeCpuAndFeatures(cpu_arch, os, query), - .baseline => Target.Cpu.baseline(cpu_arch), - .determined_by_cpu_arch => if (query.cpu_arch == null) - detectNativeCpuAndFeatures(cpu_arch, os, query) - else - Target.Cpu.baseline(cpu_arch), - .explicit => |model| model.toCpu(cpu_arch), - } orelse backup_cpu_detection: { - break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); - }; - var result = try detectAbiAndDynamicLinker(cpu, os, query); - // For x86, we need to populate some CPU feature flags depending on architecture - // and mode: - // * 16bit_mode => if the abi is code16 - // * 32bit_mode => if the arch is x86 - // However, the "mode" flags can be used as overrides, so if the user explicitly - // sets one of them, that takes precedence. - switch (cpu_arch) { - .x86 => { - if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{ - .@"16bit_mode", .@"32bit_mode", - })) { - switch (result.target.abi) { - .code16 => result.target.cpu.features.addFeature( - @intFromEnum(Target.x86.Feature.@"16bit_mode"), - ), - else => result.target.cpu.features.addFeature( - @intFromEnum(Target.x86.Feature.@"32bit_mode"), - ), - } - } - }, - .arm, .armeb => { - // XXX What do we do if the target has the noarm feature? - // What do we do if the user specifies +thumb_mode? - }, - .thumb, .thumbeb => { - result.target.cpu.features.addFeature( - @intFromEnum(Target.arm.Feature.thumb_mode), - ); - }, - else => {}, - } - query.updateCpuFeatures(&result.target.cpu.features); - return result; -} - -/// In the past, this function attempted to use the executable's own binary if it was dynamically -/// linked to answer both the C ABI question and the dynamic linker question. However, this -/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking -/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc -/// version. The problem is that libc.so.6 glibc version will match that of the system while -/// the dynamic linker will match that of the compiler binary. Executables with these versions -/// mismatching will fail to run. -/// -/// Therefore, this function works the same regardless of whether the compiler binary is -/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the -/// answer to these questions, or if there is a shebang line, then it chases the referenced -/// file recursively. If that does not provide the answer, then the function falls back to -/// defaults. -fn detectAbiAndDynamicLinker( - cpu: Target.Cpu, - os: Target.Os, - query: Target.Query, -) DetectError!NativeTargetInfo { - const native_target_has_ld = comptime builtin.target.hasDynamicLinker(); - const is_linux = builtin.target.os.tag == .linux; - const is_solarish = builtin.target.os.tag.isSolarish(); - const have_all_info = query.dynamic_linker.get() != null and - query.abi != null and (!is_linux or query.abi.?.isGnu()); - const os_is_non_native = query.os_tag != null; - // The Solaris/illumos environment is always the same. - if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) { - return defaultAbiAndDynamicLinker(cpu, os, query); - } - if (query.abi) |abi| { - if (abi.isMusl()) { - // musl implies static linking. - return defaultAbiAndDynamicLinker(cpu, os, query); - } - } - // The current target's ABI cannot be relied on for this. For example, we may build the zig - // compiler for target riscv64-linux-musl and provide a tarball for users to download. - // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined - // and supported by Zig. But that means that we must detect the system ABI here rather than - // relying on `builtin.target`. - const all_abis = comptime blk: { - assert(@intFromEnum(Target.Abi.none) == 0); - const fields = std.meta.fields(Target.Abi)[1..]; - var array: [fields.len]Target.Abi = undefined; - for (fields, 0..) |field, i| { - array[i] = @field(Target.Abi, field.name); - } - break :blk array; - }; - var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; - var ld_info_list_len: usize = 0; - const ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); - - for (all_abis) |abi| { - // This may be a nonsensical parameter. We detect this with - // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`. - const target: Target = .{ - .cpu = cpu, - .os = os, - .abi = abi, - .ofmt = ofmt, - }; - const ld = target.standardDynamicLinkerPath(); - if (ld.get() == null) continue; - - ld_info_list_buffer[ld_info_list_len] = .{ - .ld = ld, - .abi = abi, - }; - ld_info_list_len += 1; - } - const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; - - // Best case scenario: the executable is dynamically linked, and we can iterate - // over our own shared objects and find a dynamic linker. - const elf_file = blk: { - // This block looks for a shebang line in /usr/bin/env, - // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, - // doing the same logic recursively in case it finds another shebang line. - - // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a - // reasonably reliable path to start with. - var file_name: []const u8 = "/usr/bin/env"; - // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) - var buffer: [258]u8 = undefined; - while (true) { - const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { - error.NoSpaceLeft => unreachable, - error.NameTooLong => unreachable, - error.PathAlreadyExists => unreachable, - error.SharingViolation => unreachable, - error.InvalidUtf8 => unreachable, - error.BadPathName => unreachable, - error.PipeBusy => unreachable, - error.FileLocksNotSupported => unreachable, - error.WouldBlock => unreachable, - error.FileBusy => unreachable, // opened without write permissions - - error.IsDir, - error.NotDir, - error.InvalidHandle, - error.AccessDenied, - error.NoDevice, - error.FileNotFound, - error.NetworkNotFound, - error.FileTooBig, - error.Unexpected, - => |e| { - std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); - return defaultAbiAndDynamicLinker(cpu, os, query); - }, - - else => |e| return e, - }; - errdefer file.close(); - - const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) { - error.UnexpectedEndOfFile, - error.UnableToReadElfFile, - => break :blk file, - - else => |e| return e, - }; - const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; - const line = buffer[0..newline]; - if (!mem.startsWith(u8, line, "#!")) break :blk file; - var it = mem.tokenizeScalar(u8, line[2..], ' '); - file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query); - file.close(); - } - }; - defer elf_file.close(); - - // If Zig is statically linked, such as via distributed binary static builds, the above - // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. - // TODO: inline this function and combine the buffer we already read above to find - // the possible shebang line with the buffer we use for the ELF header. - return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) { - error.FileSystem, - error.SystemResources, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded, - => |e| return e, - - error.UnableToReadElfFile, - error.InvalidElfClass, - error.InvalidElfVersion, - error.InvalidElfEndian, - error.InvalidElfFile, - error.InvalidElfMagic, - error.Unexpected, - error.UnexpectedEndOfFile, - error.NameTooLong, - // Finally, we fall back on the standard path. - => |e| { - std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); - return defaultAbiAndDynamicLinker(cpu, os, query); - }, - }; -} - -fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { - var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.InvalidUtf8 => unreachable, - error.BadPathName => unreachable, - error.DeviceBusy => unreachable, - error.NetworkNotFound => unreachable, // Windows-only - - error.FileNotFound, - error.NotDir, - error.InvalidHandle, - error.AccessDenied, - error.NoDevice, - => return error.GLibCNotFound, - - error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded, - error.SystemResources, - error.SymLinkLoop, - error.Unexpected, - => |e| return e, - }; - defer dir.close(); - - // Now we have a candidate for the path to libc shared object. In - // the past, we used readlink() here because the link name would - // reveal the glibc version. However, in more recent GNU/Linux - // installations, there is no symlink. Thus we instead use a more - // robust check of opening the libc shared object and looking at the - // .dynstr section, and finding the max version number of symbols - // that start with "GLIBC_2.". - const glibc_so_basename = "libc.so.6"; - var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.InvalidUtf8 => unreachable, // Windows only - error.BadPathName => unreachable, // Windows only - error.PipeBusy => unreachable, // Windows-only - error.SharingViolation => unreachable, // Windows-only - error.NetworkNotFound => unreachable, // Windows-only - error.FileLocksNotSupported => unreachable, // No lock requested. - error.NoSpaceLeft => unreachable, // read-only - error.PathAlreadyExists => unreachable, // read-only - error.DeviceBusy => unreachable, // read-only - error.FileBusy => unreachable, // read-only - error.InvalidHandle => unreachable, // should not be in the error set - error.WouldBlock => unreachable, // not using O_NONBLOCK - error.NoDevice => unreachable, // not asking for a special device - - error.AccessDenied, - error.FileNotFound, - error.NotDir, - error.IsDir, - => return error.GLibCNotFound, - - error.FileTooBig => return error.Unexpected, - - error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded, - error.SystemResources, - error.SymLinkLoop, - error.Unexpected, - => |e| return e, - }; - defer f.close(); - - return glibcVerFromSoFile(f) catch |err| switch (err) { - error.InvalidElfMagic, - error.InvalidElfEndian, - error.InvalidElfClass, - error.InvalidElfFile, - error.InvalidElfVersion, - error.InvalidGnuLibCVersion, - error.UnexpectedEndOfFile, - => return error.GLibCNotFound, - - error.SystemResources, - error.UnableToReadElfFile, - error.Unexpected, - error.FileSystem, - => |e| return e, - }; -} - -fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { - var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; - _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); - const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); - const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); - if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, - }; - const need_bswap = elf_endian != native_endian; - if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; - - const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { - elf.ELFCLASS32 => false, - elf.ELFCLASS64 => true, - else => return error.InvalidElfClass, - }; - const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); - var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); - const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); - const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); - var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; - if (sh_buf.len < shentsize) return error.InvalidElfFile; - - _ = try preadMin(file, &sh_buf, str_section_off, shentsize); - const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); - const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); - var strtab_buf: [4096:0]u8 = undefined; - const shstrtab_len = @min(shstrtab_size, strtab_buf.len); - const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); - const shstrtab = strtab_buf[0..shstrtab_read_len]; - const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); - var sh_i: u16 = 0; - const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); - const sh_read_byte_len = try preadMin( - file, - sh_buf[0 .. sh_buf.len - sh_reserve], - shoff, - shentsize, - ); - var sh_buf_i: usize = 0; - while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ - sh_i += 1; - shoff += shentsize; - sh_buf_i += shentsize; - }) { - const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); - const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); - if (mem.eql(u8, sh_name, ".dynstr")) { - break :find_dyn_str .{ - .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), - .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), - }; - } - } - } else return error.InvalidGnuLibCVersion; - - // Here we loop over all the strings in the dynstr string table, assuming that any - // strings that start with "GLIBC_2." indicate the existence of such a glibc version, - // and furthermore, that the system-installed glibc is at minimum that version. - - // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. - // Here I use double this value plus some headroom. This makes it only need - // a single read syscall here. - var buf: [80000]u8 = undefined; - if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion; - - const dynstr_size: usize = @intCast(dynstr.size); - const dynstr_bytes = buf[0..dynstr_size]; - _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len); - var it = mem.splitScalar(u8, dynstr_bytes, 0); - var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 }; - while (it.next()) |s| { - if (mem.startsWith(u8, s, "GLIBC_2.")) { - const chopped = s["GLIBC_".len..]; - const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) { - error.Overflow => return error.InvalidGnuLibCVersion, - error.InvalidVersion => return error.InvalidGnuLibCVersion, - }; - switch (ver.order(max_ver)) { - .gt => max_ver = ver, - .lt, .eq => continue, - } - } - } - return max_ver; -} - -fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion { - // example: "libc-2.3.4.so" - // example: "libc-2.27.so" - // example: "ld-2.33.so" - const suffix = ".so"; - if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { - return error.UnrecognizedGnuLibCFileName; - } - // chop off "libc-" and ".so" - const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; - return Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) { - error.Overflow => return error.InvalidGnuLibCVersion, - error.InvalidVersion => return error.InvalidGnuLibCVersion, - }; -} - -test glibcVerFromLinkName { - try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist")); - try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-")); - - try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-")); - try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-")); - try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-")); - try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-")); - try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-")); -} - -pub const AbiAndDynamicLinkerFromFileError = error{ - FileSystem, - SystemResources, - SymLinkLoop, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - UnableToReadElfFile, - InvalidElfClass, - InvalidElfVersion, - InvalidElfEndian, - InvalidElfFile, - InvalidElfMagic, - Unexpected, - UnexpectedEndOfFile, - NameTooLong, -}; - -pub fn abiAndDynamicLinkerFromFile( - file: fs.File, - cpu: Target.Cpu, - os: Target.Os, - ld_info_list: []const LdInfo, - query: Target.Query, -) AbiAndDynamicLinkerFromFileError!NativeTargetInfo { - var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; - _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); - const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); - const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); - if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, - }; - const need_bswap = elf_endian != native_endian; - if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; - - const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { - elf.ELFCLASS32 => false, - elf.ELFCLASS64 => true, - else => return error.InvalidElfClass, - }; - var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); - const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); - const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); - - var result: NativeTargetInfo = .{ - .target = .{ - .cpu = cpu, - .os = os, - .abi = query.abi orelse Target.Abi.default(cpu.arch, os), - .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), - }, - .dynamic_linker = query.dynamic_linker, - }; - var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC - const look_for_ld = query.dynamic_linker.get() == null; - - var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; - if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; - - var ph_i: u16 = 0; - while (ph_i < phnum) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); - const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); - var ph_buf_i: usize = 0; - while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ - ph_i += 1; - phoff += phentsize; - ph_buf_i += phentsize; - }) { - const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); - const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); - const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); - switch (p_type) { - elf.PT_INTERP => if (look_for_ld) { - const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); - const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); - if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; - const filesz = @as(usize, @intCast(p_filesz)); - _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz); - // PT_INTERP includes a null byte in filesz. - const len = filesz - 1; - // dynamic_linker.max_byte is "max", not "len". - // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. - result.dynamic_linker.max_byte = @as(u8, @intCast(len - 1)); - - // Use it to determine ABI. - const full_ld_path = result.dynamic_linker.buffer[0..len]; - for (ld_info_list) |ld_info| { - const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); - if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { - result.target.abi = ld_info.abi; - break; - } - } - }, - // We only need this for detecting glibc version. - elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and - query.glibc_version == null) - { - var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); - const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); - const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); - const dyn_num = p_filesz / dyn_size; - var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; - var dyn_i: usize = 0; - dyn: while (dyn_i < dyn_num) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); - const dyn_read_byte_len = try preadMin( - file, - dyn_buf[0 .. dyn_buf.len - dyn_reserve], - dyn_off, - dyn_size, - ); - var dyn_buf_i: usize = 0; - while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ - dyn_i += 1; - dyn_off += dyn_size; - dyn_buf_i += dyn_size; - }) { - const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); - const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); - const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); - const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); - if (tag == elf.DT_RUNPATH) { - rpath_offset = val; - break :dyn; - } - } - } - }, - else => continue, - } - } - } - - if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and - query.glibc_version == null) - { - const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); - - var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); - const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); - const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); - - var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; - if (sh_buf.len < shentsize) return error.InvalidElfFile; - - _ = try preadMin(file, &sh_buf, str_section_off, shentsize); - const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); - const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); - var strtab_buf: [4096:0]u8 = undefined; - const shstrtab_len = @min(shstrtab_size, strtab_buf.len); - const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); - const shstrtab = strtab_buf[0..shstrtab_read_len]; - - const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); - var sh_i: u16 = 0; - const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); - const sh_read_byte_len = try preadMin( - file, - sh_buf[0 .. sh_buf.len - sh_reserve], - shoff, - shentsize, - ); - var sh_buf_i: usize = 0; - while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ - sh_i += 1; - shoff += shentsize; - sh_buf_i += shentsize; - }) { - const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); - const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); - if (mem.eql(u8, sh_name, ".dynstr")) { - break :find_dyn_str .{ - .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), - .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), - }; - } - } - } else null; - - if (dynstr) |ds| { - if (rpath_offset) |rpoff| { - if (rpoff > ds.size) return error.InvalidElfFile; - const rpoff_file = ds.offset + rpoff; - const rp_max_size = ds.size - rpoff; - - const strtab_len = @min(rp_max_size, strtab_buf.len); - const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len); - const strtab = strtab_buf[0..strtab_read_len]; - - const rpath_list = mem.sliceTo(strtab, 0); - var it = mem.tokenizeScalar(u8, rpath_list, ':'); - while (it.next()) |rpath| { - if (glibcVerFromRPath(rpath)) |ver| { - result.target.os.version_range.linux.glibc = ver; - return result; - } else |err| switch (err) { - error.GLibCNotFound => continue, - else => |e| return e, - } - } - } - } - - if (result.dynamic_linker.get()) |dl_path| glibc_ver: { - // There is no DT_RUNPATH so we try to find libc.so.6 inside the same - // directory as the dynamic linker. - if (fs.path.dirname(dl_path)) |rpath| { - if (glibcVerFromRPath(rpath)) |ver| { - result.target.os.version_range.linux.glibc = ver; - return result; - } else |err| switch (err) { - error.GLibCNotFound => {}, - else => |e| return e, - } - } - - // So far, no luck. Next we try to see if the information is - // present in the symlink data for the dynamic linker path. - var link_buf: [std.os.PATH_MAX]u8 = undefined; - const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.InvalidUtf8 => unreachable, // Windows only - error.BadPathName => unreachable, // Windows only - error.UnsupportedReparsePointType => unreachable, // Windows only - error.NetworkNotFound => unreachable, // Windows only - - error.AccessDenied, - error.FileNotFound, - error.NotLink, - error.NotDir, - => break :glibc_ver, - - error.SystemResources, - error.FileSystem, - error.SymLinkLoop, - error.Unexpected, - => |e| return e, - }; - result.target.os.version_range.linux.glibc = glibcVerFromLinkName( - fs.path.basename(link_name), - "ld-", - ) catch |err| switch (err) { - error.UnrecognizedGnuLibCFileName, - error.InvalidGnuLibCVersion, - => break :glibc_ver, - }; - return result; - } - - // Nothing worked so far. Finally we fall back to hard-coded search paths. - // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`. - var path_buf: [std.os.PATH_MAX]u8 = undefined; - var index: usize = 0; - const prefix = "/lib/"; - const cpu_arch = @tagName(result.target.cpu.arch); - const os_tag = @tagName(result.target.os.tag); - const abi = @tagName(result.target.abi); - @memcpy(path_buf[index..][0..prefix.len], prefix); - index += prefix.len; - @memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch); - index += cpu_arch.len; - path_buf[index] = '-'; - index += 1; - @memcpy(path_buf[index..][0..os_tag.len], os_tag); - index += os_tag.len; - path_buf[index] = '-'; - index += 1; - @memcpy(path_buf[index..][0..abi.len], abi); - index += abi.len; - const rpath = path_buf[0..index]; - if (glibcVerFromRPath(rpath)) |ver| { - result.target.os.version_range.linux.glibc = ver; - return result; - } else |err| switch (err) { - error.GLibCNotFound => {}, - else => |e| return e, - } - } - - return result; -} - -fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { - var i: usize = 0; - while (i < min_read_len) { - const len = file.pread(buf[i..], offset + i) catch |err| switch (err) { - error.OperationAborted => unreachable, // Windows-only - error.WouldBlock => unreachable, // Did not request blocking mode - error.NotOpenForReading => unreachable, - error.SystemResources => return error.SystemResources, - error.IsDir => return error.UnableToReadElfFile, - error.BrokenPipe => return error.UnableToReadElfFile, - error.Unseekable => return error.UnableToReadElfFile, - error.ConnectionResetByPeer => return error.UnableToReadElfFile, - error.ConnectionTimedOut => return error.UnableToReadElfFile, - error.SocketNotConnected => return error.UnableToReadElfFile, - error.NetNameDeleted => return error.UnableToReadElfFile, - error.Unexpected => return error.Unexpected, - error.InputOutput => return error.FileSystem, - error.AccessDenied => return error.Unexpected, - }; - if (len == 0) return error.UnexpectedEndOfFile; - i += len; - } - return i; -} - -fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !NativeTargetInfo { - const target: Target = .{ - .cpu = cpu, - .os = os, - .abi = query.abi orelse Target.Abi.default(cpu.arch, os), - .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), - }; - return NativeTargetInfo{ - .target = target, - .dynamic_linker = if (query.dynamic_linker.get() == null) - target.standardDynamicLinkerPath() - else - query.dynamic_linker, - }; -} - -pub const LdInfo = struct { - ld: DynamicLinker, - abi: Target.Abi, -}; - -pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { - if (is_64) { - if (need_bswap) { - return @byteSwap(int_64); - } else { - return int_64; - } - } else { - if (need_bswap) { - return @byteSwap(int_32); - } else { - return int_32; - } - } -} - -fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu { - // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`, - // although it is a runtime value, is guaranteed to be one of the architectures in the set - // of the respective switch prong. - switch (builtin.cpu.arch) { - .x86_64, .x86 => { - return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query); - }, - else => {}, - } - - switch (builtin.os.tag) { - .linux => return linux.detectNativeCpuAndFeatures(), - .macos => return darwin.macos.detectNativeCpuAndFeatures(), - .windows => return windows.detectNativeCpuAndFeatures(), - else => {}, - } - - // This architecture does not have CPU model & feature detection yet. - // See https://github.com/ziglang/zig/issues/4591 - return null; -} - -pub const Executor = union(enum) { - native, - rosetta, - qemu: []const u8, - wine: []const u8, - wasmtime: []const u8, - darling: []const u8, - bad_dl: []const u8, - bad_os_or_cpu, -}; - -pub const GetExternalExecutorOptions = struct { - allow_darling: bool = true, - allow_qemu: bool = true, - allow_rosetta: bool = true, - allow_wasmtime: bool = true, - allow_wine: bool = true, - qemu_fixes_dl: bool = false, - link_libc: bool = false, -}; - -/// Return whether or not the given host is capable of running executables of -/// the other target. -pub fn getExternalExecutor( - host: NativeTargetInfo, - candidate: *const NativeTargetInfo, - options: GetExternalExecutorOptions, -) Executor { - const os_match = host.target.os.tag == candidate.target.os.tag; - const cpu_ok = cpu_ok: { - if (host.target.cpu.arch == candidate.target.cpu.arch) - break :cpu_ok true; - - if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .x86) - break :cpu_ok true; - - if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm) - break :cpu_ok true; - - if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb) - break :cpu_ok true; - - // TODO additionally detect incompatible CPU features. - // Note that in some cases the OS kernel will emulate missing CPU features - // when an illegal instruction is encountered. - - break :cpu_ok false; - }; - - var bad_result: Executor = .bad_os_or_cpu; - - if (os_match and cpu_ok) native: { - if (options.link_libc) { - if (candidate.dynamic_linker.get()) |candidate_dl| { - fs.cwd().access(candidate_dl, .{}) catch { - bad_result = .{ .bad_dl = candidate_dl }; - break :native; - }; - } - } - return .native; - } - - // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 - // to emulate the foreign architecture. - if (options.allow_rosetta and os_match and - host.target.os.tag == .macos and host.target.cpu.arch == .aarch64) - { - switch (candidate.target.cpu.arch) { - .x86_64 => return .rosetta, - else => return bad_result, - } - } - - // If the OS matches, we can use QEMU to emulate a foreign architecture. - if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { - return switch (candidate.target.cpu.arch) { - .aarch64 => Executor{ .qemu = "qemu-aarch64" }, - .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, - .arm => Executor{ .qemu = "qemu-arm" }, - .armeb => Executor{ .qemu = "qemu-armeb" }, - .hexagon => Executor{ .qemu = "qemu-hexagon" }, - .x86 => Executor{ .qemu = "qemu-i386" }, - .m68k => Executor{ .qemu = "qemu-m68k" }, - .mips => Executor{ .qemu = "qemu-mips" }, - .mipsel => Executor{ .qemu = "qemu-mipsel" }, - .mips64 => Executor{ .qemu = "qemu-mips64" }, - .mips64el => Executor{ .qemu = "qemu-mips64el" }, - .powerpc => Executor{ .qemu = "qemu-ppc" }, - .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, - .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, - .riscv32 => Executor{ .qemu = "qemu-riscv32" }, - .riscv64 => Executor{ .qemu = "qemu-riscv64" }, - .s390x => Executor{ .qemu = "qemu-s390x" }, - .sparc => Executor{ .qemu = "qemu-sparc" }, - .sparc64 => Executor{ .qemu = "qemu-sparc64" }, - .x86_64 => Executor{ .qemu = "qemu-x86_64" }, - else => return bad_result, - }; - } - - switch (candidate.target.os.tag) { - .windows => { - if (options.allow_wine) { - // x86_64 wine does not support emulating aarch64-windows and - // vice versa. - if (candidate.target.cpu.arch != builtin.cpu.arch) { - return bad_result; - } - switch (candidate.target.ptrBitWidth()) { - 32 => return Executor{ .wine = "wine" }, - 64 => return Executor{ .wine = "wine64" }, - else => return bad_result, - } - } - return bad_result; - }, - .wasi => { - if (options.allow_wasmtime) { - switch (candidate.target.ptrBitWidth()) { - 32 => return Executor{ .wasmtime = "wasmtime" }, - else => return bad_result, - } - } - return bad_result; - }, - .macos => { - if (options.allow_darling) { - // This check can be loosened once darling adds a QEMU-based emulation - // layer for non-host architectures: - // https://github.com/darlinghq/darling/issues/863 - if (candidate.target.cpu.arch != builtin.cpu.arch) { - return bad_result; - } - return Executor{ .darling = "darling" }; - } - return bad_result; - }, - else => return bad_result, - } -} diff --git a/src/Compilation.zig b/src/Compilation.zig index 7831a58840..0b275f97c1 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6527,7 +6527,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)}); } } - try buffer.writer().print( \\ }}), \\}}; @@ -6607,15 +6606,31 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca .{ windows.min, windows.max }, ), } - try buffer.appendSlice("};\n"); - - try buffer.writer().print( - \\pub const target = std.Target{{ + try buffer.appendSlice( + \\}; + \\pub const target: std.Target = .{ \\ .cpu = cpu, \\ .os = os, \\ .abi = abi, \\ .ofmt = object_format, - \\}}; + \\ + ); + + if (target.dynamic_linker.get()) |dl| { + try buffer.writer().print( + \\ .dynamic_linker = std.Target.DynamicLinker.init("{s}"), + \\}}; + \\ + , .{dl}); + } else { + try buffer.appendSlice( + \\ .dynamic_linker = std.Target.DynamicLinker.none, + \\}; + \\ + ); + } + + try buffer.writer().print( \\pub const object_format = std.Target.ObjectFormat.{}; \\pub const mode = std.builtin.OptimizeMode.{}; \\pub const link_libc = {}; diff --git a/src/main.zig b/src/main.zig index 17603acf2c..716be60763 100644 --- a/src/main.zig +++ b/src/main.zig @@ -321,13 +321,14 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi } else if (mem.eql(u8, cmd, "init")) { return cmdInit(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { - const info = try detectNativeTargetInfo(.{}); + const host = try std.zig.system.resolveTargetQuery(.{}); const stdout = io.getStdOut().writer(); - return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); + return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, host); } else if (mem.eql(u8, cmd, "version")) { try std.io.getStdOut().writeAll(build_options.version ++ "\n"); - // Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version" - // to avoid affecting the startup time for build-critical commands (check takes about ~10 μs) + // Check libc++ linkage to make sure Zig was built correctly, but only + // for "env" and "version" to avoid affecting the startup time for + // build-critical commands (check takes about ~10 μs) return verifyLibcxxCorrectlyLinked(); } else if (mem.eql(u8, cmd, "env")) { verifyLibcxxCorrectlyLinked(); @@ -2608,9 +2609,9 @@ fn buildOutputType( } const target_query = try parseTargetQueryOrReportFatalError(arena, target_parse_options); - const target_info = try detectNativeTargetInfo(target_query); + const target = try std.zig.system.resolveTargetQuery(target_query); - if (target_info.target.os.tag != .freestanding) { + if (target.os.tag != .freestanding) { if (ensure_libc_on_non_freestanding) link_libc = true; if (ensure_libcpp_on_non_freestanding) @@ -2621,7 +2622,7 @@ fn buildOutputType( if (!force) { entry = null; } else if (entry == null and output_mode == .Exe) { - entry = switch (target_info.target.ofmt) { + entry = switch (target.ofmt) { .coff => "wWinMainCRTStartup", .macho => "_main", .elf, .plan9 => "_start", @@ -2629,12 +2630,12 @@ fn buildOutputType( else => |tag| fatal("No default entry point available for output format {s}", .{@tagName(tag)}), }; } - } else if (entry == null and target_info.target.isWasm() and output_mode == .Exe) { + } else if (entry == null and target.isWasm() and output_mode == .Exe) { // For WebAssembly the compiler defaults to setting the entry name when no flags are set. entry = defaultWasmEntryName(wasi_exec_model); } - if (target_info.target.ofmt == .coff) { + if (target.ofmt == .coff) { // Now that we know the target supports resources, // we can add the res files as link objects. for (res_files.items) |res_file| { @@ -2652,7 +2653,7 @@ fn buildOutputType( } } - if (target_info.target.cpu.arch.isWasm()) blk: { + if (target.cpu.arch.isWasm()) blk: { if (single_threaded == null) { single_threaded = true; } @@ -2678,8 +2679,8 @@ fn buildOutputType( fatal("shared memory is not allowed in object files", .{}); } - if (!target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or - !target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory))) + if (!target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or + !target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory))) { fatal("'atomics' and 'bulk-memory' features must be enabled to use shared memory", .{}); } @@ -2777,15 +2778,15 @@ fn buildOutputType( } for (system_libs.keys(), system_libs.values()) |lib_name, info| { - if (target_info.target.is_libc_lib_name(lib_name)) { + if (target.is_libc_lib_name(lib_name)) { link_libc = true; continue; } - if (target_info.target.is_libcpp_lib_name(lib_name)) { + if (target.is_libcpp_lib_name(lib_name)) { link_libcpp = true; continue; } - switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) { + switch (target_util.classifyCompilerRtLibName(target, lib_name)) { .none => {}, .only_libunwind, .both => { link_libunwind = true; @@ -2797,8 +2798,8 @@ fn buildOutputType( }, } - if (target_info.target.isMinGW()) { - const exists = mingw.libExists(arena, target_info.target, zig_lib_directory, lib_name) catch |err| { + if (target.isMinGW()) { + const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| { fatal("failed to check zig installation for DLL import libs: {s}", .{ @errorName(err), }); @@ -2820,7 +2821,7 @@ fn buildOutputType( fatal("cannot use absolute path as a system library: {s}", .{lib_name}); } - if (target_info.target.os.tag == .wasi) { + if (target.os.tag == .wasi) { if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| { try wasi_emulated_libs.append(crt_file); continue; @@ -2838,7 +2839,7 @@ fn buildOutputType( if (sysroot == null and target_query.isNativeOs() and target_query.isNativeAbi() and (external_system_libs.len != 0 or want_native_include_dirs)) { - const paths = std.zig.system.NativePaths.detect(arena, target_info) catch |err| { + const paths = std.zig.system.NativePaths.detect(arena, target) catch |err| { fatal("unable to detect native system paths: {s}", .{@errorName(err)}); }; for (paths.warnings.items) |warning| { @@ -2857,7 +2858,7 @@ fn buildOutputType( } if (builtin.target.os.tag == .windows and - target_info.target.abi == .msvc and + target.abi == .msvc and external_system_libs.len != 0) { if (libc_installation == null) { @@ -2902,7 +2903,7 @@ fn buildOutputType( &checked_paths, lib_dir_path, lib_name, - target_info.target, + target, info.preferred_mode, )) { const path = try arena.dupe(u8, test_path.items); @@ -2936,7 +2937,7 @@ fn buildOutputType( &checked_paths, lib_dir_path, lib_name, - target_info.target, + target, info.fallbackMode(), )) { const path = try arena.dupe(u8, test_path.items); @@ -2970,7 +2971,7 @@ fn buildOutputType( &checked_paths, lib_dir_path, lib_name, - target_info.target, + target, info.preferred_mode, )) { const path = try arena.dupe(u8, test_path.items); @@ -2994,7 +2995,7 @@ fn buildOutputType( &checked_paths, lib_dir_path, lib_name, - target_info.target, + target, info.fallbackMode(), )) { const path = try arena.dupe(u8, test_path.items); @@ -3089,15 +3090,13 @@ fn buildOutputType( } // After this point, resolved_frameworks is used instead of frameworks. - const object_format = target_info.target.ofmt; - - if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) { + if (output_mode == .Obj and (target.ofmt == .coff or target.ofmt == .macho)) { const total_obj_count = c_source_files.items.len + @intFromBool(root_src_file != null) + rc_source_files.items.len + link_objects.items.len; if (total_obj_count > 1) { - fatal("{s} does not support linking multiple objects into one", .{@tagName(object_format)}); + fatal("{s} does not support linking multiple objects into one", .{@tagName(target.ofmt)}); } } @@ -3110,7 +3109,7 @@ fn buildOutputType( const resolved_soname: ?[]const u8 = switch (soname) { .yes => |explicit| explicit, .no => null, - .yes_default_value => switch (object_format) { + .yes_default_value => switch (target.ofmt) { .elf => if (have_version) try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ root_name, version.major }) else @@ -3119,7 +3118,7 @@ fn buildOutputType( }, }; - const a_out_basename = switch (object_format) { + const a_out_basename = switch (target.ofmt) { .coff => "a.exe", else => "a.out", }; @@ -3141,7 +3140,7 @@ fn buildOutputType( }, .basename = try std.zig.binNameAlloc(arena, .{ .root_name = root_name, - .target = target_info.target, + .target = target, .output_mode = output_mode, .link_mode = link_mode, .version = optional_version, @@ -3269,7 +3268,7 @@ fn buildOutputType( // Note that cmake when targeting Windows will try to execute // zig cc to make an executable and output an implib too. const implib_eligible = is_exe_or_dyn_lib and - emit_bin_loc != null and target_info.target.os.tag == .windows; + emit_bin_loc != null and target.os.tag == .windows; if (!implib_eligible) { if (!emit_implib_arg_provided) { emit_implib = .no; @@ -3419,7 +3418,7 @@ fn buildOutputType( // "-" is stdin. Dump it to a real file. const sep = fs.path.sep_str; const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{ - std.crypto.random.int(u64), ext.canonicalName(target_info.target), + std.crypto.random.int(u64), ext.canonicalName(target), }); try local_cache_directory.handle.makePath("tmp"); // Note that in one of the happy paths, execve() is used to switch @@ -3454,10 +3453,10 @@ fn buildOutputType( .local_cache_directory = local_cache_directory, .global_cache_directory = global_cache_directory, .root_name = root_name, - .target = target_info.target, + .target = target, .is_native_os = target_query.isNativeOs(), .is_native_abi = target_query.isNativeAbi(), - .dynamic_linker = target_info.dynamic_linker.get(), + .dynamic_linker = target.dynamic_linker.get(), .sysroot = sysroot, .output_mode = output_mode, .main_mod = main_mod, @@ -3603,7 +3602,6 @@ fn buildOutputType( .want_structured_cfg = want_structured_cfg, }) catch |err| switch (err) { error.LibCUnavailable => { - const target = target_info.target; const triple_name = try target.zigTriple(arena); std.log.err("unable to find or provide libc for target '{s}'", .{triple_name}); @@ -3692,7 +3690,7 @@ fn buildOutputType( try comp.makeBinFileExecutable(); saveState(comp, debug_incremental); - if (test_exec_args.items.len == 0 and object_format == .c) default_exec_args: { + if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: { // Default to using `zig run` to execute the produced .c code from `zig test`. const c_code_loc = emit_bin_loc orelse break :default_exec_args; const c_code_directory = c_code_loc.directory orelse comp.bin_file.options.emit.?.directory; @@ -3707,7 +3705,7 @@ fn buildOutputType( if (link_libc) { try test_exec_args.append("-lc"); - } else if (target_info.target.os.tag == .windows) { + } else if (target.os.tag == .windows) { try test_exec_args.appendSlice(&.{ "--subsystem", "console", "-lkernel32", "-lntdll", @@ -3741,7 +3739,7 @@ fn buildOutputType( test_exec_args.items, self_exe_path.?, arg_mode, - &target_info, + &target, &comp_destroyed, all_args, runtime_args_start, @@ -3861,7 +3859,7 @@ fn serve( // test_exec_args, // self_exe_path.?, // arg_mode, - // target_info, + // target, // true, // &comp_destroyed, // all_args, @@ -4071,7 +4069,7 @@ fn runOrTest( test_exec_args: []const ?[]const u8, self_exe_path: []const u8, arg_mode: ArgMode, - target_info: *const std.zig.system.NativeTargetInfo, + target: *const std.Target, comp_destroyed: *bool, all_args: []const []const u8, runtime_args_start: ?usize, @@ -4105,7 +4103,7 @@ fn runOrTest( if (process.can_execv and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. const err = process.execve(gpa, argv.items, &env_map); - try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); + try warnAboutForeignBinaries(arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd }); } else if (process.can_spawn) { @@ -4121,7 +4119,7 @@ fn runOrTest( comp_destroyed.* = true; const term = child.spawnAndWait() catch |err| { - try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); + try warnAboutForeignBinaries(arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd }); }; @@ -4820,12 +4818,10 @@ pub fn cmdLibC(gpa: Allocator, args: []const []const u8) !void { if (!target_query.isNative()) { fatal("unable to detect libc for non-native target", .{}); } - const target_info = try detectNativeTargetInfo(target_query); - var libc = LibCInstallation.findNative(.{ .allocator = gpa, .verbose = true, - .target = target_info.target, + .target = try std.zig.system.resolveTargetQuery(target_query), }) catch |err| { fatal("unable to detect native libc: {s}", .{@errorName(err)}); }; @@ -5114,11 +5110,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi gimmeMoreOfThoseSweetSweetFileDescriptors(); const target_query: std.Target.Query = .{}; - const target_info = try detectNativeTargetInfo(target_query); + const target = try std.zig.system.resolveTargetQuery(target_query); const exe_basename = try std.zig.binNameAlloc(arena, .{ .root_name = "build", - .target = target_info.target, + .target = target, .output_mode = .Exe, }); const emit_bin: Compilation.EmitLoc = .{ @@ -5282,10 +5278,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi .local_cache_directory = local_cache_directory, .global_cache_directory = global_cache_directory, .root_name = "build", - .target = target_info.target, + .target = target, .is_native_os = target_query.isNativeOs(), .is_native_abi = target_query.isNativeAbi(), - .dynamic_linker = target_info.dynamic_linker.get(), + .dynamic_linker = target.dynamic_linker.get(), .output_mode = .Exe, .main_mod = &main_mod, .emit_bin = emit_bin, @@ -6269,10 +6265,6 @@ test "fds" { gimmeMoreOfThoseSweetSweetFileDescriptors(); } -fn detectNativeTargetInfo(target_query: std.Target.Query) !std.zig.system.NativeTargetInfo { - return std.zig.system.NativeTargetInfo.detect(target_query); -} - const usage_ast_check = \\Usage: zig ast-check [file] \\ @@ -6669,24 +6661,24 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 { fn warnAboutForeignBinaries( arena: Allocator, arg_mode: ArgMode, - target_info: *const std.zig.system.NativeTargetInfo, + target: *const std.Target, link_libc: bool, ) !void { const host_query: std.Target.Query = .{}; - const host_target_info = try detectNativeTargetInfo(host_query); + const host_target = try std.zig.system.resolveTargetQuery(host_query); - switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) { + switch (std.zig.system.getExternalExecutor(host_target, target, .{ .link_libc = link_libc })) { .native => return, .rosetta => { - const host_name = try host_target_info.target.zigTriple(arena); - const foreign_name = try target_info.target.zigTriple(arena); + const host_name = try host_target.zigTriple(arena); + const foreign_name = try target.zigTriple(arena); warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{ host_name, foreign_name, }); }, .qemu => |qemu| { - const host_name = try host_target_info.target.zigTriple(arena); - const foreign_name = try target_info.target.zigTriple(arena); + const host_name = try host_target.zigTriple(arena); + const foreign_name = try target.zigTriple(arena); switch (arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ @@ -6702,8 +6694,8 @@ fn warnAboutForeignBinaries( } }, .wine => |wine| { - const host_name = try host_target_info.target.zigTriple(arena); - const foreign_name = try target_info.target.zigTriple(arena); + const host_name = try host_target.zigTriple(arena); + const foreign_name = try target.zigTriple(arena); switch (arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ @@ -6719,8 +6711,8 @@ fn warnAboutForeignBinaries( } }, .wasmtime => |wasmtime| { - const host_name = try host_target_info.target.zigTriple(arena); - const foreign_name = try target_info.target.zigTriple(arena); + const host_name = try host_target.zigTriple(arena); + const foreign_name = try target.zigTriple(arena); switch (arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ @@ -6736,8 +6728,8 @@ fn warnAboutForeignBinaries( } }, .darling => |darling| { - const host_name = try host_target_info.target.zigTriple(arena); - const foreign_name = try target_info.target.zigTriple(arena); + const host_name = try host_target.zigTriple(arena); + const foreign_name = try target.zigTriple(arena); switch (arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ @@ -6753,7 +6745,7 @@ fn warnAboutForeignBinaries( } }, .bad_dl => |foreign_dl| { - const host_dl = host_target_info.dynamic_linker.get() orelse "(none)"; + const host_dl = host_target.dynamic_linker.get() orelse "(none)"; const tip_suffix = switch (arg_mode) { .zig_test => ", '--test-no-exec', or '--test-cmd'", else => "", @@ -6763,8 +6755,8 @@ fn warnAboutForeignBinaries( }); }, .bad_os_or_cpu => { - const host_name = try host_target_info.target.zigTriple(arena); - const foreign_name = try target_info.target.zigTriple(arena); + const host_name = try host_target.zigTriple(arena); + const foreign_name = try target.zigTriple(arena); const tip_suffix = switch (arg_mode) { .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'", else => "", diff --git a/src/print_env.zig b/src/print_env.zig index 89c6ffe754..46ca10d149 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -17,8 +17,8 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8, stdout: std.fs.File.Wr const global_cache_dir = try introspect.resolveGlobalCacheDir(arena); - const info = try std.zig.system.NativeTargetInfo.detect(.{}); - const triple = try info.target.zigTriple(arena); + const host = try std.zig.system.resolveTargetQuery(.{}); + const triple = try host.zigTriple(arena); var bw = std.io.bufferedWriter(stdout); const w = bw.writer(); diff --git a/test/src/Cases.zig b/test/src/Cases.zig index de7378d10a..0f2645f0e0 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -541,7 +541,7 @@ pub fn lowerToBuildSteps( cases_dir_path: []const u8, incremental_exe: *std.Build.Step.Compile, ) void { - const host = std.zig.system.NativeTargetInfo.detect(.{}) catch |err| + const host = std.zig.system.resolveTargetQuery(.{}) catch |err| std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)}); for (self.incremental_cases.items) |incr_case| { @@ -648,8 +648,7 @@ pub fn lowerToBuildSteps( }, .Execution => |expected_stdout| no_exec: { const run = if (case.target.target.ofmt == .c) run_step: { - const target_info = case.target.toNativeTargetInfo(); - if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) { + if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) { // We wouldn't be able to run the compiled C code. break :no_exec; } @@ -694,8 +693,7 @@ pub fn lowerToBuildSteps( continue; // Pass test. } - const target_info = case.target.toNativeTargetInfo(); - if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) { + if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) { // We wouldn't be able to run the compiled C code. continue; // Pass test. } @@ -1199,6 +1197,8 @@ const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const getExternalExecutor = std.zig.system.getExternalExecutor; + const Compilation = @import("../../src/Compilation.zig"); const zig_h = @import("../../src/link.zig").File.C.zig_h; const introspect = @import("../../src/introspect.zig"); @@ -1386,18 +1386,15 @@ pub fn main() !void { } fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget { - const result = std.zig.system.NativeTargetInfo.detect(query) catch - @panic("unable to resolve target query"); - return .{ .query = query, - .target = result.target, - .dynamic_linker = result.dynamic_linker, + .target = std.zig.system.resolveTargetQuery(query) catch + @panic("unable to resolve target query"), }; } fn runCases(self: *Cases, zig_exe_path: []const u8) !void { - const host = try std.zig.system.NativeTargetInfo.detect(.{}); + const host = try std.zig.system.resolveTargetQuery(.{}); var progress = std.Progress{}; const root_node = progress.start("compiler", self.cases.items.len); @@ -1478,7 +1475,7 @@ fn runOneCase( zig_exe_path: []const u8, thread_pool: *ThreadPool, global_cache_directory: Compilation.Directory, - host: std.zig.system.NativeTargetInfo, + host: std.Target, ) !void { const tmp_src_path = "tmp.zig"; const enable_rosetta = build_options.enable_rosetta; @@ -1488,8 +1485,7 @@ fn runOneCase( const enable_darling = build_options.enable_darling; const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; - const target_info = try std.zig.system.NativeTargetInfo.detect(case.target); - const target = target_info.target; + const target = try std.zig.system.resolveTargetQuery(case.target); var arena_allocator = std.heap.ArenaAllocator.init(allocator); defer arena_allocator.deinit(); @@ -1579,7 +1575,7 @@ fn runOneCase( .keep_source_files_loaded = true, .is_native_os = case.target.isNativeOs(), .is_native_abi = case.target.isNativeAbi(), - .dynamic_linker = target_info.dynamic_linker.get(), + .dynamic_linker = target.dynamic_linker.get(), .link_libc = case.link_libc, .use_llvm = use_llvm, .self_exe_path = zig_exe_path, @@ -1715,7 +1711,7 @@ fn runOneCase( .{ &tmp.sub_path, bin_name }, ); if (case.target.ofmt != null and case.target.ofmt.? == .c) { - if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) { + if (getExternalExecutor(host, &target, .{ .link_libc = true }) != .native) { // We wouldn't be able to run the compiled C code. continue :update; // Pass test. } @@ -1734,7 +1730,7 @@ fn runOneCase( if (zig_lib_directory.path) |p| { try argv.appendSlice(&.{ "-I", p }); } - } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) { + } else switch (getExternalExecutor(host, &target, .{ .link_libc = case.link_libc })) { .native => { if (case.backend == .stage2 and case.target.getCpuArch().isArmOrThumb()) { // https://github.com/ziglang/zig/issues/13623