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 {