mirror of
https://github.com/ziglang/zig.git
synced 2026-02-18 15:31:48 +00:00
improve std.zig.system.NativeTargetInfo.detect
It now takes a std.zig.CrossTarget parameter, and only resolves native things, leaving explicitly overridden things alone.
This commit is contained in:
parent
578dc16910
commit
8691d3c50d
@ -7,19 +7,17 @@ const mem = std.mem;
|
||||
/// The purpose of this abstraction is to provide meaningful and unsurprising defaults.
|
||||
/// This struct does reference any resources and it is copyable.
|
||||
pub const CrossTarget = struct {
|
||||
/// `null` means native.
|
||||
/// `null` means native. If this is `null` then `cpu_model` must be `null`.
|
||||
cpu_arch: ?Target.Cpu.Arch = null,
|
||||
|
||||
/// If `cpu_arch` is native, `null` means native. Otherwise it means baseline.
|
||||
/// `null` means native.
|
||||
/// If this is non-null, `cpu_arch` must be specified.
|
||||
cpu_model: ?*const Target.Cpu.Model = null,
|
||||
|
||||
/// Sparse set of CPU features to add to the set from `cpu_model`.
|
||||
/// If this is non-empty, `cpu_arch` must be specified.
|
||||
cpu_features_add: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty,
|
||||
|
||||
/// Sparse set of CPU features to remove from the set from `cpu_model`.
|
||||
/// If this is non-empty, `cpu_arch` must be specified.
|
||||
cpu_features_sub: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty,
|
||||
|
||||
/// `null` means native.
|
||||
@ -33,13 +31,13 @@ pub const CrossTarget = struct {
|
||||
/// When `os_tag` is native, `null` means equal to the native OS version.
|
||||
os_version_max: ?OsVersion = null,
|
||||
|
||||
/// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI.
|
||||
abi: ?Target.Abi = null,
|
||||
|
||||
/// `null` means default when cross compiling, or native when os_tag is native.
|
||||
/// If `isGnuLibC()` is `false`, this must be `null` and is ignored.
|
||||
glibc_version: ?SemVer = null,
|
||||
|
||||
/// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI.
|
||||
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{},
|
||||
@ -146,6 +144,7 @@ pub const CrossTarget = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
pub fn toTarget(self: CrossTarget) Target {
|
||||
return .{
|
||||
.cpu = self.getCpu(),
|
||||
@ -307,6 +306,7 @@ pub const CrossTarget = struct {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
pub fn getCpu(self: CrossTarget) Target.Cpu {
|
||||
if (self.cpu_arch) |arch| {
|
||||
if (self.cpu_model) |model| {
|
||||
@ -342,6 +342,7 @@ pub const CrossTarget = struct {
|
||||
return self.getCpu().features;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
pub fn getOs(self: CrossTarget) Target.Os {
|
||||
// `Target.current.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
|
||||
@ -378,6 +379,7 @@ pub const CrossTarget = struct {
|
||||
return self.os_tag orelse Target.current.os.tag;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
pub fn getOsVersionMin(self: CrossTarget) OsVersion {
|
||||
if (self.os_version_min) |version_min| return version_min;
|
||||
var tmp: CrossTarget = undefined;
|
||||
@ -385,6 +387,7 @@ pub const CrossTarget = struct {
|
||||
return tmp.os_version_min.?;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
pub fn getOsVersionMax(self: CrossTarget) OsVersion {
|
||||
if (self.os_version_max) |version_max| return version_max;
|
||||
var tmp: CrossTarget = undefined;
|
||||
@ -392,6 +395,7 @@ pub const CrossTarget = struct {
|
||||
return tmp.os_version_max.?;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
pub fn getAbi(self: CrossTarget) Target.Abi {
|
||||
if (self.abi) |abi| return abi;
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ const ArrayList = std.ArrayList;
|
||||
const assert = std.debug.assert;
|
||||
const process = std.process;
|
||||
const Target = std.Target;
|
||||
const CrossTarget = std.zig.CrossTarget;
|
||||
|
||||
const is_windows = Target.current.os.tag == .windows;
|
||||
|
||||
@ -182,37 +183,87 @@ pub const NativeTargetInfo = struct {
|
||||
DeviceBusy,
|
||||
};
|
||||
|
||||
/// Detects the native CPU model & features, operating system & version, and C ABI & dynamic linker.
|
||||
/// On Linux, this is additionally responsible for detecting the native glibc version when applicable.
|
||||
/// Given a `CrossTarget`, 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.
|
||||
/// Any resources this function allocates are released before returning, and so there is no
|
||||
/// deinitialization method.
|
||||
/// TODO Remove the Allocator requirement from this function.
|
||||
pub fn detect(allocator: *Allocator) DetectError!NativeTargetInfo {
|
||||
const arch = Target.current.cpu.arch;
|
||||
const os_tag = Target.current.os.tag;
|
||||
|
||||
// TODO Detect native CPU model & features. Until that is implemented we hard code baseline.
|
||||
const cpu = Target.Cpu.baseline(arch);
|
||||
|
||||
// TODO Detect native operating system version. Until that is implemented we use the default range.
|
||||
var os = Target.Os.defaultVersionRange(os_tag);
|
||||
switch (Target.current.os.tag) {
|
||||
.linux => {
|
||||
const uts = std.os.uname();
|
||||
const release = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.release));
|
||||
if (std.builtin.Version.parse(release)) |ver| {
|
||||
os.version_range.linux.range.min = ver;
|
||||
os.version_range.linux.range.max = ver;
|
||||
} else |err| switch (err) {
|
||||
error.Overflow => {},
|
||||
error.InvalidCharacter => {},
|
||||
error.InvalidVersion => {},
|
||||
pub fn detect(allocator: *Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
|
||||
const cpu = blk: {
|
||||
if (cross_target.cpu_arch) |arch| {
|
||||
if (cross_target.cpu_model) |model| {
|
||||
var adjusted_model = model.toCpu(arch);
|
||||
cross_target.updateCpuFeatures(&adjusted_model.features);
|
||||
break :blk adjusted_model;
|
||||
} else {
|
||||
// TODO Detect native CPU model. Until that is implemented we use baseline.
|
||||
var adjusted_baseline = Target.Cpu.baseline(arch);
|
||||
cross_target.updateCpuFeatures(&adjusted_baseline.features);
|
||||
break :blk adjusted_baseline;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
} else {
|
||||
assert(cross_target.cpu_model == null);
|
||||
// TODO Detect native CPU model & features. Until that is implemented we use baseline.
|
||||
var adjusted_baseline = Target.Cpu.baseline(Target.current.cpu.arch);
|
||||
cross_target.updateCpuFeatures(&adjusted_baseline.features);
|
||||
break :blk adjusted_baseline;
|
||||
}
|
||||
};
|
||||
|
||||
var os = Target.Os.defaultVersionRange(cross_target.getOsTag());
|
||||
if (cross_target.os_tag == null) {
|
||||
switch (Target.current.os.tag) {
|
||||
.linux => {
|
||||
const uts = std.os.uname();
|
||||
const release = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.release));
|
||||
if (std.builtin.Version.parse(release)) |ver| {
|
||||
os.version_range.linux.range.min = ver;
|
||||
os.version_range.linux.range.max = ver;
|
||||
} else |err| switch (err) {
|
||||
error.Overflow => {},
|
||||
error.InvalidCharacter => {},
|
||||
error.InvalidVersion => {},
|
||||
}
|
||||
},
|
||||
.windows => {
|
||||
// TODO Detect native operating system version.
|
||||
},
|
||||
.macosx => {
|
||||
// TODO Detect native operating system version.
|
||||
},
|
||||
.freebsd => {
|
||||
// TODO Detect native operating system version.
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
return detectAbiAndDynamicLinker(allocator, cpu, os);
|
||||
if (cross_target.os_version_min) |min| switch (min) {
|
||||
.none => {},
|
||||
.semver => |semver| switch (cross_target.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 (cross_target.os_version_max) |max| switch (max) {
|
||||
.none => {},
|
||||
.semver => |semver| switch (cross_target.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 (cross_target.glibc_version) |glibc| {
|
||||
assert(cross_target.isGnuLibC());
|
||||
os.version_range.linux.glibc = glibc;
|
||||
}
|
||||
|
||||
return detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
|
||||
}
|
||||
|
||||
/// First we attempt to use the executable's own binary. If it is dynamically
|
||||
@ -224,9 +275,14 @@ pub const NativeTargetInfo = struct {
|
||||
allocator: *Allocator,
|
||||
cpu: Target.Cpu,
|
||||
os: Target.Os,
|
||||
cross_target: CrossTarget,
|
||||
) DetectError!NativeTargetInfo {
|
||||
if (!comptime Target.current.hasDynamicLinker()) {
|
||||
return defaultAbiAndDynamicLinker(cpu, os);
|
||||
const native_target_has_ld = comptime Target.current.hasDynamicLinker();
|
||||
const is_linux = Target.current.os.tag == .linux;
|
||||
const have_all_info = cross_target.dynamic_linker.get() != null and
|
||||
cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
|
||||
if (!native_target_has_ld or have_all_info) {
|
||||
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
|
||||
}
|
||||
// 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.
|
||||
@ -264,6 +320,13 @@ pub const NativeTargetInfo = struct {
|
||||
}
|
||||
const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
|
||||
|
||||
if (cross_target.dynamic_linker.get()) |explicit_ld| {
|
||||
const explicit_ld_basename = fs.path.basename(explicit_ld);
|
||||
for (ld_info_list) |ld_info| {
|
||||
const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
|
||||
}
|
||||
}
|
||||
|
||||
// Best case scenario: the executable is dynamically linked, and we can iterate
|
||||
// over our own shared objects and find a dynamic linker.
|
||||
self_exe: {
|
||||
@ -288,7 +351,9 @@ pub const NativeTargetInfo = struct {
|
||||
|
||||
// Look for glibc version.
|
||||
var os_adjusted = os;
|
||||
if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu()) {
|
||||
if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu() and
|
||||
cross_target.glibc_version == null)
|
||||
{
|
||||
for (lib_paths) |lib_path| {
|
||||
if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
|
||||
os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
|
||||
@ -306,9 +371,12 @@ pub const NativeTargetInfo = struct {
|
||||
.target = .{
|
||||
.cpu = cpu,
|
||||
.os = os_adjusted,
|
||||
.abi = found_ld_info.abi,
|
||||
.abi = cross_target.abi orelse found_ld_info.abi,
|
||||
},
|
||||
.dynamic_linker = DynamicLinker.init(found_ld_path),
|
||||
.dynamic_linker = if (cross_target.dynamic_linker.get() == null)
|
||||
DynamicLinker.init(found_ld_path)
|
||||
else
|
||||
cross_target.dynamic_linker,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
@ -317,7 +385,7 @@ pub const NativeTargetInfo = struct {
|
||||
// trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
|
||||
// Since that path is hard-coded into the shebang line of many portable scripts, it's a
|
||||
// reasonably reliable path to check for.
|
||||
return abiAndDynamicLinkerFromUsrBinEnv(cpu, os, ld_info_list) catch |err| switch (err) {
|
||||
return abiAndDynamicLinkerFromUsrBinEnv(cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
|
||||
error.FileSystem,
|
||||
error.SystemResources,
|
||||
error.SymLinkLoop,
|
||||
@ -337,7 +405,7 @@ pub const NativeTargetInfo = struct {
|
||||
error.UnexpectedEndOfFile,
|
||||
error.NameTooLong,
|
||||
// Finally, we fall back on the standard path.
|
||||
=> defaultAbiAndDynamicLinker(cpu, os),
|
||||
=> defaultAbiAndDynamicLinker(cpu, os, cross_target),
|
||||
};
|
||||
}
|
||||
|
||||
@ -379,6 +447,7 @@ pub const NativeTargetInfo = struct {
|
||||
cpu: Target.Cpu,
|
||||
os: Target.Os,
|
||||
ld_info_list: []const LdInfo,
|
||||
cross_target: CrossTarget,
|
||||
) !NativeTargetInfo {
|
||||
const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) {
|
||||
error.NoSpaceLeft => unreachable,
|
||||
@ -424,10 +493,12 @@ pub const NativeTargetInfo = struct {
|
||||
.target = .{
|
||||
.cpu = cpu,
|
||||
.os = os,
|
||||
.abi = Target.Abi.default(cpu.arch, os),
|
||||
.abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
|
||||
},
|
||||
.dynamic_linker = cross_target.dynamic_linker,
|
||||
};
|
||||
var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
|
||||
const look_for_ld = cross_target.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;
|
||||
@ -448,7 +519,7 @@ pub const NativeTargetInfo = struct {
|
||||
const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &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 => {
|
||||
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;
|
||||
@ -470,7 +541,9 @@ pub const NativeTargetInfo = struct {
|
||||
}
|
||||
},
|
||||
// We only need this for detecting glibc version.
|
||||
elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC()) {
|
||||
elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC() and
|
||||
cross_target.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: u64 = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
|
||||
@ -515,7 +588,7 @@ pub const NativeTargetInfo = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (Target.current.os.tag == .linux and result.target.isGnuLibC()) {
|
||||
if (Target.current.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) {
|
||||
if (rpath_offset) |rpoff| {
|
||||
const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
|
||||
|
||||
@ -657,15 +730,18 @@ pub const NativeTargetInfo = struct {
|
||||
return i;
|
||||
}
|
||||
|
||||
fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo {
|
||||
fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
|
||||
const target: Target = .{
|
||||
.cpu = cpu,
|
||||
.os = os,
|
||||
.abi = Target.Abi.default(cpu.arch, os),
|
||||
.abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
|
||||
};
|
||||
return NativeTargetInfo{
|
||||
.target = target,
|
||||
.dynamic_linker = target.standardDynamicLinkerPath(),
|
||||
.dynamic_linker = if (cross_target.dynamic_linker.get() == null)
|
||||
target.standardDynamicLinkerPath()
|
||||
else
|
||||
cross_target.dynamic_linker,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1152,44 +1152,24 @@ fn enumInt(comptime Enum: type, int: c_int) Enum {
|
||||
return @intToEnum(Enum, @intCast(@TagType(Enum), int));
|
||||
}
|
||||
|
||||
/// TODO move dynamic linker to be part of the target
|
||||
/// TODO self-host this function
|
||||
fn crossTargetToTarget(cross_target: CrossTarget, dynamic_linker_ptr: *?[*:0]u8) !Target {
|
||||
var adjusted_target = cross_target.toTarget();
|
||||
var have_native_dl = false;
|
||||
if (cross_target.cpu_arch == null or cross_target.os_tag == null) {
|
||||
const detected_info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator);
|
||||
if (cross_target.cpu_arch == null) {
|
||||
adjusted_target.cpu = detected_info.target.cpu;
|
||||
|
||||
// TODO We want to just use detected_info.target but implementing
|
||||
// CPU model & feature detection is todo so here we rely on LLVM.
|
||||
// There is another occurrence of this; search for detectNativeCpuWithLLVM.
|
||||
const llvm = @import("llvm.zig");
|
||||
const llvm_cpu_name = llvm.GetHostCPUName();
|
||||
const llvm_cpu_features = llvm.GetNativeFeatures();
|
||||
const arch = std.Target.current.cpu.arch;
|
||||
adjusted_target.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features);
|
||||
}
|
||||
if (cross_target.os_tag == null) {
|
||||
adjusted_target.os = detected_info.target.os;
|
||||
|
||||
if (detected_info.dynamic_linker.get()) |dl| {
|
||||
have_native_dl = true;
|
||||
dynamic_linker_ptr.* = try mem.dupeZ(std.heap.c_allocator, u8, dl);
|
||||
}
|
||||
if (cross_target.abi == null) {
|
||||
adjusted_target.abi = detected_info.target.abi;
|
||||
}
|
||||
} else if (cross_target.abi == null) {
|
||||
adjusted_target.abi = Target.Abi.default(adjusted_target.cpu.arch, adjusted_target.os);
|
||||
}
|
||||
var info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator, cross_target);
|
||||
if (cross_target.cpu_arch == null or cross_target.cpu_model == null) {
|
||||
// TODO We want to just use detected_info.target but implementing
|
||||
// CPU model & feature detection is todo so here we rely on LLVM.
|
||||
const llvm = @import("llvm.zig");
|
||||
const llvm_cpu_name = llvm.GetHostCPUName();
|
||||
const llvm_cpu_features = llvm.GetNativeFeatures();
|
||||
const arch = std.Target.current.cpu.arch;
|
||||
info.target.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features);
|
||||
cross_target.updateCpuFeatures(&info.target.cpu.features);
|
||||
}
|
||||
if (!have_native_dl) {
|
||||
const dl = adjusted_target.standardDynamicLinkerPath();
|
||||
dynamic_linker_ptr.* = if (dl.get()) |s| try mem.dupeZ(std.heap.c_allocator, u8, s) else null;
|
||||
if (info.dynamic_linker.get()) |dl| {
|
||||
dynamic_linker_ptr.* = try mem.dupeZ(std.heap.c_allocator, u8, dl);
|
||||
} else {
|
||||
dynamic_linker_ptr.* = null;
|
||||
}
|
||||
return adjusted_target;
|
||||
return info.target;
|
||||
}
|
||||
|
||||
// ABI warning
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user