From ab5ce5b5e1742f54ae2b9a78899f50523f947665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Sep 2026 06:24:24 +0200 Subject: [PATCH 1/5] std.Target: make DynamicLinker.standard() version range agnostic Dynamic linker paths do change sometimes, but I'm not aware of a single instance of this happening where a compatibility symlink wasn't left in place. And even if we ignore that, the only two instances that we handled here were FreeBSD 6.0 (~2005) and DragonFly BSD 3.8 (~2014). Removing this requirement helps solve the chicken-and-egg problem going on in std.zig.system.resolveTargetQuery(). --- lib/std/Target.zig | 20 +++++--------------- lib/std/zig/system.zig | 4 ++-- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 0de47c02ff514595b2075a2a9b8359b497d06488..11fac4f21af221387229ce583c0d8d71ca3e9d4f 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2532,8 +2532,8 @@ pub const DynamicLinker = struct { /// the ABI; it does not necessarily mean that `abi` makes any sense at all for that platform. /// The responsibility for determining whether `abi` is valid in this case rests with the /// caller. `Abi.default()` can be used to pick a best-effort default ABI for such platforms. - pub fn standard(cpu: Cpu, os: Os, abi: Abi) DynamicLinker { - return switch (os.tag) { + pub fn standard(cpu: Cpu, os: Os.Tag, abi: Abi) DynamicLinker { + return switch (os) { .fuchsia => switch (cpu.arch) { .arm, .aarch64, @@ -2833,12 +2833,7 @@ pub const DynamicLinker = struct { else => none, }, - .dragonfly => if (cpu.arch == .x86_64) initFmt("{s}/libexec/ld-elf.so.2", .{ - if (os.version_range.semver.isAtLeast(.{ .major = 3, .minor = 8, .patch = 0 }) orelse false) - "" - else - "/usr", - }) else none, + .dragonfly => if (cpu.arch == .x86_64) init("/libexec/ld-elf.so.2") else none, .freebsd => switch (cpu.arch) { .arm, @@ -2849,12 +2844,7 @@ pub const DynamicLinker = struct { .riscv64, .x86, .x86_64, - => initFmt("{s}/libexec/ld-elf.so.1", .{ - if (os.version_range.semver.isAtLeast(.{ .major = 6, .minor = 0, .patch = 0 }) orelse false) - "" - else - "/usr", - }), + => init("/libexec/ld-elf.so.1"), else => none, }, @@ -2968,7 +2958,7 @@ pub const DynamicLinker = struct { }; pub fn standardDynamicLinkerPath(target: *const Target) DynamicLinker { - return DynamicLinker.standard(target.cpu, target.os, target.abi); + return DynamicLinker.standard(target.cpu, target.os.tag, target.abi); } pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 5930a32533428a8c4718fd165de4ac4078ba4780..eab97ae8de5e2175c5acdeab1824da5b0eab79ae 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -995,7 +995,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ // will then look for a dynamic linker with a matching path on the system and pick the ABI // we associated it with here. .arch_os_abi => for (all_abis) |abi| { - const ld = Target.DynamicLinker.standard(cpu, os, abi); + const ld = Target.DynamicLinker.standard(cpu, os.tag, abi); // Does the generated target triple actually have a standard dynamic linker path? if (ld.get() == null) continue; @@ -1141,7 +1141,7 @@ fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Quer .os = os, .abi = abi, .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), - .dynamic_linker = query.dynamic_linker orelse .standard(cpu, os, abi), + .dynamic_linker = query.dynamic_linker orelse .standard(cpu, os.tag, abi), }; } -- 2.54.0 From 055064a37a24b0150b6c7ae426a8f2279819e7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Sep 2026 06:46:44 +0200 Subject: [PATCH 2/5] std.Target: riscv64-linux-android requires API level 35 --- lib/std/Target.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 11fac4f21af221387229ce583c0d8d71ca3e9d4f..fd490cc2abb094af885b54da4f82e3614e23a52b 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -499,7 +499,10 @@ pub const Os = struct { break :blk default_min; }, - .android = 29, + .android = switch (arch) { + .riscv64 => 35, + else => 29, + }, }, }, .rtems => .{ -- 2.54.0 From edad096b772d58bc39d227b0ba4d13b641ca99e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Sep 2026 07:43:43 +0200 Subject: [PATCH 3/5] std.zig.system: remove some pointless parameters being passed around --- lib/std/zig/system.zig | 10 +++++----- lib/std/zig/system/loongarch.zig | 9 +-------- lib/std/zig/system/x86.zig | 11 +++++------ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index eab97ae8de5e2175c5acdeab1824da5b0eab79ae..6ab26aab14edb82113a7b9cd8f165e5a23df3cfb 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -374,10 +374,10 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { } var cpu = switch (query.cpu_model) { - .native => detectNativeCpuAndFeatures(io, query_cpu_arch, os, query), + .native => detectNativeCpuAndFeatures(io, query_cpu_arch), .baseline => Target.Cpu.baseline(query_cpu_arch, os), .determined_by_arch_os => if (query.cpu_arch == null) - detectNativeCpuAndFeatures(io, query_cpu_arch, os, query) + detectNativeCpuAndFeatures(io, query_cpu_arch) else Target.Cpu.baseline(query_cpu_arch, os), .explicit => |model| model.toCpu(query_cpu_arch), @@ -535,13 +535,13 @@ fn updateCpuFeatures( set.removeFeatureSet(sub_set); } -fn detectNativeCpuAndFeatures(io: Io, cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu { +fn detectNativeCpuAndFeatures(io: Io, cpu_arch: Target.Cpu.Arch) ?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) { - .loongarch32, .loongarch64 => return @import("system/loongarch.zig").detectNativeCpuAndFeatures(cpu_arch, os, query), - .x86_64, .x86 => return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query), + .loongarch32, .loongarch64 => return @import("system/loongarch.zig").detectNativeCpuAndFeatures(cpu_arch), + .x86_64, .x86 => return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch), else => {}, } diff --git a/lib/std/zig/system/loongarch.zig b/lib/std/zig/system/loongarch.zig index da0d6fecb2807b7fa8b01afcb9ad74ab4f029a2c..04a628e4e6cb73f0b196935d0b1e1184adde7dd0 100644 --- a/lib/std/zig/system/loongarch.zig +++ b/lib/std/zig/system/loongarch.zig @@ -18,14 +18,7 @@ fn setFeature(cpu: *std.Target.Cpu, feature: std.Target.loongarch.Feature, enabl if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx); } -pub fn detectNativeCpuAndFeatures( - arch: std.Target.Cpu.Arch, - os: std.Target.Os, - query: std.Target.Query, -) ?std.Target.Cpu { - _ = os; - _ = query; - +pub fn detectNativeCpuAndFeatures(arch: std.Target.Cpu.Arch) ?std.Target.Cpu { const variant: Variant = @fromBackingInt(@intCast(cpucfg(1) & 0b11)); var cpu: std.Target.Cpu = .{ diff --git a/lib/std/zig/system/x86.zig b/lib/std/zig/system/x86.zig index 23687255a5be522c3c0f5bb7579df484e7e45277..0b535c7e810b149d4614c6035f835e2ac15aa710 100644 --- a/lib/std/zig/system/x86.zig +++ b/lib/std/zig/system/x86.zig @@ -41,8 +41,7 @@ inline fn hasMask(input: u32, mask: u32) bool { return (input & mask) == mask; } -pub fn detectNativeCpuAndFeatures(arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) Target.Cpu { - _ = query; +pub fn detectNativeCpuAndFeatures(arch: Target.Cpu.Arch) Target.Cpu { var cpu = Target.Cpu{ .arch = arch, .model = Target.Cpu.Model.generic(arch), @@ -50,7 +49,7 @@ pub fn detectNativeCpuAndFeatures(arch: Target.Cpu.Arch, os: Target.Os, query: T }; // First we detect features, to use as hints when detecting CPU Model. - detectNativeFeatures(&cpu, os.tag); + detectNativeFeatures(&cpu); var leaf = cpuid(0, 0); const max_leaf = leaf.eax; @@ -90,7 +89,7 @@ pub fn detectNativeCpuAndFeatures(arch: Target.Cpu.Arch, os: Target.Os, query: T // Add the CPU model's feature set into the working set, but then // override with actual detected features again. cpu.features.addFeatureSet(cpu.model.features); - detectNativeFeatures(&cpu, os.tag); + detectNativeFeatures(&cpu); cpu.features.populateDependencies(cpu.arch.allFeaturesList()); @@ -215,7 +214,7 @@ fn detectAmdProcessor(cpu: *const Target.Cpu, family: u32, model: u32) ?*const T }; } -fn detectNativeFeatures(cpu: *Target.Cpu, os_tag: Target.Os.Tag) void { +fn detectNativeFeatures(cpu: *Target.Cpu) void { var leaf = cpuid(0, 0); const max_level = leaf.eax; @@ -268,7 +267,7 @@ fn detectNativeFeatures(cpu: *Target.Cpu, os_tag: Target.Os.Tag) void { // Darwin lazily saves the AVX512 context on first use: trust that the OS will // save the AVX512 context if we use AVX512 instructions, even if the bit is not // set right now. - const has_avx512_save = if (os_tag.isDarwin()) + const has_avx512_save = if (builtin.target.os.tag.isDarwin()) true else xcr0.zmm_hi256 and xcr0.hi16_zmm; -- 2.54.0 From 4d1c5d698fffe9684b72bcd72d60dfd758eb0ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Sep 2026 07:47:23 +0200 Subject: [PATCH 4/5] std.zig.system: turn an assumption in detectNativeCpuAndFeatures() into an assert --- lib/std/zig/system.zig | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 6ab26aab14edb82113a7b9cd8f165e5a23df3cfb..5f9a5c68fe52aa4a9bed491c81adf4edfa12c149 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -375,15 +375,13 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { var cpu = switch (query.cpu_model) { .native => detectNativeCpuAndFeatures(io, query_cpu_arch), - .baseline => Target.Cpu.baseline(query_cpu_arch, os), + .baseline => null, .determined_by_arch_os => if (query.cpu_arch == null) detectNativeCpuAndFeatures(io, query_cpu_arch) else - Target.Cpu.baseline(query_cpu_arch, os), + null, .explicit => |model| model.toCpu(query_cpu_arch), - } orelse backup_cpu_detection: { - break :backup_cpu_detection Target.Cpu.baseline(query_cpu_arch, os); - }; + } orelse Target.Cpu.baseline(query_cpu_arch, os); // For x86, we need to populate some CPU feature flags depending on architecture // and mode: @@ -536,12 +534,11 @@ fn updateCpuFeatures( } fn detectNativeCpuAndFeatures(io: Io, cpu_arch: Target.Cpu.Arch) ?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) { - .loongarch32, .loongarch64 => return @import("system/loongarch.zig").detectNativeCpuAndFeatures(cpu_arch), - .x86_64, .x86 => return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch), + const family = builtin.target.cpu.arch.family(); + assert(cpu_arch.family() == family); + switch (family) { + .loongarch => return @import("system/loongarch.zig").detectNativeCpuAndFeatures(cpu_arch), + .x86 => return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch), else => {}, } -- 2.54.0 From b61678c1b631f78c3a5da4eca2e77ae3577c1093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Sep 2026 11:54:50 +0200 Subject: [PATCH 5/5] std.Target.Query: rework native ABI detection a bit Native detection of the libc version now only happens if the ABI component in the triple is native (= omitted in text form), rather than the OS component. So the ABI is either fully native or fully specified with no weird in-between. I think this much more closely matches people's mental model for this stuff; Matthew and I struggled to make sense of the current semantics for almost an hour, and ultimately agreed that it really shouldn't work the way it did prior to this patch. This is technically a breaking change because `native-native-gnu` no longer means "use glibc with the detected native glibc version", and this concept is no longer expressible. I don't consider this a great loss; I couldn't find any usage in the wild that would be broken by this, and I would argue it was more likely to be an unhelpful surprise. On the other hand, this patch makes `native-native-gnueabihf` on e.g. an `x86_64-linux-gnu` system use the default glibc version rather than the native one - which was a behavior I found bizarre. I think the ABI detection code is now also much easier to reason about. closes https://codeberg.org/ziglang/zig/issues/36863 --- lib/std/Target/Query.zig | 79 +++++++++---------- lib/std/zig/system.zig | 166 ++++++++++++++++++--------------------- 2 files changed, 113 insertions(+), 132 deletions(-) diff --git a/lib/std/Target/Query.zig b/lib/std/Target/Query.zig index c1a533020b2de773e1c04544ff61a46eb865d583..b889354c5e68d15c1ddc86d825877cb68b8d4632 100644 --- a/lib/std/Target/Query.zig +++ b/lib/std/Target/Query.zig @@ -26,33 +26,37 @@ cpu_features_sub: Target.Cpu.Feature.Set = .empty, /// `null` means native. os_tag: ?Target.Os.Tag = null, -/// `null` means the default version range for `os_tag`. If `os_tag` is `null` (native) -/// then `null` for this field means native. +/// `null` means native when `os_tag` is native; otherwise, the default minimum version for +/// `cpu_arch`, `os_tag`, and `abi`. os_version_min: ?OsVersion = null, -/// When cross compiling, `null` means default (latest known OS version). -/// When `os_tag` is native, `null` means equal to the native OS version. +/// `null` means native when `os_tag` is native; otherwise, the latest known OS version. os_version_max: ?OsVersion = null, -/// `null` means default when cross compiling, or native when `os_tag` is native. -/// If `isGnu()` is `false`, this must be `null` and is ignored. +/// `null` means native when `abi` is native; otherwise, the default minimum version for `cpu_arch`, +/// `os_tag`, and `abi`. +/// +/// Must be `null` when `abi` is `null` or `isGnu()` is `false`. glibc_version: ?SemanticVersion = null, -/// `null` means default when cross compiling, or native when `os_tag` is native. -/// If `isAndroid()` is `false`, this must be `null` and is ignored. +/// `null` means native when `abi` is native; otherwise, the default minimum version for `cpu_arch` +/// and `abi`. +/// +/// Must be `null` when `abi` is `null` or `isAndroid()` is `false`. android_api_level: ?u32 = null, -/// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI. +/// `null` means native if `os_tag` is native; otherwise, best-effort default for `cpu_arch` and +/// `os_tag`. abi: ?Target.Abi = null, -/// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path -/// based on the `os_tag`. When `dynamic_linker` is a non-`null` empty string, no dynamic -/// linker is used regardless of `os_tag`. -dynamic_linker: ?Target.DynamicLinker = null, - -/// `null` means default for the cpu/arch/os combo. +/// `null` means default for the `cpu_arch` and `os_tag` combination. ofmt: ?Target.ObjectFormat = null, +/// `null` means native when `abi` is native; otherwise, the standard path based on `cpu_arch`, +/// `os_tag`, and `abi`. A non-`null` empty string means no dynamic linker is used regardless of the +/// values of other fields. +dynamic_linker: ?Target.DynamicLinker = null, + pub const CpuModel = union(enum) { /// Always native native, @@ -175,12 +179,12 @@ pub const ParseOptions = struct { /// parsed CPU Architecture. If native, then this will be "native". Otherwise, it will be "baseline". cpu_features: ?[]const u8 = null, + object_format: ?[]const u8 = null, + /// Absolute path to dynamic linker, to override the default, which is either a natively /// detected path, or a standard path. dynamic_linker: ?[]const u8 = null, - object_format: ?[]const u8 = null, - /// If this is provided, the function will populate some information about parsing failures, /// so that user-friendly error messages can be delivered. diagnostics: ?*Diagnostics = null, @@ -381,12 +385,16 @@ pub fn isNativeCpu(self: Query) bool { } pub fn isNativeOs(self: Query) bool { - return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and - self.dynamic_linker == null and self.glibc_version == null and self.android_api_level == null; + return self.os_tag == null and + self.os_version_min == null and + self.os_version_max == null; } pub fn isNativeAbi(self: Query) bool { - return self.os_tag == null and self.abi == null; + return self.os_tag == null and + self.abi == null and + self.glibc_version == null and + self.android_api_level == null; } pub fn isNativeTriple(self: Query) bool { @@ -394,11 +402,13 @@ pub fn isNativeTriple(self: Query) bool { } pub fn isNative(self: Query) bool { - return self.isNativeTriple() and self.ofmt == null; + return self.isNativeTriple() and + self.ofmt == null and + self.dynamic_linker == null; } pub fn canDetectLibC(self: Query) bool { - if (self.isNativeOs()) return true; + if (self.isNativeOs() and self.isNativeAbi()) return true; if (self.os_tag) |os| { if (builtin.os.tag == .macos and os.isDarwin()) return true; if (os == .linux) { @@ -459,14 +469,14 @@ pub fn zigTriple(self: Query, gpa: Allocator) Allocator.Error![]u8 { } if (self.glibc_version) |v| { - const name = if (self.abi) |abi| @tagName(abi) else "gnu"; + const name = @tagName(self.abi.?); try result.ensureUnusedCapacity(gpa, name.len + 2); result.appendAssumeCapacity('-'); result.appendSliceAssumeCapacity(name); result.appendAssumeCapacity('.'); try formatVersion(v, gpa, &result); } else if (self.android_api_level) |lvl| { - const name = if (self.abi) |abi| @tagName(abi) else "android"; + const name = @tagName(self.abi.?); try result.ensureUnusedCapacity(gpa, name.len + 2); result.appendAssumeCapacity('-'); result.appendSliceAssumeCapacity(name); @@ -538,6 +548,7 @@ pub fn allocDescription(self: Query, allocator: Allocator) ![]u8 { return self.zigTriple(allocator); } +/// Deprecated; set `glibc_version` directly. Will be removed in 0.18.0. pub fn setGnuLibCVersion(self: *Query, major: u32, minor: u32, patch: u32) void { self.glibc_version = SemanticVersion{ .major = major, .minor = minor, .patch = patch }; } @@ -599,8 +610,8 @@ pub fn eql(a: Query, b: Query) bool { if (!versionEqualOpt(a.glibc_version, b.glibc_version)) return false; if (a.android_api_level != b.android_api_level) return false; if (a.abi != b.abi) return false; - if (!dynamicLinkerEqualOpt(a.dynamic_linker, b.dynamic_linker)) return false; if (a.ofmt != b.ofmt) return false; + if (!dynamicLinkerEqualOpt(a.dynamic_linker, b.dynamic_linker)) return false; return true; } @@ -620,24 +631,6 @@ fn dynamicLinkerEqualOpt(a: ?Target.DynamicLinker, b: ?Target.DynamicLinker) boo test parse { const io = std.testing.io; - if (builtin.target.isGnuLibC()) { - var query = try Query.parse(.{}); - query.setGnuLibCVersion(2, 1, 1); - - const text = try query.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - - try std.testing.expectEqualSlices(u8, "native-native-gnu.2.1.1", text); - } - if (builtin.target.abi.isAndroid()) { - var query = try Query.parse(.{}); - query.android_api_level = 30; - - const text = try query.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - - try std.testing.expectEqualSlices(u8, "native-native-android.30", text); - } { const query = try Query.parse(.{ .arch_os_abi = "aarch64-linux", diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 5f9a5c68fe52aa4a9bed491c81adf4edfa12c149..857ae1d2ff1c35feac45eb5b2f40cca4986d74f3 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -419,6 +419,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { var result = detectAbiAndDynamicLinker(io, cpu, os, query) catch |err| switch (err) { error.Canceled => |e| return e, error.Unexpected => |e| return e, + error.ApiLevelQueryFailed => |e| return e, error.WouldBlock => return error.Unexpected, error.ConnectionResetByPeer => return error.Unexpected, error.NotOpenForReading => return error.Unexpected, @@ -497,27 +498,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { } } - if (builtin.os.tag == .linux and result.isBionicLibC() and query.os_tag == null and query.android_api_level == null) { - result.os.version_range.linux.android = detectAndroidApiLevel(io) catch |err| return switch (err) { - error.InvalidWtf8, - error.InvalidBatchScriptArg, - => unreachable, // Windows-only - error.ApiLevelQueryFailed => |e| e, - else => blk: { - std.log.err("spawning or reading from getprop failed ({s})", .{@errorName(err)}); - switch (err) { - error.SystemResources, - error.FileSystem, - error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded, - error.SymLinkLoop, - => |e| break :blk e, - else => break :blk error.ApiLevelQueryFailed, - } - }, - }; - } - return result; } @@ -591,11 +571,13 @@ fn abiAndDynamicLinkerFromFile( ld_info_list: []const LdInfo, query: Target.Query, ) AbiAndDynamicLinkerFromFileError!Target { + assert(query.abi == null); // See the early exit in `detectAbiAndDynamicLinker`. + const io = file_reader.io; var result: Target = .{ .cpu = cpu, .os = os, - .abi = query.abi orelse Target.Abi.default(cpu.arch, os.tag), + .abi = Target.Abi.default(cpu.arch, os.tag), .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), .dynamic_linker = query.dynamic_linker orelse .none, }; @@ -609,26 +591,25 @@ fn abiAndDynamicLinkerFromFile( .INTERP => { got_dyn_section = true; - if (look_for_ld) { - const p_filesz = phdr.filesz; - if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; - const filesz: usize = @intCast(p_filesz); - try file_reader.seekTo(phdr.offset); - try file_reader.interface.readSliceAll(result.dynamic_linker.buffer[0..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.len = @intCast(len); + var interp: Target.DynamicLinker = .none; + const p_filesz = phdr.filesz; + if (p_filesz < 2) return error.InvalidElfFile; + if (p_filesz > interp.buffer.len) return error.NameTooLong; + const filesz: usize = @intCast(p_filesz); + try file_reader.seekTo(phdr.offset); + try file_reader.interface.readSliceAll(interp.buffer[0..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. + interp.len = @intCast(len); - // 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; - } + // Use it to determine ABI. + for (ld_info_list) |ld_info| { + if (std.mem.endsWith(u8, interp.get().?, fs.path.basename(ld_info.ld.get().?))) { + result.abi = ld_info.abi; + if (look_for_ld) result.dynamic_linker = interp; + break; } } }, @@ -650,6 +631,10 @@ fn abiAndDynamicLinkerFromFile( }; } + if (look_for_ld and result.dynamic_linker.get() == null) { + result.dynamic_linker = .standard(cpu, os.tag, result.abi); + } + if (!got_dyn_section) { return error.StaticElfFile; } @@ -951,17 +936,14 @@ fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion { /// file recursively. If that does not provide the answer, then the function falls back to /// defaults. fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target { - const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none; - const is_linux = builtin.target.os.tag == .linux; - const is_illumos = builtin.target.os.tag == .illumos; - const is_darwin = builtin.target.os.tag.isDarwin(); - const have_all_info = query.dynamic_linker != null and - query.abi != null and (!is_linux or query.abi.?.isGnu()); - const os_is_non_native = query.os_tag != null; - // The illumos environment is always the same. - if (!native_target_has_ld or have_all_info or os_is_non_native or is_illumos or is_darwin) { + const native_os_uses_elf = comptime Target.ObjectFormat.default(builtin.target.os.tag, builtin.target.cpu.arch) == .elf; + const native_ld_needs_abi = comptime Target.DynamicLinker.kind(builtin.target.os.tag) == .arch_os_abi; + if (!native_os_uses_elf or !native_ld_needs_abi or query.os_tag != null or query.abi != null) { return defaultAbiAndDynamicLinker(cpu, os, query); } + + assert(os.tag == builtin.target.os.tag); + // 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 @@ -979,30 +961,17 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; var ld_info_list_len: usize = 0; - switch (Target.DynamicLinker.kind(os.tag)) { - // The OS has no dynamic linker. Leave the list empty and rely on `Abi.default()` to pick - // something sensible in `abiAndDynamicLinkerFromFile()`. - .none => {}, - // The OS has a system-wide dynamic linker. Unfortunately, this implies that there's no - // useful ABI information that we can glean from it merely being present. That means the - // best we can do for this case (for now) is also `Abi.default()`. - .arch_os => {}, - // The OS can have different dynamic linker paths depending on libc/ABI. In this case, we - // need to gather all the valid arch/OS/ABI combinations. `abiAndDynamicLinkerFromFile()` - // will then look for a dynamic linker with a matching path on the system and pick the ABI - // we associated it with here. - .arch_os_abi => for (all_abis) |abi| { - const ld = Target.DynamicLinker.standard(cpu, os.tag, abi); + for (all_abis) |abi| { + const ld: Target.DynamicLinker = .standard(cpu, os.tag, abi); - // Does the generated target triple actually have a standard dynamic linker path? - if (ld.get() == null) continue; + // Does the generated target triple actually have a standard dynamic linker path? + if (ld.get() == null) continue; - ld_info_list_buffer[ld_info_list_len] = .{ - .ld = ld, - .abi = abi, - }; - ld_info_list_len += 1; - }, + 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]; @@ -1031,13 +1000,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ // it uses the file it references instead, doing the same logic // recursively in case it finds another shebang line. - var file_name: []const u8 = switch (os.tag) { - // Since /usr/bin/env is hard-coded into the shebang line of many - // portable scripts, it's a reasonably reliable path to start with. - else => "/usr/bin/env", - // Haiku does not have a /usr root directory. - .haiku => "/bin/env", - }; + // 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"; while (true) { const file = Io.Dir.openFileAbsolute(io, file_name, .{}) catch |err| switch (err) { @@ -1059,7 +1024,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ error.NetworkNotFound, error.FileTooBig, error.Unexpected, - => |e| if (e == error.FileNotFound and os.tag == .linux and mem.eql(u8, file_name, "/usr/bin/env")) { + => |e| if (builtin.target.os.tag == .linux and e == error.FileNotFound and mem.eql(u8, file_name, "/usr/bin/env")) { // Android does not have a /usr directory, so try again file_name = "/system/bin/env"; continue; @@ -1113,7 +1078,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ }; defer file_reader.file.close(io); - return abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) { + var result = abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) { error.FileSystem, error.SystemResources, error.SymLinkLoop, @@ -1129,6 +1094,29 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ return defaultAbiAndDynamicLinker(cpu, os, query); }, }; + + if (builtin.target.os.tag == .linux and result.isBionicLibC() and query.android_api_level == null) { + result.os.version_range.linux.android = detectAndroidApiLevel(io) catch |err| return switch (err) { + error.InvalidWtf8, + error.InvalidBatchScriptArg, + => unreachable, // Windows-only + error.ApiLevelQueryFailed => |e| e, + else => blk: { + std.log.err("spawning or reading from getprop failed ({s})", .{@errorName(err)}); + switch (err) { + error.SystemResources, + error.FileSystem, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.SymLinkLoop, + => |e| break :blk e, + else => break :blk error.ApiLevelQueryFailed, + } + }, + }; + } + + return result; } fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) Target { @@ -1166,13 +1154,6 @@ fn detectAndroidApiLevel(io: Io) !u32 { var stdout_buf: [92 + 1]u8 = undefined; var reader = child.stdout.?.readerStreaming(io, &.{}); const n = try reader.interface.readSliceShort(&stdout_buf); - const api_level = std.fmt.parseInt(u32, stdout_buf[0 .. n - 1], 10) catch |e| { - std.log.err( - "Could not parse API level, unexpected getprop output '{s}' ({s})", - .{ stdout_buf[0 .. n - 1], @errorName(e) }, - ); - return error.ApiLevelQueryFailed; - }; switch (try child.wait(io)) { .exited => |code| if (code != 0) { @@ -1193,7 +1174,14 @@ fn detectAndroidApiLevel(io: Io) !u32 { }, } - return api_level; + const result = mem.trimEnd(u8, stdout_buf[0..n], "\n"); + return std.fmt.parseInt(u32, result, 10) catch |e| { + std.log.err( + "Could not parse API level, unexpected getprop output '{s}' ({s})", + .{ result, @errorName(e) }, + ); + return error.ApiLevelQueryFailed; + }; } test { -- 2.54.0