From 77c5208c77f8e91786192dfc4e5945d40d67312a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 9 Nov 2021 10:31:51 +0100 Subject: [PATCH 1/5] Treat x86_64 tests as native under the Rosetta 2 on M1 Macs --- lib/std/zig/CrossTarget.zig | 6 + lib/std/zig/cross_target.zig | 899 +++++++++++++++++++++++++++++++++++ 2 files changed, 905 insertions(+) create mode 100644 lib/std/zig/cross_target.zig diff --git a/lib/std/zig/CrossTarget.zig b/lib/std/zig/CrossTarget.zig index 03bb6bc5ff..6024c893fb 100644 --- a/lib/std/zig/CrossTarget.zig +++ b/lib/std/zig/CrossTarget.zig @@ -612,6 +612,7 @@ pub fn vcpkgTriplet(self: CrossTarget, allocator: mem.Allocator, linkage: VcpkgL pub const Executor = union(enum) { native, + rosetta, qemu: []const u8, wine: []const u8, wasmtime: []const u8, @@ -642,6 +643,11 @@ pub fn getExternalExecutor(self: CrossTarget) Executor { return .native; } } + // If the OS match and OS is macOS and CPU is arm64, treat always as native + // since we'll be running the foreign architecture tests using Rosetta2. + if (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) { + return .native; + } // If the OS matches, we can use QEMU to emulate a foreign architecture. if (os_match) { diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig new file mode 100644 index 0000000000..8e3725c623 --- /dev/null +++ b/lib/std/zig/cross_target.zig @@ -0,0 +1,899 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Target = std.Target; +const mem = std.mem; + +/// Contains all the same data as `Target`, additionally introducing the concept of "the native target". +/// 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. + cpu_arch: ?Target.Cpu.Arch = null, + + cpu_model: CpuModel = CpuModel.determined_by_cpu_arch, + + /// Sparse set of CPU features to add to the set from `cpu_model`. + 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`. + cpu_features_sub: Target.Cpu.Feature.Set = 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. + 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. + os_version_max: ?OsVersion = 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{}, + + pub const CpuModel = union(enum) { + /// Always native + native, + + /// Always baseline + baseline, + + /// If CPU Architecture is native, then the CPU model will be native. Otherwise, + /// it will be baseline. + determined_by_cpu_arch, + + explicit: *const Target.Cpu.Model, + }; + + pub const OsVersion = union(enum) { + none: void, + semver: SemVer, + windows: Target.Os.WindowsVersion, + }; + + pub const SemVer = std.builtin.Version; + + pub const DynamicLinker = Target.DynamicLinker; + + pub fn fromTarget(target: Target) CrossTarget { + var result: CrossTarget = .{ + .cpu_arch = target.cpu.arch, + .cpu_model = .{ .explicit = target.cpu.model }, + .os_tag = target.os.tag, + .os_version_min = undefined, + .os_version_max = undefined, + .abi = target.abi, + .glibc_version = if (target.isGnuLibC()) + target.os.version_range.linux.glibc + else + null, + }; + result.updateOsVersionRange(target.os); + + const all_features = target.cpu.arch.allFeaturesList(); + var cpu_model_set = target.cpu.model.features; + cpu_model_set.populateDependencies(all_features); + { + // The "add" set is the full set with the CPU Model set removed. + const add_set = &result.cpu_features_add; + add_set.* = target.cpu.features; + add_set.removeFeatureSet(cpu_model_set); + } + { + // The "sub" set is the features that are on in CPU Model set and off in the full set. + const sub_set = &result.cpu_features_sub; + sub_set.* = cpu_model_set; + sub_set.removeFeatureSet(target.cpu.features); + } + return result; + } + + fn updateOsVersionRange(self: *CrossTarget, os: Target.Os) void { + switch (os.tag) { + .freestanding, + .ananas, + .cloudabi, + .fuchsia, + .kfreebsd, + .lv2, + .solaris, + .zos, + .haiku, + .minix, + .rtems, + .nacl, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .opencl, + .glsl450, + .vulkan, + .plan9, + .other, + => { + self.os_version_min = .{ .none = {} }; + self.os_version_max = .{ .none = {} }; + }, + + .freebsd, + .macos, + .ios, + .tvos, + .watchos, + .netbsd, + .openbsd, + .dragonfly, + => { + self.os_version_min = .{ .semver = os.version_range.semver.min }; + self.os_version_max = .{ .semver = os.version_range.semver.max }; + }, + + .linux => { + self.os_version_min = .{ .semver = os.version_range.linux.range.min }; + self.os_version_max = .{ .semver = os.version_range.linux.range.max }; + }, + + .windows => { + self.os_version_min = .{ .windows = os.version_range.windows.min }; + self.os_version_max = .{ .windows = os.version_range.windows.max }; + }, + } + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn toTarget(self: CrossTarget) Target { + return .{ + .cpu = self.getCpu(), + .os = self.getOs(), + .abi = self.getAbi(), + }; + } + + pub const ParseOptions = struct { + /// This is sometimes called a "triple". It looks roughly like this: + /// riscv64-linux-musl + /// The fields are, respectively: + /// * CPU Architecture + /// * Operating System (and optional version range) + /// * C ABI (optional, with optional glibc version) + /// The string "native" can be used for CPU architecture as well as Operating System. + /// If the CPU Architecture is specified as "native", then the Operating System and C ABI may be omitted. + arch_os_abi: []const u8 = "native", + + /// Looks like "name+a+b-c-d+e", where "name" is a CPU Model name, "a", "b", and "e" + /// are examples of CPU features to add to the set, and "c" and "d" are examples of CPU features + /// to remove from the set. + /// The following special strings are recognized for CPU Model name: + /// * "baseline" - The "default" set of CPU features for cross-compiling. A conservative set + /// of features that is expected to be supported on most available hardware. + /// * "native" - The native CPU model is to be detected when compiling. + /// If this field is not provided (`null`), then the value will depend on the + /// parsed CPU Architecture. If native, then this will be "native". Otherwise, it will be "baseline". + cpu_features: ?[]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, + + /// 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, + + pub const Diagnostics = struct { + /// If the architecture was determined, this will be populated. + arch: ?Target.Cpu.Arch = null, + + /// If the OS name was determined, this will be populated. + os_name: ?[]const u8 = null, + + /// If the OS tag was determined, this will be populated. + os_tag: ?Target.Os.Tag = null, + + /// If the ABI was determined, this will be populated. + abi: ?Target.Abi = null, + + /// If the CPU name was determined, this will be populated. + cpu_name: ?[]const u8 = null, + + /// If error.UnknownCpuFeature is returned, this will be populated. + unknown_feature_name: ?[]const u8 = null, + }; + }; + + pub fn parse(args: ParseOptions) !CrossTarget { + var dummy_diags: ParseOptions.Diagnostics = undefined; + const diags = args.diagnostics orelse &dummy_diags; + + var result: CrossTarget = .{ + .dynamic_linker = DynamicLinker.init(args.dynamic_linker), + }; + + var it = mem.split(u8, args.arch_os_abi, "-"); + const arch_name = it.next().?; + const arch_is_native = mem.eql(u8, arch_name, "native"); + if (!arch_is_native) { + result.cpu_arch = std.meta.stringToEnum(Target.Cpu.Arch, arch_name) orelse + return error.UnknownArchitecture; + } + const arch = result.getCpuArch(); + diags.arch = arch; + + if (it.next()) |os_text| { + try parseOs(&result, diags, os_text); + } else if (!arch_is_native) { + return error.MissingOperatingSystem; + } + + const opt_abi_text = it.next(); + if (opt_abi_text) |abi_text| { + var abi_it = mem.split(u8, abi_text, "."); + const abi = std.meta.stringToEnum(Target.Abi, abi_it.next().?) orelse + return error.UnknownApplicationBinaryInterface; + result.abi = abi; + diags.abi = abi; + + const abi_ver_text = abi_it.rest(); + if (abi_it.next() != null) { + if (result.isGnuLibC()) { + result.glibc_version = SemVer.parse(abi_ver_text) catch |err| switch (err) { + error.Overflow => return error.InvalidAbiVersion, + error.InvalidCharacter => return error.InvalidAbiVersion, + error.InvalidVersion => return error.InvalidAbiVersion, + }; + } else { + return error.InvalidAbiVersion; + } + } + } + + if (it.next() != null) return error.UnexpectedExtraField; + + if (args.cpu_features) |cpu_features| { + const all_features = arch.allFeaturesList(); + var index: usize = 0; + while (index < cpu_features.len and + cpu_features[index] != '+' and + cpu_features[index] != '-') + { + index += 1; + } + const cpu_name = cpu_features[0..index]; + diags.cpu_name = cpu_name; + + const add_set = &result.cpu_features_add; + const sub_set = &result.cpu_features_sub; + if (mem.eql(u8, cpu_name, "native")) { + result.cpu_model = .native; + } else if (mem.eql(u8, cpu_name, "baseline")) { + result.cpu_model = .baseline; + } else { + result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) }; + } + + while (index < cpu_features.len) { + const op = cpu_features[index]; + const set = switch (op) { + '+' => add_set, + '-' => sub_set, + else => unreachable, + }; + index += 1; + const start = index; + while (index < cpu_features.len and + cpu_features[index] != '+' and + cpu_features[index] != '-') + { + index += 1; + } + const feature_name = cpu_features[start..index]; + for (all_features) |feature, feat_index_usize| { + const feat_index = @intCast(Target.Cpu.Feature.Set.Index, feat_index_usize); + if (mem.eql(u8, feature_name, feature.name)) { + set.addFeature(feat_index); + break; + } + } else { + diags.unknown_feature_name = feature_name; + return error.UnknownCpuFeature; + } + } + } + + return result; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getCpu(self: CrossTarget) 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`. + return builtin.cpu; + }, + .baseline => { + var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); + self.updateCpuFeatures(&adjusted_baseline.features); + return adjusted_baseline; + }, + .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`. + return builtin.cpu; + } else { + var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); + self.updateCpuFeatures(&adjusted_baseline.features); + return adjusted_baseline; + }, + .explicit => |model| { + var adjusted_model = model.toCpu(self.getCpuArch()); + self.updateCpuFeatures(&adjusted_model.features); + return adjusted_model; + }, + } + } + + pub fn getCpuArch(self: CrossTarget) Target.Cpu.Arch { + return self.cpu_arch orelse builtin.cpu.arch; + } + + pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model { + return switch (self.cpu_model) { + .explicit => |cpu_model| cpu_model, + else => self.getCpu().model, + }; + } + + pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set { + return self.getCpu().features; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getOs(self: CrossTarget) 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`. + var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange() else builtin.os; + + if (self.os_version_min) |min| switch (min) { + .none => {}, + .semver => |semver| switch (self.getOsTag()) { + .linux => adjusted_os.version_range.linux.range.min = semver, + else => adjusted_os.version_range.semver.min = semver, + }, + .windows => |win_ver| adjusted_os.version_range.windows.min = win_ver, + }; + + if (self.os_version_max) |max| switch (max) { + .none => {}, + .semver => |semver| switch (self.getOsTag()) { + .linux => adjusted_os.version_range.linux.range.max = semver, + else => adjusted_os.version_range.semver.max = semver, + }, + .windows => |win_ver| adjusted_os.version_range.windows.max = win_ver, + }; + + if (self.glibc_version) |glibc| { + assert(self.isGnuLibC()); + adjusted_os.version_range.linux.glibc = glibc; + } + + return adjusted_os; + } + + pub fn getOsTag(self: CrossTarget) Target.Os.Tag { + return self.os_tag orelse builtin.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; + tmp.updateOsVersionRange(self.getOs()); + 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; + tmp.updateOsVersionRange(self.getOs()); + 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; + + 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`. + return builtin.abi; + } + + return Target.Abi.default(self.getCpuArch(), self.getOs()); + } + + pub fn isFreeBSD(self: CrossTarget) bool { + return self.getOsTag() == .freebsd; + } + + pub fn isDarwin(self: CrossTarget) bool { + return self.getOsTag().isDarwin(); + } + + pub fn isNetBSD(self: CrossTarget) bool { + return self.getOsTag() == .netbsd; + } + + pub fn isOpenBSD(self: CrossTarget) bool { + return self.getOsTag() == .openbsd; + } + + pub fn isUefi(self: CrossTarget) bool { + return self.getOsTag() == .uefi; + } + + pub fn isDragonFlyBSD(self: CrossTarget) bool { + return self.getOsTag() == .dragonfly; + } + + pub fn isLinux(self: CrossTarget) bool { + return self.getOsTag() == .linux; + } + + pub fn isWindows(self: CrossTarget) bool { + return self.getOsTag() == .windows; + } + + pub fn exeFileExt(self: CrossTarget) [:0]const u8 { + return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag()); + } + + pub fn staticLibSuffix(self: CrossTarget) [:0]const u8 { + return Target.staticLibSuffix_os_abi(self.getOsTag(), self.getAbi()); + } + + pub fn dynamicLibSuffix(self: CrossTarget) [:0]const u8 { + return self.getOsTag().dynamicLibSuffix(); + } + + pub fn libPrefix(self: CrossTarget) [:0]const u8 { + return Target.libPrefix_os_abi(self.getOsTag(), self.getAbi()); + } + + pub fn isNativeCpu(self: CrossTarget) bool { + return self.cpu_arch == null and + (self.cpu_model == .native or self.cpu_model == .determined_by_cpu_arch) and + self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty(); + } + + pub fn isNativeOs(self: CrossTarget) bool { + return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and + self.dynamic_linker.get() == null and self.glibc_version == null; + } + + pub fn isNativeAbi(self: CrossTarget) bool { + return self.os_tag == null and self.abi == null; + } + + pub fn isNative(self: CrossTarget) bool { + return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi(); + } + + pub fn zigTriple(self: CrossTarget, allocator: *mem.Allocator) error{OutOfMemory}![]u8 { + if (self.isNative()) { + return allocator.dupe(u8, "native"); + } + + const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native"; + const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native"; + + var result = std.ArrayList(u8).init(allocator); + defer result.deinit(); + + try result.writer().print("{s}-{s}", .{ arch_name, os_name }); + + // The zig target syntax does not allow specifying a max os version with no min, so + // if either are present, we need the min. + if (self.os_version_min != null or self.os_version_max != null) { + switch (self.getOsVersionMin()) { + .none => {}, + .semver => |v| try result.writer().print(".{}", .{v}), + .windows => |v| try result.writer().print("{s}", .{v}), + } + } + if (self.os_version_max) |max| { + switch (max) { + .none => {}, + .semver => |v| try result.writer().print("...{}", .{v}), + .windows => |v| try result.writer().print("..{s}", .{v}), + } + } + + if (self.glibc_version) |v| { + try result.writer().print("-{s}.{}", .{ @tagName(self.getAbi()), v }); + } else if (self.abi) |abi| { + try result.writer().print("-{s}", .{@tagName(abi)}); + } + + return result.toOwnedSlice(); + } + + pub fn allocDescription(self: CrossTarget, allocator: *mem.Allocator) ![]u8 { + // TODO is there anything else worthy of the description that is not + // already captured in the triple? + return self.zigTriple(allocator); + } + + pub fn linuxTriple(self: CrossTarget, allocator: *mem.Allocator) ![]u8 { + return Target.linuxTripleSimple(allocator, self.getCpuArch(), self.getOsTag(), self.getAbi()); + } + + pub fn wantSharedLibSymLinks(self: CrossTarget) bool { + return self.getOsTag() != .windows; + } + + pub const VcpkgLinkage = std.builtin.LinkMode; + + /// Returned slice must be freed by the caller. + pub fn vcpkgTriplet(self: CrossTarget, allocator: *mem.Allocator, linkage: VcpkgLinkage) ![]u8 { + const arch = switch (self.getCpuArch()) { + .i386 => "x86", + .x86_64 => "x64", + + .arm, + .armeb, + .thumb, + .thumbeb, + .aarch64_32, + => "arm", + + .aarch64, + .aarch64_be, + => "arm64", + + else => return error.UnsupportedVcpkgArchitecture, + }; + + const os = switch (self.getOsTag()) { + .windows => "windows", + .linux => "linux", + .macos => "macos", + else => return error.UnsupportedVcpkgOperatingSystem, + }; + + const static_suffix = switch (linkage) { + .Static => "-static", + .Dynamic => "", + }; + + return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix }); + } + + pub const Executor = union(enum) { + native, + qemu: []const u8, + wine: []const u8, + wasmtime: []const u8, + darling: []const u8, + unavailable, + }; + + /// Note that even a `CrossTarget` which returns `false` for `isNative` could still be natively executed. + /// For example `-target arm-native` running on an aarch64 host. + pub fn getExternalExecutor(self: CrossTarget) Executor { + const cpu_arch = self.getCpuArch(); + const os_tag = self.getOsTag(); + const os_match = os_tag == builtin.os.tag; + + // If the OS and CPU arch match, the binary can be considered native. + // TODO additionally match the CPU features. This `getExternalExecutor` function should + // be moved to std.Target and match any chosen target against the native target. + if (os_match and cpu_arch == builtin.cpu.arch) { + // However, we also need to verify that the dynamic linker path is valid. + if (self.os_tag == null) { + return .native; + } + // TODO here we call toTarget, a deprecated function, because of the above TODO about moving + // this code to std.Target. + const opt_dl = self.dynamic_linker.get() orelse self.toTarget().standardDynamicLinkerPath().get(); + if (opt_dl) |dl| blk: { + std.fs.cwd().access(dl, .{}) catch break :blk; + return .native; + } + } + // If the OS match and OS is macOS and CPU is arm64, treat always as native + // since we'll be running the foreign architecture tests using Rosetta2. + if (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) { + return .native; + } + + // If the OS matches, we can use QEMU to emulate a foreign architecture. + if (os_match) { + return switch (cpu_arch) { + .aarch64 => Executor{ .qemu = "qemu-aarch64" }, + .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, + .arm => Executor{ .qemu = "qemu-arm" }, + .armeb => Executor{ .qemu = "qemu-armeb" }, + .i386 => Executor{ .qemu = "qemu-i386" }, + .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" }, + .x86_64 => Executor{ .qemu = "qemu-x86_64" }, + else => return .unavailable, + }; + } + + switch (os_tag) { + .windows => switch (cpu_arch.ptrBitWidth()) { + 32 => return Executor{ .wine = "wine" }, + 64 => return Executor{ .wine = "wine64" }, + else => return .unavailable, + }, + .wasi => switch (cpu_arch.ptrBitWidth()) { + 32 => return Executor{ .wasmtime = "wasmtime" }, + else => return .unavailable, + }, + .macos => { + // TODO loosen this check once upstream adds QEMU-based emulation + // layer for non-host architectures: + // https://github.com/darlinghq/darling/issues/863 + if (cpu_arch != builtin.cpu.arch) { + return .unavailable; + } + return Executor{ .darling = "darling" }; + }, + else => return .unavailable, + } + } + + pub fn isGnuLibC(self: CrossTarget) bool { + return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi()); + } + + pub fn setGnuLibCVersion(self: *CrossTarget, major: u32, minor: u32, patch: u32) void { + assert(self.isGnuLibC()); + self.glibc_version = SemVer{ .major = major, .minor = minor, .patch = patch }; + } + + pub fn getObjectFormat(self: CrossTarget) Target.ObjectFormat { + return Target.getObjectFormatSimple(self.getOsTag(), self.getCpuArch()); + } + + pub fn updateCpuFeatures(self: CrossTarget, set: *Target.Cpu.Feature.Set) void { + set.removeFeatureSet(self.cpu_features_sub); + set.addFeatureSet(self.cpu_features_add); + set.populateDependencies(self.getCpuArch().allFeaturesList()); + set.removeFeatureSet(self.cpu_features_sub); + } + + fn parseOs(result: *CrossTarget, diags: *ParseOptions.Diagnostics, text: []const u8) !void { + var it = mem.split(u8, text, "."); + const os_name = it.next().?; + diags.os_name = os_name; + const os_is_native = mem.eql(u8, os_name, "native"); + if (!os_is_native) { + result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse + return error.UnknownOperatingSystem; + } + const tag = result.getOsTag(); + diags.os_tag = tag; + + const version_text = it.rest(); + if (it.next() == null) return; + + switch (tag) { + .freestanding, + .ananas, + .cloudabi, + .fuchsia, + .kfreebsd, + .lv2, + .solaris, + .zos, + .haiku, + .minix, + .rtems, + .nacl, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .opencl, + .glsl450, + .vulkan, + .plan9, + .other, + => return error.InvalidOperatingSystemVersion, + + .freebsd, + .macos, + .ios, + .tvos, + .watchos, + .netbsd, + .openbsd, + .linux, + .dragonfly, + => { + var range_it = mem.split(u8, version_text, "..."); + + const min_text = range_it.next().?; + const min_ver = SemVer.parse(min_text) catch |err| switch (err) { + error.Overflow => return error.InvalidOperatingSystemVersion, + error.InvalidCharacter => return error.InvalidOperatingSystemVersion, + error.InvalidVersion => return error.InvalidOperatingSystemVersion, + }; + result.os_version_min = .{ .semver = min_ver }; + + const max_text = range_it.next() orelse return; + const max_ver = SemVer.parse(max_text) catch |err| switch (err) { + error.Overflow => return error.InvalidOperatingSystemVersion, + error.InvalidCharacter => return error.InvalidOperatingSystemVersion, + error.InvalidVersion => return error.InvalidOperatingSystemVersion, + }; + result.os_version_max = .{ .semver = max_ver }; + }, + + .windows => { + var range_it = mem.split(u8, version_text, "..."); + + const min_text = range_it.next().?; + const min_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, min_text) orelse + return error.InvalidOperatingSystemVersion; + result.os_version_min = .{ .windows = min_ver }; + + const max_text = range_it.next() orelse return; + const max_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, max_text) orelse + return error.InvalidOperatingSystemVersion; + result.os_version_max = .{ .windows = max_ver }; + }, + } + } +}; + +test "CrossTarget.parse" { + if (builtin.target.isGnuLibC()) { + var cross_target = try CrossTarget.parse(.{}); + cross_target.setGnuLibCVersion(2, 1, 1); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + + var buf: [256]u8 = undefined; + const triple = std.fmt.bufPrint( + buf[0..], + "native-native-{s}.2.1.1", + .{@tagName(builtin.abi)}, + ) catch unreachable; + + try std.testing.expectEqualSlices(u8, triple, text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "aarch64-linux", + .cpu_features = "native", + }); + + try std.testing.expect(cross_target.cpu_arch.? == .aarch64); + try std.testing.expect(cross_target.cpu_model == .native); + } + { + const cross_target = try CrossTarget.parse(.{ .arch_os_abi = "native" }); + + try std.testing.expect(cross_target.cpu_arch == null); + try std.testing.expect(cross_target.isNative()); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + try std.testing.expectEqualSlices(u8, "native", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "x86_64-linux-gnu", + .cpu_features = "x86_64-sse-sse2-avx-cx8", + }); + const target = cross_target.toTarget(); + + try std.testing.expect(target.os.tag == .linux); + try std.testing.expect(target.abi == .gnu); + try std.testing.expect(target.cpu.arch == .x86_64); + try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse)); + try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx)); + try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8)); + try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov)); + try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr)); + + try std.testing.expect(Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx, .cmov })); + try std.testing.expect(!Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx })); + try std.testing.expect(Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87 })); + try std.testing.expect(!Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87, .sse })); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + try std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "arm-linux-musleabihf", + .cpu_features = "generic+v8a", + }); + const target = cross_target.toTarget(); + + try std.testing.expect(target.os.tag == .linux); + try std.testing.expect(target.abi == .musleabihf); + try std.testing.expect(target.cpu.arch == .arm); + try std.testing.expect(target.cpu.model == &Target.arm.cpu.generic); + try std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a)); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + try std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27", + .cpu_features = "generic+v8a", + }); + const target = cross_target.toTarget(); + + try std.testing.expect(target.cpu.arch == .aarch64); + try std.testing.expect(target.os.tag == .linux); + try std.testing.expect(target.os.version_range.linux.range.min.major == 3); + try std.testing.expect(target.os.version_range.linux.range.min.minor == 10); + try std.testing.expect(target.os.version_range.linux.range.min.patch == 0); + try std.testing.expect(target.os.version_range.linux.range.max.major == 4); + try std.testing.expect(target.os.version_range.linux.range.max.minor == 4); + try std.testing.expect(target.os.version_range.linux.range.max.patch == 1); + try std.testing.expect(target.os.version_range.linux.glibc.major == 2); + try std.testing.expect(target.os.version_range.linux.glibc.minor == 27); + try std.testing.expect(target.os.version_range.linux.glibc.patch == 0); + try std.testing.expect(target.abi == .gnu); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text); + } +} From 3e2f8233a86bf4a2787eb6d37a54b9c8eae8a985 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 10 Nov 2021 07:40:52 -0800 Subject: [PATCH 2/5] Make Rosetta a new variant in ExternalExecutor enum When running, check if Rosetta is available, otherwise, pass the tests. --- lib/std/build.zig | 2 +- lib/std/zig/CrossTarget.zig | 9 ++++++--- lib/std/zig/cross_target.zig | 10 +++++++--- src/test.zig | 23 +++++++++++++++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index ea6a0e05f1..6c91303c14 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2529,7 +2529,7 @@ pub const LibExeObjStep = struct { } } } else switch (self.target.getExternalExecutor()) { - .native, .unavailable => {}, + .native, .rosetta, .unavailable => {}, .qemu => |bin_name| if (self.enable_qemu) qemu: { const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc; const glibc_dir_arg = if (need_cross_glibc) diff --git a/lib/std/zig/CrossTarget.zig b/lib/std/zig/CrossTarget.zig index 6024c893fb..96213b1471 100644 --- a/lib/std/zig/CrossTarget.zig +++ b/lib/std/zig/CrossTarget.zig @@ -643,10 +643,13 @@ pub fn getExternalExecutor(self: CrossTarget) Executor { return .native; } } - // If the OS match and OS is macOS and CPU is arm64, treat always as native - // since we'll be running the foreign architecture tests using Rosetta2. + // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 + // to emulate the foreign architecture. if (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) { - return .native; + return switch (cpu_arch) { + .x86_64 => .rosetta, + else => .unavailable, + }; } // If the OS matches, we can use QEMU to emulate a foreign architecture. diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 8e3725c623..9134b6ad5c 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -596,6 +596,7 @@ pub const CrossTarget = struct { pub const Executor = union(enum) { native, + rosetta, qemu: []const u8, wine: []const u8, wasmtime: []const u8, @@ -626,10 +627,13 @@ pub const CrossTarget = struct { return .native; } } - // If the OS match and OS is macOS and CPU is arm64, treat always as native - // since we'll be running the foreign architecture tests using Rosetta2. + // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 + // to emulate the foreign architecture. if (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) { - return .native; + return switch (cpu_arch) { + .x86_64 => .rosetta, + else => .unavailable, + }; } // If the OS matches, we can use QEMU to emulate a foreign architecture. diff --git a/src/test.zig b/src/test.zig index 44faea0ed9..a0bf9c2c03 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1132,6 +1132,29 @@ pub const TestContext = struct { .native => try argv.append(exe_path), .unavailable => return, // Pass test. + .rosetta => if (builtin.os.tag == .macos) { + // Check based on official Apple docs. + // If sysctlbyname returns errno.ENOENT, then we are running a native process. + // Otherwise, if an error occurs then we are not native and there is no Rosetta available. + // Finally if OK, we are running a translated process via Rosetta. + // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment + var ret: c_int = 0; + var size: usize = @sizeOf(c_int); + std.os.sysctlbynameZ( + "sysctl.proc_translated", + &ret, + &size, + null, + 0, + ) catch |err| switch (err) { + error.UnknownName => unreachable, // Native process, we should never trigger it as .rosetta. + else => return, // No Rosetta available, pass test. + }; + try argv.append(exe_path); + } else { + return; // Rosetta not available, pass test. + }, + .qemu => |qemu_bin_name| if (enable_qemu) { // TODO Ability for test cases to specify whether to link libc. const need_cross_glibc = false; // target.isGnuLibC() and self.is_linking_libc; From 852841fd1f82bb5ba5ecdf9a85a1f15ef70e6dd0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 1 Dec 2021 23:33:28 +0100 Subject: [PATCH 3/5] Make Rosetta availability declarative by the user Like QEMU or Wine, you need to declare you want Rosetta enabled for running tests/build artifacts via `-Denable-rosetta` flag. --- build.zig | 6 ++++++ lib/std/build.zig | 8 +++++++- src/test.zig | 20 ++------------------ test/tests.zig | 2 ++ 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/build.zig b/build.zig index 5887d2a932..28d1486ad0 100644 --- a/build.zig +++ b/build.zig @@ -300,6 +300,7 @@ pub fn build(b: *Builder) !void { const is_qemu_enabled = b.option(bool, "enable-qemu", "Use QEMU to run cross compiled foreign architecture tests") orelse false; const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false; const is_darling_enabled = b.option(bool, "enable-darling", "[Experimental] Use Darling to run cross compiled macOS tests") orelse false; + const is_rosetta_enabled = b.option(bool, "enable-rosetta", "(Darwin) Use Rosetta to run x86_64 macOS tests on arm64 macOS") orelse false; const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc"); const test_stage2_options = b.addOptions(); @@ -319,6 +320,7 @@ pub fn build(b: *Builder) !void { test_stage2_options.addOption(bool, "enable_qemu", is_qemu_enabled); test_stage2_options.addOption(bool, "enable_wine", is_wine_enabled); test_stage2_options.addOption(bool, "enable_wasmtime", is_wasmtime_enabled); + test_stage2_options.addOption(bool, "enable_rosetta", is_rosetta_enabled); test_stage2_options.addOption(u32, "mem_leak_frames", mem_leak_frames * 2); test_stage2_options.addOption(bool, "enable_darling", is_darling_enabled); test_stage2_options.addOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir); @@ -370,6 +372,7 @@ pub fn build(b: *Builder) !void { is_qemu_enabled, is_wasmtime_enabled, is_darling_enabled, + is_rosetta_enabled, glibc_multi_dir, )); @@ -387,6 +390,7 @@ pub fn build(b: *Builder) !void { is_qemu_enabled, is_wasmtime_enabled, is_darling_enabled, + is_rosetta_enabled, glibc_multi_dir, )); @@ -404,6 +408,7 @@ pub fn build(b: *Builder) !void { is_qemu_enabled, is_wasmtime_enabled, is_darling_enabled, + is_rosetta_enabled, glibc_multi_dir, )); @@ -434,6 +439,7 @@ pub fn build(b: *Builder) !void { is_qemu_enabled, is_wasmtime_enabled, is_darling_enabled, + is_rosetta_enabled, glibc_multi_dir, ); diff --git a/lib/std/build.zig b/lib/std/build.zig index 6c91303c14..600fb691ae 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1516,6 +1516,9 @@ pub const LibExeObjStep = struct { /// Experimental. Uses system Darling installation to run cross compiled macOS build artifacts. enable_darling: bool = false, + /// Darwin. Uses Rosetta to run x86_64 macOS build artifacts on arm64 macOS. + enable_rosetta: bool = false, + /// After following the steps in https://github.com/ziglang/zig/wiki/Updating-libc#glibc, /// this will be the directory $glibc-build-dir/install/glibcs /// Given the example of the aarch64 target, this is the directory @@ -2529,7 +2532,10 @@ pub const LibExeObjStep = struct { } } } else switch (self.target.getExternalExecutor()) { - .native, .rosetta, .unavailable => {}, + .native, .unavailable => {}, + .rosetta => if (self.enable_rosetta) { + try zig_args.append("--test-cmd-bin"); + }, .qemu => |bin_name| if (self.enable_qemu) qemu: { const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc; const glibc_dir_arg = if (need_cross_glibc) diff --git a/src/test.zig b/src/test.zig index a0bf9c2c03..69d2d31a08 100644 --- a/src/test.zig +++ b/src/test.zig @@ -10,6 +10,7 @@ const enable_qemu: bool = build_options.enable_qemu; const enable_wine: bool = build_options.enable_wine; const enable_wasmtime: bool = build_options.enable_wasmtime; const enable_darling: bool = build_options.enable_darling; +const enable_rosetta: bool = build_options.enable_rosetta; const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir; const skip_compile_errors = build_options.skip_compile_errors; const ThreadPool = @import("ThreadPool.zig"); @@ -1132,24 +1133,7 @@ pub const TestContext = struct { .native => try argv.append(exe_path), .unavailable => return, // Pass test. - .rosetta => if (builtin.os.tag == .macos) { - // Check based on official Apple docs. - // If sysctlbyname returns errno.ENOENT, then we are running a native process. - // Otherwise, if an error occurs then we are not native and there is no Rosetta available. - // Finally if OK, we are running a translated process via Rosetta. - // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment - var ret: c_int = 0; - var size: usize = @sizeOf(c_int); - std.os.sysctlbynameZ( - "sysctl.proc_translated", - &ret, - &size, - null, - 0, - ) catch |err| switch (err) { - error.UnknownName => unreachable, // Native process, we should never trigger it as .rosetta. - else => return, // No Rosetta available, pass test. - }; + .rosetta => if (enable_rosetta) { try argv.append(exe_path); } else { return; // Rosetta not available, pass test. diff --git a/test/tests.zig b/test/tests.zig index 267830bbdc..b7fcffe502 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -511,6 +511,7 @@ pub fn addPkgTests( is_qemu_enabled: bool, is_wasmtime_enabled: bool, is_darling_enabled: bool, + is_rosetta_enabled: bool, glibc_dir: ?[]const u8, ) *build.Step { const step = b.step(b.fmt("test-{s}", .{name}), desc); @@ -572,6 +573,7 @@ pub fn addPkgTests( these_tests.enable_qemu = is_qemu_enabled; these_tests.enable_wasmtime = is_wasmtime_enabled; these_tests.enable_darling = is_darling_enabled; + these_tests.enable_rosetta = is_rosetta_enabled; these_tests.glibc_multi_install_dir = glibc_dir; these_tests.addIncludeDir("test"); From cf4423bb33598f13a4842fbae4013c1c25d907e2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 2 Dec 2021 00:11:29 +0100 Subject: [PATCH 4/5] Remove .disable_native for x86_64-macos as it's fixed now Add `aarch64-macos-gnu` corresponding test case. Fix rebase gone wrong. --- lib/std/zig/cross_target.zig | 903 ----------------------------------- test/tests.zig | 10 +- 2 files changed, 8 insertions(+), 905 deletions(-) delete mode 100644 lib/std/zig/cross_target.zig diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig deleted file mode 100644 index 9134b6ad5c..0000000000 --- a/lib/std/zig/cross_target.zig +++ /dev/null @@ -1,903 +0,0 @@ -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const Target = std.Target; -const mem = std.mem; - -/// Contains all the same data as `Target`, additionally introducing the concept of "the native target". -/// 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. - cpu_arch: ?Target.Cpu.Arch = null, - - cpu_model: CpuModel = CpuModel.determined_by_cpu_arch, - - /// Sparse set of CPU features to add to the set from `cpu_model`. - 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`. - cpu_features_sub: Target.Cpu.Feature.Set = 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. - 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. - os_version_max: ?OsVersion = 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{}, - - pub const CpuModel = union(enum) { - /// Always native - native, - - /// Always baseline - baseline, - - /// If CPU Architecture is native, then the CPU model will be native. Otherwise, - /// it will be baseline. - determined_by_cpu_arch, - - explicit: *const Target.Cpu.Model, - }; - - pub const OsVersion = union(enum) { - none: void, - semver: SemVer, - windows: Target.Os.WindowsVersion, - }; - - pub const SemVer = std.builtin.Version; - - pub const DynamicLinker = Target.DynamicLinker; - - pub fn fromTarget(target: Target) CrossTarget { - var result: CrossTarget = .{ - .cpu_arch = target.cpu.arch, - .cpu_model = .{ .explicit = target.cpu.model }, - .os_tag = target.os.tag, - .os_version_min = undefined, - .os_version_max = undefined, - .abi = target.abi, - .glibc_version = if (target.isGnuLibC()) - target.os.version_range.linux.glibc - else - null, - }; - result.updateOsVersionRange(target.os); - - const all_features = target.cpu.arch.allFeaturesList(); - var cpu_model_set = target.cpu.model.features; - cpu_model_set.populateDependencies(all_features); - { - // The "add" set is the full set with the CPU Model set removed. - const add_set = &result.cpu_features_add; - add_set.* = target.cpu.features; - add_set.removeFeatureSet(cpu_model_set); - } - { - // The "sub" set is the features that are on in CPU Model set and off in the full set. - const sub_set = &result.cpu_features_sub; - sub_set.* = cpu_model_set; - sub_set.removeFeatureSet(target.cpu.features); - } - return result; - } - - fn updateOsVersionRange(self: *CrossTarget, os: Target.Os) void { - switch (os.tag) { - .freestanding, - .ananas, - .cloudabi, - .fuchsia, - .kfreebsd, - .lv2, - .solaris, - .zos, - .haiku, - .minix, - .rtems, - .nacl, - .aix, - .cuda, - .nvcl, - .amdhsa, - .ps4, - .elfiamcu, - .mesa3d, - .contiki, - .amdpal, - .hermit, - .hurd, - .wasi, - .emscripten, - .uefi, - .opencl, - .glsl450, - .vulkan, - .plan9, - .other, - => { - self.os_version_min = .{ .none = {} }; - self.os_version_max = .{ .none = {} }; - }, - - .freebsd, - .macos, - .ios, - .tvos, - .watchos, - .netbsd, - .openbsd, - .dragonfly, - => { - self.os_version_min = .{ .semver = os.version_range.semver.min }; - self.os_version_max = .{ .semver = os.version_range.semver.max }; - }, - - .linux => { - self.os_version_min = .{ .semver = os.version_range.linux.range.min }; - self.os_version_max = .{ .semver = os.version_range.linux.range.max }; - }, - - .windows => { - self.os_version_min = .{ .windows = os.version_range.windows.min }; - self.os_version_max = .{ .windows = os.version_range.windows.max }; - }, - } - } - - /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. - pub fn toTarget(self: CrossTarget) Target { - return .{ - .cpu = self.getCpu(), - .os = self.getOs(), - .abi = self.getAbi(), - }; - } - - pub const ParseOptions = struct { - /// This is sometimes called a "triple". It looks roughly like this: - /// riscv64-linux-musl - /// The fields are, respectively: - /// * CPU Architecture - /// * Operating System (and optional version range) - /// * C ABI (optional, with optional glibc version) - /// The string "native" can be used for CPU architecture as well as Operating System. - /// If the CPU Architecture is specified as "native", then the Operating System and C ABI may be omitted. - arch_os_abi: []const u8 = "native", - - /// Looks like "name+a+b-c-d+e", where "name" is a CPU Model name, "a", "b", and "e" - /// are examples of CPU features to add to the set, and "c" and "d" are examples of CPU features - /// to remove from the set. - /// The following special strings are recognized for CPU Model name: - /// * "baseline" - The "default" set of CPU features for cross-compiling. A conservative set - /// of features that is expected to be supported on most available hardware. - /// * "native" - The native CPU model is to be detected when compiling. - /// If this field is not provided (`null`), then the value will depend on the - /// parsed CPU Architecture. If native, then this will be "native". Otherwise, it will be "baseline". - cpu_features: ?[]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, - - /// 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, - - pub const Diagnostics = struct { - /// If the architecture was determined, this will be populated. - arch: ?Target.Cpu.Arch = null, - - /// If the OS name was determined, this will be populated. - os_name: ?[]const u8 = null, - - /// If the OS tag was determined, this will be populated. - os_tag: ?Target.Os.Tag = null, - - /// If the ABI was determined, this will be populated. - abi: ?Target.Abi = null, - - /// If the CPU name was determined, this will be populated. - cpu_name: ?[]const u8 = null, - - /// If error.UnknownCpuFeature is returned, this will be populated. - unknown_feature_name: ?[]const u8 = null, - }; - }; - - pub fn parse(args: ParseOptions) !CrossTarget { - var dummy_diags: ParseOptions.Diagnostics = undefined; - const diags = args.diagnostics orelse &dummy_diags; - - var result: CrossTarget = .{ - .dynamic_linker = DynamicLinker.init(args.dynamic_linker), - }; - - var it = mem.split(u8, args.arch_os_abi, "-"); - const arch_name = it.next().?; - const arch_is_native = mem.eql(u8, arch_name, "native"); - if (!arch_is_native) { - result.cpu_arch = std.meta.stringToEnum(Target.Cpu.Arch, arch_name) orelse - return error.UnknownArchitecture; - } - const arch = result.getCpuArch(); - diags.arch = arch; - - if (it.next()) |os_text| { - try parseOs(&result, diags, os_text); - } else if (!arch_is_native) { - return error.MissingOperatingSystem; - } - - const opt_abi_text = it.next(); - if (opt_abi_text) |abi_text| { - var abi_it = mem.split(u8, abi_text, "."); - const abi = std.meta.stringToEnum(Target.Abi, abi_it.next().?) orelse - return error.UnknownApplicationBinaryInterface; - result.abi = abi; - diags.abi = abi; - - const abi_ver_text = abi_it.rest(); - if (abi_it.next() != null) { - if (result.isGnuLibC()) { - result.glibc_version = SemVer.parse(abi_ver_text) catch |err| switch (err) { - error.Overflow => return error.InvalidAbiVersion, - error.InvalidCharacter => return error.InvalidAbiVersion, - error.InvalidVersion => return error.InvalidAbiVersion, - }; - } else { - return error.InvalidAbiVersion; - } - } - } - - if (it.next() != null) return error.UnexpectedExtraField; - - if (args.cpu_features) |cpu_features| { - const all_features = arch.allFeaturesList(); - var index: usize = 0; - while (index < cpu_features.len and - cpu_features[index] != '+' and - cpu_features[index] != '-') - { - index += 1; - } - const cpu_name = cpu_features[0..index]; - diags.cpu_name = cpu_name; - - const add_set = &result.cpu_features_add; - const sub_set = &result.cpu_features_sub; - if (mem.eql(u8, cpu_name, "native")) { - result.cpu_model = .native; - } else if (mem.eql(u8, cpu_name, "baseline")) { - result.cpu_model = .baseline; - } else { - result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) }; - } - - while (index < cpu_features.len) { - const op = cpu_features[index]; - const set = switch (op) { - '+' => add_set, - '-' => sub_set, - else => unreachable, - }; - index += 1; - const start = index; - while (index < cpu_features.len and - cpu_features[index] != '+' and - cpu_features[index] != '-') - { - index += 1; - } - const feature_name = cpu_features[start..index]; - for (all_features) |feature, feat_index_usize| { - const feat_index = @intCast(Target.Cpu.Feature.Set.Index, feat_index_usize); - if (mem.eql(u8, feature_name, feature.name)) { - set.addFeature(feat_index); - break; - } - } else { - diags.unknown_feature_name = feature_name; - return error.UnknownCpuFeature; - } - } - } - - return result; - } - - /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. - pub fn getCpu(self: CrossTarget) 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`. - return builtin.cpu; - }, - .baseline => { - var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); - self.updateCpuFeatures(&adjusted_baseline.features); - return adjusted_baseline; - }, - .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`. - return builtin.cpu; - } else { - var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); - self.updateCpuFeatures(&adjusted_baseline.features); - return adjusted_baseline; - }, - .explicit => |model| { - var adjusted_model = model.toCpu(self.getCpuArch()); - self.updateCpuFeatures(&adjusted_model.features); - return adjusted_model; - }, - } - } - - pub fn getCpuArch(self: CrossTarget) Target.Cpu.Arch { - return self.cpu_arch orelse builtin.cpu.arch; - } - - pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model { - return switch (self.cpu_model) { - .explicit => |cpu_model| cpu_model, - else => self.getCpu().model, - }; - } - - pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set { - return self.getCpu().features; - } - - /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. - pub fn getOs(self: CrossTarget) 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`. - var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange() else builtin.os; - - if (self.os_version_min) |min| switch (min) { - .none => {}, - .semver => |semver| switch (self.getOsTag()) { - .linux => adjusted_os.version_range.linux.range.min = semver, - else => adjusted_os.version_range.semver.min = semver, - }, - .windows => |win_ver| adjusted_os.version_range.windows.min = win_ver, - }; - - if (self.os_version_max) |max| switch (max) { - .none => {}, - .semver => |semver| switch (self.getOsTag()) { - .linux => adjusted_os.version_range.linux.range.max = semver, - else => adjusted_os.version_range.semver.max = semver, - }, - .windows => |win_ver| adjusted_os.version_range.windows.max = win_ver, - }; - - if (self.glibc_version) |glibc| { - assert(self.isGnuLibC()); - adjusted_os.version_range.linux.glibc = glibc; - } - - return adjusted_os; - } - - pub fn getOsTag(self: CrossTarget) Target.Os.Tag { - return self.os_tag orelse builtin.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; - tmp.updateOsVersionRange(self.getOs()); - 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; - tmp.updateOsVersionRange(self.getOs()); - 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; - - 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`. - return builtin.abi; - } - - return Target.Abi.default(self.getCpuArch(), self.getOs()); - } - - pub fn isFreeBSD(self: CrossTarget) bool { - return self.getOsTag() == .freebsd; - } - - pub fn isDarwin(self: CrossTarget) bool { - return self.getOsTag().isDarwin(); - } - - pub fn isNetBSD(self: CrossTarget) bool { - return self.getOsTag() == .netbsd; - } - - pub fn isOpenBSD(self: CrossTarget) bool { - return self.getOsTag() == .openbsd; - } - - pub fn isUefi(self: CrossTarget) bool { - return self.getOsTag() == .uefi; - } - - pub fn isDragonFlyBSD(self: CrossTarget) bool { - return self.getOsTag() == .dragonfly; - } - - pub fn isLinux(self: CrossTarget) bool { - return self.getOsTag() == .linux; - } - - pub fn isWindows(self: CrossTarget) bool { - return self.getOsTag() == .windows; - } - - pub fn exeFileExt(self: CrossTarget) [:0]const u8 { - return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag()); - } - - pub fn staticLibSuffix(self: CrossTarget) [:0]const u8 { - return Target.staticLibSuffix_os_abi(self.getOsTag(), self.getAbi()); - } - - pub fn dynamicLibSuffix(self: CrossTarget) [:0]const u8 { - return self.getOsTag().dynamicLibSuffix(); - } - - pub fn libPrefix(self: CrossTarget) [:0]const u8 { - return Target.libPrefix_os_abi(self.getOsTag(), self.getAbi()); - } - - pub fn isNativeCpu(self: CrossTarget) bool { - return self.cpu_arch == null and - (self.cpu_model == .native or self.cpu_model == .determined_by_cpu_arch) and - self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty(); - } - - pub fn isNativeOs(self: CrossTarget) bool { - return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and - self.dynamic_linker.get() == null and self.glibc_version == null; - } - - pub fn isNativeAbi(self: CrossTarget) bool { - return self.os_tag == null and self.abi == null; - } - - pub fn isNative(self: CrossTarget) bool { - return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi(); - } - - pub fn zigTriple(self: CrossTarget, allocator: *mem.Allocator) error{OutOfMemory}![]u8 { - if (self.isNative()) { - return allocator.dupe(u8, "native"); - } - - const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native"; - const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native"; - - var result = std.ArrayList(u8).init(allocator); - defer result.deinit(); - - try result.writer().print("{s}-{s}", .{ arch_name, os_name }); - - // The zig target syntax does not allow specifying a max os version with no min, so - // if either are present, we need the min. - if (self.os_version_min != null or self.os_version_max != null) { - switch (self.getOsVersionMin()) { - .none => {}, - .semver => |v| try result.writer().print(".{}", .{v}), - .windows => |v| try result.writer().print("{s}", .{v}), - } - } - if (self.os_version_max) |max| { - switch (max) { - .none => {}, - .semver => |v| try result.writer().print("...{}", .{v}), - .windows => |v| try result.writer().print("..{s}", .{v}), - } - } - - if (self.glibc_version) |v| { - try result.writer().print("-{s}.{}", .{ @tagName(self.getAbi()), v }); - } else if (self.abi) |abi| { - try result.writer().print("-{s}", .{@tagName(abi)}); - } - - return result.toOwnedSlice(); - } - - pub fn allocDescription(self: CrossTarget, allocator: *mem.Allocator) ![]u8 { - // TODO is there anything else worthy of the description that is not - // already captured in the triple? - return self.zigTriple(allocator); - } - - pub fn linuxTriple(self: CrossTarget, allocator: *mem.Allocator) ![]u8 { - return Target.linuxTripleSimple(allocator, self.getCpuArch(), self.getOsTag(), self.getAbi()); - } - - pub fn wantSharedLibSymLinks(self: CrossTarget) bool { - return self.getOsTag() != .windows; - } - - pub const VcpkgLinkage = std.builtin.LinkMode; - - /// Returned slice must be freed by the caller. - pub fn vcpkgTriplet(self: CrossTarget, allocator: *mem.Allocator, linkage: VcpkgLinkage) ![]u8 { - const arch = switch (self.getCpuArch()) { - .i386 => "x86", - .x86_64 => "x64", - - .arm, - .armeb, - .thumb, - .thumbeb, - .aarch64_32, - => "arm", - - .aarch64, - .aarch64_be, - => "arm64", - - else => return error.UnsupportedVcpkgArchitecture, - }; - - const os = switch (self.getOsTag()) { - .windows => "windows", - .linux => "linux", - .macos => "macos", - else => return error.UnsupportedVcpkgOperatingSystem, - }; - - const static_suffix = switch (linkage) { - .Static => "-static", - .Dynamic => "", - }; - - return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix }); - } - - pub const Executor = union(enum) { - native, - rosetta, - qemu: []const u8, - wine: []const u8, - wasmtime: []const u8, - darling: []const u8, - unavailable, - }; - - /// Note that even a `CrossTarget` which returns `false` for `isNative` could still be natively executed. - /// For example `-target arm-native` running on an aarch64 host. - pub fn getExternalExecutor(self: CrossTarget) Executor { - const cpu_arch = self.getCpuArch(); - const os_tag = self.getOsTag(); - const os_match = os_tag == builtin.os.tag; - - // If the OS and CPU arch match, the binary can be considered native. - // TODO additionally match the CPU features. This `getExternalExecutor` function should - // be moved to std.Target and match any chosen target against the native target. - if (os_match and cpu_arch == builtin.cpu.arch) { - // However, we also need to verify that the dynamic linker path is valid. - if (self.os_tag == null) { - return .native; - } - // TODO here we call toTarget, a deprecated function, because of the above TODO about moving - // this code to std.Target. - const opt_dl = self.dynamic_linker.get() orelse self.toTarget().standardDynamicLinkerPath().get(); - if (opt_dl) |dl| blk: { - std.fs.cwd().access(dl, .{}) catch break :blk; - 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 (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) { - return switch (cpu_arch) { - .x86_64 => .rosetta, - else => .unavailable, - }; - } - - // If the OS matches, we can use QEMU to emulate a foreign architecture. - if (os_match) { - return switch (cpu_arch) { - .aarch64 => Executor{ .qemu = "qemu-aarch64" }, - .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, - .arm => Executor{ .qemu = "qemu-arm" }, - .armeb => Executor{ .qemu = "qemu-armeb" }, - .i386 => Executor{ .qemu = "qemu-i386" }, - .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" }, - .x86_64 => Executor{ .qemu = "qemu-x86_64" }, - else => return .unavailable, - }; - } - - switch (os_tag) { - .windows => switch (cpu_arch.ptrBitWidth()) { - 32 => return Executor{ .wine = "wine" }, - 64 => return Executor{ .wine = "wine64" }, - else => return .unavailable, - }, - .wasi => switch (cpu_arch.ptrBitWidth()) { - 32 => return Executor{ .wasmtime = "wasmtime" }, - else => return .unavailable, - }, - .macos => { - // TODO loosen this check once upstream adds QEMU-based emulation - // layer for non-host architectures: - // https://github.com/darlinghq/darling/issues/863 - if (cpu_arch != builtin.cpu.arch) { - return .unavailable; - } - return Executor{ .darling = "darling" }; - }, - else => return .unavailable, - } - } - - pub fn isGnuLibC(self: CrossTarget) bool { - return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi()); - } - - pub fn setGnuLibCVersion(self: *CrossTarget, major: u32, minor: u32, patch: u32) void { - assert(self.isGnuLibC()); - self.glibc_version = SemVer{ .major = major, .minor = minor, .patch = patch }; - } - - pub fn getObjectFormat(self: CrossTarget) Target.ObjectFormat { - return Target.getObjectFormatSimple(self.getOsTag(), self.getCpuArch()); - } - - pub fn updateCpuFeatures(self: CrossTarget, set: *Target.Cpu.Feature.Set) void { - set.removeFeatureSet(self.cpu_features_sub); - set.addFeatureSet(self.cpu_features_add); - set.populateDependencies(self.getCpuArch().allFeaturesList()); - set.removeFeatureSet(self.cpu_features_sub); - } - - fn parseOs(result: *CrossTarget, diags: *ParseOptions.Diagnostics, text: []const u8) !void { - var it = mem.split(u8, text, "."); - const os_name = it.next().?; - diags.os_name = os_name; - const os_is_native = mem.eql(u8, os_name, "native"); - if (!os_is_native) { - result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse - return error.UnknownOperatingSystem; - } - const tag = result.getOsTag(); - diags.os_tag = tag; - - const version_text = it.rest(); - if (it.next() == null) return; - - switch (tag) { - .freestanding, - .ananas, - .cloudabi, - .fuchsia, - .kfreebsd, - .lv2, - .solaris, - .zos, - .haiku, - .minix, - .rtems, - .nacl, - .aix, - .cuda, - .nvcl, - .amdhsa, - .ps4, - .elfiamcu, - .mesa3d, - .contiki, - .amdpal, - .hermit, - .hurd, - .wasi, - .emscripten, - .uefi, - .opencl, - .glsl450, - .vulkan, - .plan9, - .other, - => return error.InvalidOperatingSystemVersion, - - .freebsd, - .macos, - .ios, - .tvos, - .watchos, - .netbsd, - .openbsd, - .linux, - .dragonfly, - => { - var range_it = mem.split(u8, version_text, "..."); - - const min_text = range_it.next().?; - const min_ver = SemVer.parse(min_text) catch |err| switch (err) { - error.Overflow => return error.InvalidOperatingSystemVersion, - error.InvalidCharacter => return error.InvalidOperatingSystemVersion, - error.InvalidVersion => return error.InvalidOperatingSystemVersion, - }; - result.os_version_min = .{ .semver = min_ver }; - - const max_text = range_it.next() orelse return; - const max_ver = SemVer.parse(max_text) catch |err| switch (err) { - error.Overflow => return error.InvalidOperatingSystemVersion, - error.InvalidCharacter => return error.InvalidOperatingSystemVersion, - error.InvalidVersion => return error.InvalidOperatingSystemVersion, - }; - result.os_version_max = .{ .semver = max_ver }; - }, - - .windows => { - var range_it = mem.split(u8, version_text, "..."); - - const min_text = range_it.next().?; - const min_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, min_text) orelse - return error.InvalidOperatingSystemVersion; - result.os_version_min = .{ .windows = min_ver }; - - const max_text = range_it.next() orelse return; - const max_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, max_text) orelse - return error.InvalidOperatingSystemVersion; - result.os_version_max = .{ .windows = max_ver }; - }, - } - } -}; - -test "CrossTarget.parse" { - if (builtin.target.isGnuLibC()) { - var cross_target = try CrossTarget.parse(.{}); - cross_target.setGnuLibCVersion(2, 1, 1); - - const text = try cross_target.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - - var buf: [256]u8 = undefined; - const triple = std.fmt.bufPrint( - buf[0..], - "native-native-{s}.2.1.1", - .{@tagName(builtin.abi)}, - ) catch unreachable; - - try std.testing.expectEqualSlices(u8, triple, text); - } - { - const cross_target = try CrossTarget.parse(.{ - .arch_os_abi = "aarch64-linux", - .cpu_features = "native", - }); - - try std.testing.expect(cross_target.cpu_arch.? == .aarch64); - try std.testing.expect(cross_target.cpu_model == .native); - } - { - const cross_target = try CrossTarget.parse(.{ .arch_os_abi = "native" }); - - try std.testing.expect(cross_target.cpu_arch == null); - try std.testing.expect(cross_target.isNative()); - - const text = try cross_target.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - try std.testing.expectEqualSlices(u8, "native", text); - } - { - const cross_target = try CrossTarget.parse(.{ - .arch_os_abi = "x86_64-linux-gnu", - .cpu_features = "x86_64-sse-sse2-avx-cx8", - }); - const target = cross_target.toTarget(); - - try std.testing.expect(target.os.tag == .linux); - try std.testing.expect(target.abi == .gnu); - try std.testing.expect(target.cpu.arch == .x86_64); - try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse)); - try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx)); - try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8)); - try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov)); - try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr)); - - try std.testing.expect(Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx, .cmov })); - try std.testing.expect(!Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx })); - try std.testing.expect(Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87 })); - try std.testing.expect(!Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87, .sse })); - - const text = try cross_target.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - try std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text); - } - { - const cross_target = try CrossTarget.parse(.{ - .arch_os_abi = "arm-linux-musleabihf", - .cpu_features = "generic+v8a", - }); - const target = cross_target.toTarget(); - - try std.testing.expect(target.os.tag == .linux); - try std.testing.expect(target.abi == .musleabihf); - try std.testing.expect(target.cpu.arch == .arm); - try std.testing.expect(target.cpu.model == &Target.arm.cpu.generic); - try std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a)); - - const text = try cross_target.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - try std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text); - } - { - const cross_target = try CrossTarget.parse(.{ - .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27", - .cpu_features = "generic+v8a", - }); - const target = cross_target.toTarget(); - - try std.testing.expect(target.cpu.arch == .aarch64); - try std.testing.expect(target.os.tag == .linux); - try std.testing.expect(target.os.version_range.linux.range.min.major == 3); - try std.testing.expect(target.os.version_range.linux.range.min.minor == 10); - try std.testing.expect(target.os.version_range.linux.range.min.patch == 0); - try std.testing.expect(target.os.version_range.linux.range.max.major == 4); - try std.testing.expect(target.os.version_range.linux.range.max.minor == 4); - try std.testing.expect(target.os.version_range.linux.range.max.patch == 1); - try std.testing.expect(target.os.version_range.linux.glibc.major == 2); - try std.testing.expect(target.os.version_range.linux.glibc.minor == 27); - try std.testing.expect(target.os.version_range.linux.glibc.patch == 0); - try std.testing.expect(target.abi == .gnu); - - const text = try cross_target.zigTriple(std.testing.allocator); - defer std.testing.allocator.free(text); - try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text); - } -} diff --git a/test/tests.zig b/test/tests.zig index b7fcffe502..511d8bc6f2 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -270,8 +270,14 @@ const test_targets = blk: { .os_tag = .macos, .abi = .gnu, }, - // https://github.com/ziglang/zig/issues/3295 - .disable_native = true, + }, + + TestTarget{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .macos, + .abi = .gnu, + }, }, TestTarget{ From 42db5156656ebd1649f7ec3707697b08657b9cab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Dec 2021 17:30:41 -0800 Subject: [PATCH 5/5] disable failing `@mulAdd` behavior test for aarch64-macos See #9900 --- test/behavior/muladd.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig index eaa30324df..cff7b5e2ad 100644 --- a/test/behavior/muladd.zig +++ b/test/behavior/muladd.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const expect = @import("std").testing.expect; test "@mulAdd" { @@ -24,6 +25,10 @@ fn testMulAdd() !void { var c: f64 = 6.25; try expect(@mulAdd(f64, a, b, c) == 20); } + if (builtin.os.tag == .macos and builtin.cpu.arch == .aarch64) { + // https://github.com/ziglang/zig/issues/9900 + return error.SkipZigTest; + } { var a: f16 = 5.5; var b: f128 = 2.5;