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(io: Io, query: Target.Query) DetectError!Target
pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
// 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 query_cpu_arch = query.cpu_arch orelse builtin.cpu.arch;
const query_os_tag = query.os_tag orelse builtin.os.tag;
const query_abi = query.abi orelse builtin.abi;
var os = query_os_tag.defaultVersionRange(query_cpu_arch, query_abi);
if (query.os_tag == null) {
switch (builtin.target.os.tag) {
.linux, .illumos => {
const uts = posix.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| {
var stripped = ver;
stripped.pre = null;
stripped.build = null;
os.version_range.linux.range.min = stripped;
os.version_range.linux.range.max = stripped;
} 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(io, &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));
switch (posix.errno(posix.system.sysctlbyname(key, &value, &len, null, 0))) {
.SUCCESS => {},
.FAULT => unreachable,
.PERM => unreachable, // only when setting values,
.NOMEM => unreachable, // memory already on the stack
.NOENT => unreachable, // constant, known good value
else => 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{
posix.CTL.KERN,
posix.KERN.OSRELEASE,
};
var buf: [64:0]u8 = undefined;
// consider that sysctl result includes null-termination
var len: usize = buf.len + 1;
posix.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,
};
if (Target.Query.parseVersion(buf[0 .. len - 1 :0])) |ver| {
assert(ver.build == null);
assert(ver.pre == null);
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 (os.tag.versionRangeTag()) {
inline .hurd, .linux => |t| @field(os.version_range, @tagName(t)).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 (os.tag.versionRangeTag()) {
inline .hurd, .linux => |t| @field(os.version_range, @tagName(t)).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| {
switch (os.tag.versionRangeTag()) {
inline .hurd, .linux => |t| @field(os.version_range, @tagName(t)).glibc = glibc,
else => {},
}
}
if (query.android_api_level) |android| {
os.version_range.linux.android = android;
}
var cpu = switch (query.cpu_model) {
.native => detectNativeCpuAndFeatures(io, query_cpu_arch, os, query),
.baseline => Target.Cpu.baseline(query_cpu_arch, os),
.determined_by_arch_os => if (query.cpu_arch == null)
detectNativeCpuAndFeatures(io, query_cpu_arch, os, query)
else
Target.Cpu.baseline(query_cpu_arch, os),
.explicit => |model| model.toCpu(query_cpu_arch),
} orelse backup_cpu_detection: {
break :backup_cpu_detection Target.Cpu.baseline(query_cpu_arch, os);
};
// For x86, we need to populate some CPU feature flags depending on architecture
// and mode:
// * 16bit_mode => if the arch is x86_16
// * 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 (query_cpu_arch) {
.x86_16 => {
cpu.features.addFeature(@backingInt(Target.x86.Feature.@"16bit_mode"));
},
.x86 => {
if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{
.@"16bit_mode", .@"32bit_mode",
})) {
cpu.features.addFeature(@backingInt(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 => {
cpu.features.addFeature(@backingInt(Target.arm.Feature.thumb_mode));
},
else => {},
}
updateCpuFeatures(
&cpu.features,
cpu.arch.allFeaturesList(),
query.cpu_features_add,
query.cpu_features_sub,
);
var result = detectAbiAndDynamicLinker(io, cpu, os, query) catch |err| switch (err) {
error.Canceled => |e| return e,
error.Unexpected => |e| return e,
error.WouldBlock => return error.Unexpected,
error.ConnectionResetByPeer => return error.Unexpected,
error.NotOpenForReading => return error.Unexpected,
error.SocketUnconnected => return error.Unexpected,
error.ReadOnlyFileSystem => return error.Unexpected,
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
error.SystemResources,
error.IsDir,
error.DeviceBusy,
error.InputOutput,
error.LockViolation,
error.FileSystem,
error.UnableToOpenElfFile,
error.UnhelpfulFile,
error.InvalidElfFile,
error.RelativeShebang,
=> return defaultAbiAndDynamicLinker(cpu, os, query),
};
// These CPU feature hacks have to come after ABI detection.
{
if (result.cpu.arch == .hexagon) {
// Both LLVM and LLD have broken support for the small data area. Yet LLVM has the
// feature on by default for all Hexagon CPUs. Clang sort of solves this by defaulting
// the `-gpsize` command line parameter for the Hexagon backend to 0, so that no
// constants get placed in the SDA. (This of course breaks down if the user passes
// `-G <n>` to Clang...) We can't do the `-gpsize` hack because we can have multiple
// concurrent LLVM emit jobs, and command line options in LLVM are shared globally. So
// just force this feature off. Lovely stuff.
result.cpu.features.removeFeature(@backingInt(Target.hexagon.Feature.small_data));
}
// https://github.com/llvm/llvm-project/issues/105978
if (result.cpu.arch.isArm() and result.abi.float() == .soft) {
result.cpu.features.removeFeature(@backingInt(Target.arm.Feature.vfp2));
}
if (result.cpu.arch.isXtensa() and result.abi == .call0) {
result.cpu.features.removeFeature(@backingInt(Target.xtensa.Feature.windowed));
}
}
// It's possible that we detect the native ABI, but fail to detect the OS version or were told
// to use the default OS version range. In that case, while we can't determine the exact native
// OS version, we do at least know that some ABIs require a particular OS version (by way of
// `std.zig.target.available_libcs`). So in this case, adjust the OS version to the minimum that
// we know is required.
if (result.abi != query_abi and query.os_version_min == null) {
const result_ver_range = &result.os.version_range;
const abi_ver_range = result.os.tag.defaultVersionRange(result.cpu.arch, result.abi).version_range;
switch (result.os.tag.versionRangeTag()) {
.none => {},
.semver => if (result_ver_range.semver.min.order(abi_ver_range.semver.min) == .lt) {
result_ver_range.semver.min = abi_ver_range.semver.min;
},
inline .hurd, .linux => |t| {
if (@field(result_ver_range, @tagName(t)).range.min.order(@field(abi_ver_range, @tagName(t)).range.min) == .lt) {
@field(result_ver_range, @tagName(t)).range.min = @field(abi_ver_range, @tagName(t)).range.min;
}
if (@field(result_ver_range, @tagName(t)).glibc.order(@field(abi_ver_range, @tagName(t)).glibc) == .lt and
query.glibc_version == null)
{
@field(result_ver_range, @tagName(t)).glibc = @field(abi_ver_range, @tagName(t)).glibc;
}
},
.windows => if (!result_ver_range.windows.min.isAtLeast(abi_ver_range.windows.min)) {
result_ver_range.windows.min = abi_ver_range.windows.min;
},
}
}
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;
}