Commit dbe4d72bcf

Andrew Kelley <andrew@ziglang.org>
2020-02-26 07:18:23
separate std.Target and std.zig.CrossTarget
Zig now supports a more fine-grained sense of what is native and what is not. Some examples: This is now allowed: -target native Different OS but native CPU, default Windows C ABI: -target native-windows This could be useful for example when running in Wine. Different CPU but native OS, native C ABI. -target x86_64-native -mcpu=skylake Different C ABI but otherwise native target: -target native-native-musl -target native-native-gnu Lots of breaking changes to related std lib APIs. Calls to getOs() will need to be changed to getOsTag(). Calls to getArch() will need to be changed to getCpuArch(). Usage of Target.Cross and Target.Native need to be updated to use CrossTarget API. `std.build.Builder.standardTargetOptions` is changed to accept its parameters as a struct with default values. It now has the ability to specify a whitelist of targets allowed, as well as the default target. Rather than two different ways of collecting the target, it's now always a string that is validated, and prints helpful diagnostics for invalid targets. This feature should now be actually useful, and contributions welcome to further improve the user experience. `std.build.LibExeObjStep.setTheTarget` is removed. `std.build.LibExeObjStep.setTarget` is updated to take a CrossTarget parameter. `std.build.LibExeObjStep.setTargetGLibC` is removed. glibc versions are handled in the CrossTarget API and can be specified with the `-target` triple. `std.builtin.Version` gains a `format` method.
1 parent 87b9e74
doc/docgen.zig
@@ -10,8 +10,8 @@ const testing = std.testing;
 
 const max_doc_file_size = 10 * 1024 * 1024;
 
-const exe_ext = @as(std.build.Target, std.build.Target.Native).exeFileExt();
-const obj_ext = @as(std.build.Target, std.build.Target.Native).oFileExt();
+const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt();
+const obj_ext = @as(std.zig.CrossTarget, .{}).oFileExt();
 const tmp_dir_name = "docgen_tmp";
 const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext;
 
lib/std/build/translate_c.zig
@@ -7,6 +7,7 @@ const LibExeObjStep = build.LibExeObjStep;
 const CheckFileStep = build.CheckFileStep;
 const fs = std.fs;
 const mem = std.mem;
+const CrossTarget = std.zig.CrossTarget;
 
 pub const TranslateCStep = struct {
     step: Step,
@@ -14,7 +15,7 @@ pub const TranslateCStep = struct {
     source: build.FileSource,
     output_dir: ?[]const u8,
     out_basename: []const u8,
-    target: build.Target = .Native,
+    target: CrossTarget = CrossTarget{},
 
     pub fn create(builder: *Builder, source: build.FileSource) *TranslateCStep {
         const self = builder.allocator.create(TranslateCStep) catch unreachable;
@@ -39,7 +40,7 @@ pub const TranslateCStep = struct {
         ) catch unreachable;
     }
 
-    pub fn setTarget(self: *TranslateCStep, target: build.Target) void {
+    pub fn setTarget(self: *TranslateCStep, target: CrossTarget) void {
         self.target = target;
     }
 
@@ -63,12 +64,9 @@ pub const TranslateCStep = struct {
         try argv_list.append("--cache");
         try argv_list.append("on");
 
-        switch (self.target) {
-            .Native => {},
-            .Cross => {
-                try argv_list.append("-target");
-                try argv_list.append(try self.target.zigTriple(self.builder.allocator));
-            },
+        if (!self.target.isNative()) {
+            try argv_list.append("-target");
+            try argv_list.append(try self.target.zigTriple(self.builder.allocator));
         }
 
         try argv_list.append(self.source.getPath(self.builder));
lib/std/zig/cross_target.zig
@@ -0,0 +1,766 @@
+const std = @import("../std.zig");
+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.
+pub const CrossTarget = struct {
+    /// `null` means native.
+    cpu_arch: ?Target.Cpu.Arch = null,
+
+    /// If `cpu_arch` is native, `null` means native. Otherwise it means baseline.
+    /// 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.
+    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 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,
+
+    pub const OsVersion = union(enum) {
+        none: void,
+        semver: SemVer,
+        windows: Target.Os.WindowsVersion,
+    };
+
+    pub const SemVer = std.builtin.Version;
+
+    pub fn fromTarget(target: Target) CrossTarget {
+        var result: CrossTarget = .{
+            .cpu_arch = target.cpu.arch,
+            .cpu_model = 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,
+            .dragonfly,
+            .fuchsia,
+            .ios,
+            .kfreebsd,
+            .lv2,
+            .solaris,
+            .haiku,
+            .minix,
+            .rtems,
+            .nacl,
+            .cnk,
+            .aix,
+            .cuda,
+            .nvcl,
+            .amdhsa,
+            .ps4,
+            .elfiamcu,
+            .tvos,
+            .watchos,
+            .mesa3d,
+            .contiki,
+            .amdpal,
+            .hermit,
+            .hurd,
+            .wasi,
+            .emscripten,
+            .uefi,
+            .other,
+            => {
+                self.os_version_min = .{ .none = {} };
+                self.os_version_max = .{ .none = {} };
+            },
+
+            .freebsd,
+            .macosx,
+            .netbsd,
+            .openbsd,
+            => {
+                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 };
+            },
+        }
+    }
+
+    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,
+
+        /// 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 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;
+
+        // Start with everything initialized to default values.
+        var result: CrossTarget = .{};
+
+        var it = mem.separate(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.separate(abi_text, ".");
+            const abi = std.meta.stringToEnum(Target.Abi, abi_it.next().?) orelse
+                return error.UnknownApplicationBinaryInterface;
+            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 = null;
+            } else if (mem.eql(u8, cpu_name, "baseline")) {
+                result.cpu_model = Target.Cpu.Model.baseline(arch);
+            } else {
+                result.cpu_model = 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;
+    }
+
+    pub fn getCpu(self: CrossTarget) Target.Cpu {
+        if (self.cpu_arch) |arch| {
+            if (self.cpu_model) |model| {
+                var adjusted_model = model.toCpu(arch);
+                self.updateCpuFeatures(&adjusted_model.features);
+                return adjusted_model;
+            } else {
+                var adjusted_baseline = Target.Cpu.baseline(arch);
+                self.updateCpuFeatures(&adjusted_baseline.features);
+                return adjusted_baseline;
+            }
+        } else {
+            assert(self.cpu_model == null);
+            assert(self.cpu_features_sub.isEmpty());
+            assert(self.cpu_features_add.isEmpty());
+            // 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 Target.current.cpu;
+        }
+    }
+
+    pub fn getCpuArch(self: CrossTarget) Target.Cpu.Arch {
+        return self.cpu_arch orelse Target.current.cpu.arch;
+    }
+
+    pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model {
+        if (self.cpu_model) |cpu_model| return cpu_model;
+        return self.getCpu().model;
+    }
+
+    pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set {
+        return self.getCpu().features;
+    }
+
+    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
+        // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
+        var adjusted_os = if (self.os_tag) |os_tag| Target.Os.defaultVersionRange(os_tag) else Target.current.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 Target.current.os.tag;
+    }
+
+    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.?;
+    }
+
+    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.?;
+    }
+
+    pub fn getAbi(self: CrossTarget) Target.Abi {
+        if (self.abi) |abi| return abi;
+
+        if (self.isNativeOs()) {
+            // 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 Target.current.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 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 oFileExt(self: CrossTarget) [:0]const u8 {
+        return self.getAbi().oFileExt();
+    }
+
+    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_cpu_arch_abi(self.getCpuArch(), 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_cpu_arch_abi(self.getCpuArch(), self.getAbi());
+    }
+
+    pub fn isNativeCpu(self: CrossTarget) bool {
+        return self.cpu_arch == null and self.cpu_model == null 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;
+    }
+
+    pub fn isNativeAbi(self: CrossTarget) bool {
+        return self.abi == null and self.glibc_version == 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}![:0]u8 {
+        if (self.isNative()) {
+            return mem.dupeZ(allocator, u8, "native");
+        }
+
+        const arch_name = if (self.isNativeCpu()) "native" else @tagName(self.getCpuArch());
+        const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native";
+
+        var result = try std.Buffer.allocPrint(allocator, "{}-{}", .{ arch_name, os_name });
+        defer result.deinit();
+
+        // 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.print(".{}", .{v}),
+                .windows => |v| try result.print(".{}", .{@tagName(v)}),
+            }
+        }
+        if (self.os_version_max) |max| {
+            switch (max) {
+                .none => {},
+                .semver => |v| try result.print("...{}", .{v}),
+                .windows => |v| try result.print("...{}", .{@tagName(v)}),
+            }
+        }
+
+        if (self.abi) |abi| {
+            try result.print("-{}", .{@tagName(abi)});
+            if (self.glibc_version) |v| {
+                try result.print(".{}", .{v});
+            }
+        } else {
+            assert(self.glibc_version == null);
+        }
+
+        return result.toOwnedSlice();
+    }
+
+    pub fn allocDescription(self: CrossTarget, allocator: *mem.Allocator) ![:0]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) ![:0]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) ![:0]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",
+            .macosx => "macos",
+            else => return error.UnsupportedVcpkgOperatingSystem,
+        };
+
+        const static_suffix = switch (linkage) {
+            .Static => "-static",
+            .Dynamic => "",
+        };
+
+        return std.fmt.allocPrint0(allocator, "{}-{}{}", .{ arch, os, static_suffix });
+    }
+
+    pub const Executor = union(enum) {
+        native,
+        qemu: []const u8,
+        wine: []const u8,
+        wasmtime: []const u8,
+        unavailable,
+    };
+
+    pub fn getExternalExecutor(self: CrossTarget) Executor {
+        const os_tag = self.getOsTag();
+        const cpu_arch = self.getCpuArch();
+
+        // If the target OS matches the host OS, we can use QEMU to emulate a foreign architecture.
+        if (os_tag == Target.current.os.tag) {
+            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,
+            },
+            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 };
+    }
+
+    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.separate(text, ".");
+        const os_name = it.next().?;
+        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,
+            .dragonfly,
+            .fuchsia,
+            .ios,
+            .kfreebsd,
+            .lv2,
+            .solaris,
+            .haiku,
+            .minix,
+            .rtems,
+            .nacl,
+            .cnk,
+            .aix,
+            .cuda,
+            .nvcl,
+            .amdhsa,
+            .ps4,
+            .elfiamcu,
+            .tvos,
+            .watchos,
+            .mesa3d,
+            .contiki,
+            .amdpal,
+            .hermit,
+            .hurd,
+            .wasi,
+            .emscripten,
+            .uefi,
+            .other,
+            => return error.InvalidOperatingSystemVersion,
+
+            .freebsd,
+            .macosx,
+            .netbsd,
+            .openbsd,
+            .linux,
+            => {
+                var range_it = mem.separate(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.separate(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" {
+    {
+        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();
+
+        std.testing.expect(target.os.tag == .linux);
+        std.testing.expect(target.abi == .gnu);
+        std.testing.expect(target.cpu.arch == .x86_64);
+        std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse));
+        std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx));
+        std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8));
+        std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov));
+        std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr));
+
+        const text = try cross_target.zigTriple(std.testing.allocator);
+        defer std.testing.allocator.free(text);
+        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();
+
+        std.testing.expect(target.os.tag == .linux);
+        std.testing.expect(target.abi == .musleabihf);
+        std.testing.expect(target.cpu.arch == .arm);
+        std.testing.expect(target.cpu.model == &Target.arm.cpu.generic);
+        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);
+        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();
+
+        std.testing.expect(target.cpu.arch == .aarch64);
+        std.testing.expect(target.os.tag == .linux);
+        std.testing.expect(target.os.version_range.linux.range.min.major == 3);
+        std.testing.expect(target.os.version_range.linux.range.min.minor == 10);
+        std.testing.expect(target.os.version_range.linux.range.min.patch == 0);
+        std.testing.expect(target.os.version_range.linux.range.max.major == 4);
+        std.testing.expect(target.os.version_range.linux.range.max.minor == 4);
+        std.testing.expect(target.os.version_range.linux.range.max.patch == 1);
+        std.testing.expect(target.os.version_range.linux.glibc.major == 2);
+        std.testing.expect(target.os.version_range.linux.glibc.minor == 27);
+        std.testing.expect(target.os.version_range.linux.glibc.patch == 0);
+        std.testing.expect(target.abi == .gnu);
+
+        const text = try cross_target.zigTriple(std.testing.allocator);
+        defer std.testing.allocator.free(text);
+        std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text);
+    }
+}
lib/std/build.zig
@@ -1,5 +1,5 @@
 const std = @import("std.zig");
-const builtin = @import("builtin");
+const builtin = std.builtin;
 const io = std.io;
 const fs = std.fs;
 const mem = std.mem;
@@ -15,6 +15,7 @@ const BufSet = std.BufSet;
 const BufMap = std.BufMap;
 const fmt_lib = std.fmt;
 const File = std.fs.File;
+const CrossTarget = std.zig.CrossTarget;
 
 pub const FmtStep = @import("build/fmt.zig").FmtStep;
 pub const TranslateCStep = @import("build/translate_c.zig").TranslateCStep;
@@ -521,24 +522,77 @@ pub const Builder = struct {
         return mode;
     }
 
-    /// Exposes standard `zig build` options for choosing a target. Pass `null` to support all targets.
-    pub fn standardTargetOptions(self: *Builder, supported_targets: ?[]const Target) Target {
-        if (supported_targets) |target_list| {
-            // TODO detect multiple args and emit an error message
-            // there's probably a better way to collect the target
-            for (target_list) |targ| {
-                const targ_str = targ.zigTriple(self.allocator) catch unreachable;
-                const targ_desc = targ.allocDescription(self.allocator) catch unreachable;
-                const this_targ_opt = self.option(bool, targ_str, targ_desc) orelse false;
-                if (this_targ_opt) {
-                    return targ;
+    pub const StandardTargetOptionsArgs = struct {
+        whitelist: ?[]const CrossTarget = null,
+
+        default_target: CrossTarget = .{},
+    };
+
+    /// Exposes standard `zig build` options for choosing a target.
+    pub fn standardTargetOptions(self: *Builder, args: StandardTargetOptionsArgs) CrossTarget {
+        const triple = self.option(
+            []const u8,
+            "target",
+            "The Arch, OS, and ABI to build for.",
+        ) orelse return args.default_target;
+
+        // TODO add cpu and features as part of the target triple
+
+        var diags: std.Target.ParseOptions.Diagnostics = .{};
+        const selected_target = CrossTarget.parse(.{
+            .arch_os_abi = triple,
+            .diagnostics = &diags,
+        }) catch |err| switch (err) {
+            error.UnknownCpuModel => {
+                std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
+                    diags.cpu_name.?,
+                    @tagName(diags.arch.?),
+                });
+                for (diags.arch.?.allCpuModels()) |cpu| {
+                    std.debug.warn(" {}\n", .{cpu.name});
+                }
+                process.exit(1);
+            },
+            error.UnknownCpuFeature => {
+                std.debug.warn(
+                    \\Unknown CPU feature: '{}'
+                    \\Available CPU features for architecture '{}':
+                    \\
+                , .{
+                    diags.unknown_feature_name,
+                    @tagName(diags.arch.?),
+                });
+                for (diags.arch.?.allFeaturesList()) |feature| {
+                    std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
+                }
+                process.exit(1);
+            },
+            else => |e| return e,
+        };
+
+        const selected_canonicalized_triple = selected_target.zigTriple(self.allocator) catch unreachable;
+
+        if (args.whitelist) |list| whitelist_check: {
+            // Make sure it's a match of one of the list.
+            for (list) |t| {
+                const t_triple = t.zigTriple(self.allocator) catch unreachable;
+                if (mem.eql(u8, t_triple, selected_canonicalized_triple)) {
+                    break :whitelist_check;
                 }
             }
-            return Target.Native;
-        } else {
-            const target_str = self.option([]const u8, "target", "the target to build for") orelse return Target.Native;
-            return Target.parse(.{ .arch_os_abi = target_str }) catch unreachable; // TODO better error message for bad target
+            std.debug.warn("Chosen target '{}' does not match one of the supported targets:\n", .{
+                selected_canonicalized_triple,
+            });
+            for (list) |t| {
+                const t_triple = t.zigTriple(self.allocator) catch unreachable;
+                std.debug.warn(" {}\n", t_triple);
+            }
+            // TODO instead of process exit, return error and have a zig build flag implemented by
+            // the build runner that turns process exits into error return traces
+            process.exit(1);
         }
+
+        return selected_target;
     }
 
     pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool {
@@ -796,7 +850,7 @@ pub const Builder = struct {
 
     pub fn findProgram(self: *Builder, names: []const []const u8, paths: []const []const u8) ![]const u8 {
         // TODO report error for ambiguous situations
-        const exe_extension = (Target{ .Native = {} }).exeFileExt();
+        const exe_extension = @as(CrossTarget, .{}).exeFileExt();
         for (self.search_prefixes.toSliceConst()) |search_prefix| {
             for (names) |name| {
                 if (fs.path.isAbsolute(name)) {
@@ -978,111 +1032,11 @@ test "builder.findProgram compiles" {
     _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null;
 }
 
-/// Deprecated. Use `builtin.Version`.
+/// Deprecated. Use `std.builtin.Version`.
 pub const Version = builtin.Version;
 
-/// Deprecated. Use `std.Target`.
-pub const CrossTarget = std.Target;
-
-/// Wraps `std.Target` so that it can be annotated as "the native target" or an explicitly specified target.
-pub const Target = union(enum) {
-    Native,
-    Cross: std.Target,
-
-    pub fn getTarget(self: Target) std.Target {
-        return switch (self) {
-            .Native => std.Target.current,
-            .Cross => |t| t,
-        };
-    }
-
-    pub fn getOs(self: Target) std.Target.Os.Tag {
-        return self.getTarget().os.tag;
-    }
-
-    pub fn getCpu(self: Target) std.Target.Cpu {
-        return self.getTarget().cpu;
-    }
-
-    pub fn getAbi(self: Target) std.Target.Abi {
-        return self.getTarget().abi;
-    }
-
-    pub fn getArch(self: Target) std.Target.Cpu.Arch {
-        return self.getCpu().arch;
-    }
-
-    pub fn isFreeBSD(self: Target) bool {
-        return self.getTarget().os.tag == .freebsd;
-    }
-
-    pub fn isDarwin(self: Target) bool {
-        return self.getTarget().os.tag.isDarwin();
-    }
-
-    pub fn isNetBSD(self: Target) bool {
-        return self.getTarget().os.tag == .netbsd;
-    }
-
-    pub fn isUefi(self: Target) bool {
-        return self.getTarget().os.tag == .uefi;
-    }
-
-    pub fn isDragonFlyBSD(self: Target) bool {
-        return self.getTarget().os.tag == .dragonfly;
-    }
-
-    pub fn isLinux(self: Target) bool {
-        return self.getTarget().os.tag == .linux;
-    }
-
-    pub fn isWindows(self: Target) bool {
-        return self.getTarget().os.tag == .windows;
-    }
-
-    pub fn oFileExt(self: Target) []const u8 {
-        return self.getTarget().oFileExt();
-    }
-
-    pub fn exeFileExt(self: Target) []const u8 {
-        return self.getTarget().exeFileExt();
-    }
-
-    pub fn staticLibSuffix(self: Target) []const u8 {
-        return self.getTarget().staticLibSuffix();
-    }
-
-    pub fn libPrefix(self: Target) []const u8 {
-        return self.getTarget().libPrefix();
-    }
-
-    pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
-        return self.getTarget().zigTriple(allocator);
-    }
-
-    pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
-        return self.getTarget().linuxTriple(allocator);
-    }
-
-    pub fn wantSharedLibSymLinks(self: Target) bool {
-        return self.getTarget().wantSharedLibSymLinks();
-    }
-
-    pub fn vcpkgTriplet(self: Target, allocator: *mem.Allocator, linkage: std.build.VcpkgLinkage) ![]const u8 {
-        return self.getTarget().vcpkgTriplet(allocator, linkage);
-    }
-
-    pub fn getExternalExecutor(self: Target) std.Target.Executor {
-        switch (self) {
-            .Native => return .native,
-            .Cross => |t| return t.getExternalExecutor(),
-        }
-    }
-
-    pub fn isGnuLibC(self: Target) bool {
-        return self.getTarget().isGnuLibC();
-    }
-};
+/// Deprecated. Use `std.zig.CrossTarget`.
+pub const Target = std.zig.CrossTarget;
 
 pub const Pkg = struct {
     name: []const u8,
@@ -1135,7 +1089,7 @@ pub const LibExeObjStep = struct {
     step: Step,
     builder: *Builder,
     name: []const u8,
-    target: Target,
+    target: CrossTarget = CrossTarget{},
     linker_script: ?[]const u8 = null,
     version_script: ?[]const u8 = null,
     out_filename: []const u8,
@@ -1188,7 +1142,6 @@ pub const LibExeObjStep = struct {
     install_step: ?*InstallArtifactStep,
 
     libc_file: ?[]const u8 = null,
-    target_glibc: ?Version = null,
 
     valgrind_support: ?bool = null,
 
@@ -1288,7 +1241,6 @@ pub const LibExeObjStep = struct {
             .kind = kind,
             .root_src = root_src,
             .name = name,
-            .target = Target.Native,
             .frameworks = BufSet.init(builder.allocator),
             .step = Step.init(name, builder.allocator, make),
             .version = ver,
@@ -1379,36 +1331,11 @@ pub const LibExeObjStep = struct {
         }
     }
 
-    /// Deprecated. Use `setTheTarget`.
-    pub fn setTarget(
-        self: *LibExeObjStep,
-        target_arch: builtin.Arch,
-        target_os: builtin.Os,
-        target_abi: builtin.Abi,
-    ) void {
-        return self.setTheTarget(Target{
-            .Cross = CrossTarget{
-                .arch = target_arch,
-                .os = target_os,
-                .abi = target_abi,
-                .cpu_features = target_arch.getBaselineCpuFeatures(),
-            },
-        });
-    }
-
-    pub fn setTheTarget(self: *LibExeObjStep, target: Target) void {
+    pub fn setTarget(self: *LibExeObjStep, target: CrossTarget) void {
         self.target = target;
         self.computeOutFileNames();
     }
 
-    pub fn setTargetGLibC(self: *LibExeObjStep, major: u32, minor: u32, patch: u32) void {
-        self.target_glibc = Version{
-            .major = major,
-            .minor = minor,
-            .patch = patch,
-        };
-    }
-
     pub fn setOutputDir(self: *LibExeObjStep, dir: []const u8) void {
         self.output_dir = self.builder.dupePath(dir);
     }
@@ -2002,47 +1929,41 @@ pub const LibExeObjStep = struct {
             try zig_args.append(@tagName(self.code_model));
         }
 
-        switch (self.target) {
-            .Native => {},
-            .Cross => |cross| {
-                try zig_args.append("-target");
-                try zig_args.append(self.target.zigTriple(builder.allocator) catch unreachable);
+        if (!self.target.isNative()) {
+            try zig_args.append("-target");
+            try zig_args.append(try self.target.zigTriple(builder.allocator));
 
-                const all_features = self.target.getArch().allFeaturesList();
-                var populated_cpu_features = cross.cpu.model.features;
-                populated_cpu_features.populateDependencies(all_features);
+            // TODO this logic can disappear if cpu model + features becomes part of the target triple
+            const cross = self.target.toTarget();
+            const all_features = cross.cpu.arch.allFeaturesList();
+            var populated_cpu_features = cross.cpu.model.features;
+            populated_cpu_features.populateDependencies(all_features);
 
-                if (populated_cpu_features.eql(cross.cpu.features)) {
-                    // The CPU name alone is sufficient.
-                    // If it is the baseline CPU, no command line args are required.
-                    if (cross.cpu.model != std.Target.Cpu.baseline(self.target.getArch()).model) {
-                        try zig_args.append("-mcpu");
-                        try zig_args.append(cross.cpu.model.name);
-                    }
-                } else {
-                    var mcpu_buffer = try std.Buffer.init(builder.allocator, "-mcpu=");
-                    try mcpu_buffer.append(cross.cpu.model.name);
-
-                    for (all_features) |feature, i_usize| {
-                        const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize);
-                        const in_cpu_set = populated_cpu_features.isEnabled(i);
-                        const in_actual_set = cross.cpu.features.isEnabled(i);
-                        if (in_cpu_set and !in_actual_set) {
-                            try mcpu_buffer.appendByte('-');
-                            try mcpu_buffer.append(feature.name);
-                        } else if (!in_cpu_set and in_actual_set) {
-                            try mcpu_buffer.appendByte('+');
-                            try mcpu_buffer.append(feature.name);
-                        }
+            if (populated_cpu_features.eql(cross.cpu.features)) {
+                // The CPU name alone is sufficient.
+                // If it is the baseline CPU, no command line args are required.
+                if (cross.cpu.model != std.Target.Cpu.baseline(cross.cpu.arch).model) {
+                    try zig_args.append("-mcpu");
+                    try zig_args.append(cross.cpu.model.name);
+                }
+            } else {
+                var mcpu_buffer = try std.Buffer.init(builder.allocator, "-mcpu=");
+                try mcpu_buffer.append(cross.cpu.model.name);
+
+                for (all_features) |feature, i_usize| {
+                    const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize);
+                    const in_cpu_set = populated_cpu_features.isEnabled(i);
+                    const in_actual_set = cross.cpu.features.isEnabled(i);
+                    if (in_cpu_set and !in_actual_set) {
+                        try mcpu_buffer.appendByte('-');
+                        try mcpu_buffer.append(feature.name);
+                    } else if (!in_cpu_set and in_actual_set) {
+                        try mcpu_buffer.appendByte('+');
+                        try mcpu_buffer.append(feature.name);
                     }
-                    try zig_args.append(mcpu_buffer.toSliceConst());
                 }
-            },
-        }
-
-        if (self.target_glibc) |ver| {
-            try zig_args.append("-target-glibc");
-            try zig_args.append(builder.fmt("{}.{}.{}", .{ ver.major, ver.minor, ver.patch }));
+                try zig_args.append(mcpu_buffer.toSliceConst());
+            }
         }
 
         if (self.linker_script) |linker_script| {
@@ -2517,10 +2438,7 @@ const VcpkgRootStatus = enum {
     Found,
 };
 
-pub const VcpkgLinkage = enum {
-    Static,
-    Dynamic,
-};
+pub const VcpkgLinkage = std.builtin.LinkMode;
 
 pub const InstallDir = enum {
     Prefix,
lib/std/builtin.zig
@@ -429,6 +429,29 @@ pub const Version = struct {
             .patch = try std.fmt.parseInt(u32, it.next() orelse "0", 10),
         };
     }
+
+    pub fn format(
+        self: Version,
+        comptime fmt: []const u8,
+        options: std.fmt.FormatOptions,
+        context: var,
+        comptime Error: type,
+        comptime output: fn (@TypeOf(context), []const u8) Error!void,
+    ) Error!void {
+        if (fmt.len == 0) {
+            if (self.patch == 0) {
+                if (self.minor == 0) {
+                    return std.fmt.format(context, Error, output, "{}", .{self.major});
+                } else {
+                    return std.fmt.format(context, Error, output, "{}.{}", .{ self.major, self.minor });
+                }
+            } else {
+                return std.fmt.format(context, Error, output, "{}.{}.{}", .{ self.major, self.minor, self.patch });
+            }
+        } else {
+            @compileError("Unknown format string: '" ++ fmt ++ "'");
+        }
+    }
 };
 
 /// This data structure is used by the Zig language code generation and
lib/std/target.zig
@@ -60,6 +60,16 @@ pub const Target = struct {
                     else => false,
                 };
             }
+
+            pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 {
+                if (tag.isDarwin()) {
+                    return ".dylib";
+                }
+                switch (tag) {
+                    .windows => return ".dll",
+                    else => return ".so",
+                }
+            }
         };
 
         /// Based on NTDDI version constants from
@@ -210,64 +220,31 @@ pub const Target = struct {
             }
         };
 
-        pub fn parse(text: []const u8) !Os {
-            var it = mem.separate(text, ".");
-            const os_name = it.next().?;
-            const tag = std.meta.stringToEnum(Tag, os_name) orelse return error.UnknownOperatingSystem;
-            const version_text = it.rest();
-            const S = struct {
-                fn parseNone(s: []const u8) !void {
-                    if (s.len != 0) return error.InvalidOperatingSystemVersion;
-                }
-                fn parseSemVer(s: []const u8, d_range: Version.Range) !Version.Range {
-                    if (s.len == 0) return d_range;
-                    var range_it = mem.separate(s, "...");
-
-                    const min_text = range_it.next().?;
-                    const min_ver = Version.parse(min_text) catch |err| switch (err) {
-                        error.Overflow => return error.InvalidOperatingSystemVersion,
-                        error.InvalidCharacter => return error.InvalidOperatingSystemVersion,
-                        error.InvalidVersion => return error.InvalidOperatingSystemVersion,
-                    };
-
-                    const max_text = range_it.next() orelse return Version.Range{
-                        .min = min_ver,
-                        .max = d_range.max,
-                    };
-                    const max_ver = Version.parse(max_text) catch |err| switch (err) {
-                        error.Overflow => return error.InvalidOperatingSystemVersion,
-                        error.InvalidCharacter => return error.InvalidOperatingSystemVersion,
-                        error.InvalidVersion => return error.InvalidOperatingSystemVersion,
-                    };
-
-                    return Version.Range{ .min = min_ver, .max = max_ver };
-                }
-                fn parseWindows(s: []const u8, d_range: WindowsVersion.Range) !WindowsVersion.Range {
-                    if (s.len == 0) return d_range;
-                    var range_it = mem.separate(s, "...");
-
-                    const min_text = range_it.next().?;
-                    const min_ver = std.meta.stringToEnum(WindowsVersion, min_text) orelse
-                        return error.InvalidOperatingSystemVersion;
+        pub fn defaultVersionRange(tag: Tag) Os {
+            return .{
+                .tag = tag,
+                .version_range = VersionRange.default(tag),
+            };
+        }
 
-                    const max_text = range_it.next() orelse return WindowsVersion.Range{
-                        .min = min_ver,
-                        .max = d_range.max,
-                    };
-                    const max_ver = std.meta.stringToEnum(WindowsVersion, max_text) orelse
-                        return error.InvalidOperatingSystemVersion;
+        pub fn requiresLibC(os: Os) bool {
+            return switch (os.tag) {
+                .freebsd,
+                .netbsd,
+                .macosx,
+                .ios,
+                .tvos,
+                .watchos,
+                .dragonfly,
+                .openbsd,
+                => true,
 
-                    return WindowsVersion.Range{ .min = min_ver, .max = max_ver };
-                }
-            };
-            const d_range = VersionRange.default(tag);
-            switch (tag) {
+                .linux,
+                .windows,
                 .freestanding,
                 .ananas,
                 .cloudabi,
-                .dragonfly,
                 .fuchsia,
-                .ios,
                 .kfreebsd,
                 .lv2,
                 .solaris,
@@ -282,8 +259,6 @@ pub const Target = struct {
                 .amdhsa,
                 .ps4,
                 .elfiamcu,
-                .tvos,
-                .watchos,
                 .mesa3d,
                 .contiki,
                 .amdpal,
@@ -293,41 +268,7 @@ pub const Target = struct {
                 .emscripten,
                 .uefi,
                 .other,
-                => return Os{
-                    .tag = tag,
-                    .version_range = .{ .none = try S.parseNone(version_text) },
-                },
-
-                .freebsd,
-                .macosx,
-                .netbsd,
-                .openbsd,
-                => return Os{
-                    .tag = tag,
-                    .version_range = .{ .semver = try S.parseSemVer(version_text, d_range.semver) },
-                },
-
-                .linux => return Os{
-                    .tag = tag,
-                    .version_range = .{
-                        .linux = .{
-                            .range = try S.parseSemVer(version_text, d_range.linux.range),
-                            .glibc = d_range.linux.glibc,
-                        },
-                    },
-                },
-
-                .windows => return Os{
-                    .tag = tag,
-                    .version_range = .{ .windows = try S.parseWindows(version_text, d_range.windows) },
-                },
-            }
-        }
-
-        pub fn defaultVersionRange(tag: Tag) Os {
-            return .{
-                .tag = tag,
-                .version_range = VersionRange.default(tag),
+                => false,
             };
         }
     };
@@ -434,6 +375,13 @@ pub const Target = struct {
                 else => false,
             };
         }
+
+        pub fn oFileExt(abi: Abi) [:0]const u8 {
+            return switch (abi) {
+                .msvc => ".obj",
+                else => ".o",
+            };
+        }
     };
 
     pub const ObjectFormat = enum {
@@ -500,6 +448,12 @@ pub const Target = struct {
                     return Set{ .ints = [1]usize{0} ** usize_count };
                 }
 
+                pub fn isEmpty(set: Set) bool {
+                    return for (set.ints) |x| {
+                        if (x != 0) break false;
+                    } else true;
+                }
+
                 pub fn isEnabled(set: Set, arch_feature_index: Index) bool {
                     const usize_index = arch_feature_index / @bitSizeOf(usize);
                     const bit_index = @intCast(ShiftInt, arch_feature_index % @bitSizeOf(usize));
@@ -526,6 +480,15 @@ pub const Target = struct {
                     set.ints[usize_index] &= ~(@as(usize, 1) << bit_index);
                 }
 
+                /// Removes the specified feature but not its dependents.
+                pub fn removeFeatureSet(set: *Set, other_set: Set) void {
+                    // TODO should be able to use binary not on @Vector type.
+                    // https://github.com/ziglang/zig/issues/903
+                    for (set.ints) |*int, i| {
+                        int.* &= ~other_set.ints[i];
+                    }
+                }
+
                 pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {
                     @setEvalBranchQuota(1000000);
 
@@ -663,7 +626,7 @@ pub const Target = struct {
                         return cpu;
                     }
                 }
-                return error.UnknownCpu;
+                return error.UnknownCpuModel;
             }
 
             pub fn toElfMachine(arch: Arch) std.elf.EM {
@@ -779,6 +742,66 @@ pub const Target = struct {
                 };
             }
 
+            pub fn ptrBitWidth(arch: Arch) u32 {
+                switch (arch) {
+                    .avr,
+                    .msp430,
+                    => return 16,
+
+                    .arc,
+                    .arm,
+                    .armeb,
+                    .hexagon,
+                    .le32,
+                    .mips,
+                    .mipsel,
+                    .powerpc,
+                    .r600,
+                    .riscv32,
+                    .sparc,
+                    .sparcel,
+                    .tce,
+                    .tcele,
+                    .thumb,
+                    .thumbeb,
+                    .i386,
+                    .xcore,
+                    .nvptx,
+                    .amdil,
+                    .hsail,
+                    .spir,
+                    .kalimba,
+                    .shave,
+                    .lanai,
+                    .wasm32,
+                    .renderscript32,
+                    .aarch64_32,
+                    => return 32,
+
+                    .aarch64,
+                    .aarch64_be,
+                    .mips64,
+                    .mips64el,
+                    .powerpc64,
+                    .powerpc64le,
+                    .riscv64,
+                    .x86_64,
+                    .nvptx64,
+                    .le64,
+                    .amdil64,
+                    .hsail64,
+                    .spir64,
+                    .wasm64,
+                    .renderscript64,
+                    .amdgcn,
+                    .bpfel,
+                    .bpfeb,
+                    .sparcv9,
+                    .s390x,
+                    => return 64,
+                }
+            }
+
             /// Returns a name that matches the lib/std/target/* directory name.
             pub fn genericName(arch: Arch) []const u8 {
                 return switch (arch) {
@@ -846,16 +869,6 @@ pub const Target = struct {
                     else => &[0]*const Model{},
                 };
             }
-
-            pub fn parse(text: []const u8) !Arch {
-                const info = @typeInfo(Arch);
-                inline for (info.Enum.fields) |field| {
-                    if (mem.eql(u8, text, field.name)) {
-                        return @as(Arch, @field(Arch, field.name));
-                    }
-                }
-                return error.UnknownArchitecture;
-            }
         };
 
         pub const Model = struct {
@@ -872,41 +885,44 @@ pub const Target = struct {
                     .features = features,
                 };
             }
+
+            pub fn baseline(arch: Arch) *const Model {
+                const S = struct {
+                    const generic_model = Model{
+                        .name = "generic",
+                        .llvm_name = null,
+                        .features = Cpu.Feature.Set.empty,
+                    };
+                };
+                return switch (arch) {
+                    .arm, .armeb, .thumb, .thumbeb => &arm.cpu.baseline,
+                    .aarch64, .aarch64_be, .aarch64_32 => &aarch64.cpu.generic,
+                    .avr => &avr.cpu.avr1,
+                    .bpfel, .bpfeb => &bpf.cpu.generic,
+                    .hexagon => &hexagon.cpu.generic,
+                    .mips, .mipsel => &mips.cpu.mips32,
+                    .mips64, .mips64el => &mips.cpu.mips64,
+                    .msp430 => &msp430.cpu.generic,
+                    .powerpc, .powerpc64, .powerpc64le => &powerpc.cpu.generic,
+                    .amdgcn => &amdgpu.cpu.generic,
+                    .riscv32 => &riscv.cpu.baseline_rv32,
+                    .riscv64 => &riscv.cpu.baseline_rv64,
+                    .sparc, .sparcv9, .sparcel => &sparc.cpu.generic,
+                    .s390x => &systemz.cpu.generic,
+                    .i386 => &x86.cpu.pentium4,
+                    .x86_64 => &x86.cpu.x86_64,
+                    .nvptx, .nvptx64 => &nvptx.cpu.sm_20,
+                    .wasm32, .wasm64 => &wasm.cpu.generic,
+
+                    else => &S.generic_model,
+                };
+            }
         };
 
         /// The "default" set of CPU features for cross-compiling. A conservative set
         /// of features that is expected to be supported on most available hardware.
         pub fn baseline(arch: Arch) Cpu {
-            const S = struct {
-                const generic_model = Model{
-                    .name = "generic",
-                    .llvm_name = null,
-                    .features = Cpu.Feature.Set.empty,
-                };
-            };
-            const model = switch (arch) {
-                .arm, .armeb, .thumb, .thumbeb => &arm.cpu.baseline,
-                .aarch64, .aarch64_be, .aarch64_32 => &aarch64.cpu.generic,
-                .avr => &avr.cpu.avr1,
-                .bpfel, .bpfeb => &bpf.cpu.generic,
-                .hexagon => &hexagon.cpu.generic,
-                .mips, .mipsel => &mips.cpu.mips32,
-                .mips64, .mips64el => &mips.cpu.mips64,
-                .msp430 => &msp430.cpu.generic,
-                .powerpc, .powerpc64, .powerpc64le => &powerpc.cpu.generic,
-                .amdgcn => &amdgpu.cpu.generic,
-                .riscv32 => &riscv.cpu.baseline_rv32,
-                .riscv64 => &riscv.cpu.baseline_rv64,
-                .sparc, .sparcv9, .sparcel => &sparc.cpu.generic,
-                .s390x => &systemz.cpu.generic,
-                .i386 => &x86.cpu.pentium4,
-                .x86_64 => &x86.cpu.x86_64,
-                .nvptx, .nvptx64 => &nvptx.cpu.sm_20,
-                .wasm32, .wasm64 => &wasm.cpu.generic,
-
-                else => &S.generic_model,
-            };
-            return model.toCpu(arch);
+            return Model.baseline(arch).toCpu(arch);
         }
     };
 
@@ -918,239 +934,70 @@ pub const Target = struct {
 
     pub const stack_align = 16;
 
-    /// TODO add OS version ranges and glibc version
-    pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
-        return std.fmt.allocPrint(allocator, "{}-{}-{}", .{
-            @tagName(self.cpu.arch),
-            @tagName(self.os.tag),
-            @tagName(self.abi),
-        });
+    pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![:0]u8 {
+        return std.zig.CrossTarget.fromTarget(self).zigTriple(allocator);
     }
 
-    /// Returned slice must be freed by the caller.
-    pub fn vcpkgTriplet(target: Target, allocator: *mem.Allocator, linkage: std.build.VcpkgLinkage) ![]const u8 {
-        const arch = switch (target.cpu.arch) {
-            .i386 => "x86",
-            .x86_64 => "x64",
-
-            .arm,
-            .armeb,
-            .thumb,
-            .thumbeb,
-            .aarch64_32,
-            => "arm",
-
-            .aarch64,
-            .aarch64_be,
-            => "arm64",
-
-            else => return error.VcpkgNoSuchArchitecture,
-        };
-
-        const os = switch (target.os) {
-            .windows => "windows",
-            .linux => "linux",
-            .macosx => "macos",
-            else => return error.VcpkgNoSuchOs,
-        };
-
-        if (linkage == .Static) {
-            return try mem.join(allocator, "-", &[_][]const u8{ arch, os, "static" });
-        } else {
-            return try mem.join(allocator, "-", &[_][]const u8{ arch, os });
-        }
+    pub fn linuxTripleSimple(allocator: *mem.Allocator, cpu_arch: Cpu.Arch, os_tag: Os.Tag, abi: Abi) ![:0]u8 {
+        return std.fmt.allocPrint0(allocator, "{}-{}-{}", .{ @tagName(cpu_arch), @tagName(os_tag), @tagName(abi) });
     }
 
-    pub fn allocDescription(self: Target, 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: Target, allocator: *mem.Allocator) ![:0]u8 {
+        return linuxTripleSimple(allocator, self.cpu.arch, self.os.tag, self.abi);
     }
 
-    pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
-        return std.fmt.allocPrint(allocator, "{}-{}-{}", .{
-            @tagName(self.cpu.arch),
-            @tagName(self.os.tag),
-            @tagName(self.abi),
-        });
+    pub fn oFileExt(self: Target) [:0]const u8 {
+        return self.abi.oFileExt();
     }
 
-    pub const ParseOptions = struct {
-        /// This is sometimes called a "triple". It looks roughly like this:
-        ///     riscv64-linux-gnu
-        /// The fields are, respectively:
-        /// * CPU Architecture
-        /// * Operating System
-        /// * C ABI (optional)
-        arch_os_abi: []const u8,
-
-        /// 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.
-        cpu_features: []const u8 = "baseline",
-
-        /// 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: ?Cpu.Arch = null,
-
-            /// If the OS was determined, this will be populated.
-            os: ?Os = null,
-
-            /// If the ABI was determined, this will be populated.
-            abi: ?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) !Target {
-        var dummy_diags: ParseOptions.Diagnostics = undefined;
-        var diags = args.diagnostics orelse &dummy_diags;
-
-        var it = mem.separate(args.arch_os_abi, "-");
-        const arch_name = it.next() orelse return error.MissingArchitecture;
-        const arch = try Cpu.Arch.parse(arch_name);
-        diags.arch = arch;
-
-        const os_name = it.next() orelse return error.MissingOperatingSystem;
-        var os = try Os.parse(os_name);
-        diags.os = os;
-
-        const opt_abi_text = it.next();
-        const abi = if (opt_abi_text) |abi_text| blk: {
-            var abi_it = mem.separate(abi_text, ".");
-            const abi = std.meta.stringToEnum(Abi, abi_it.next().?) orelse
-                return error.UnknownApplicationBinaryInterface;
-            const abi_ver_text = abi_it.rest();
-            if (abi_ver_text.len != 0) {
-                if (os.tag == .linux and abi.isGnu()) {
-                    os.version_range.linux.glibc = Version.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;
-                }
-            }
-            break :blk abi;
-        } else Abi.default(arch, os);
-        diags.abi = abi;
-
-        if (it.next() != null) return error.UnexpectedExtraField;
-
-        const all_features = arch.allFeaturesList();
-        var index: usize = 0;
-        while (index < args.cpu_features.len and
-            args.cpu_features[index] != '+' and
-            args.cpu_features[index] != '-')
-        {
-            index += 1;
+    pub fn exeFileExtSimple(cpu_arch: Cpu.Arch, os_tag: Os.Tag) [:0]const u8 {
+        switch (os_tag) {
+            .windows => return ".exe",
+            .uefi => return ".efi",
+            else => if (cpu_arch.isWasm()) {
+                return ".wasm";
+            } else {
+                return "";
+            },
         }
-        const cpu_name = args.cpu_features[0..index];
-        diags.cpu_name = cpu_name;
-
-        const cpu: Cpu = if (mem.eql(u8, cpu_name, "baseline")) Cpu.baseline(arch) else blk: {
-            const cpu_model = try arch.parseCpuModel(cpu_name);
-
-            var set = cpu_model.features;
-            while (index < args.cpu_features.len) {
-                const op = args.cpu_features[index];
-                index += 1;
-                const start = index;
-                while (index < args.cpu_features.len and
-                    args.cpu_features[index] != '+' and
-                    args.cpu_features[index] != '-')
-                {
-                    index += 1;
-                }
-                const feature_name = args.cpu_features[start..index];
-                for (all_features) |feature, feat_index_usize| {
-                    const feat_index = @intCast(Cpu.Feature.Set.Index, feat_index_usize);
-                    if (mem.eql(u8, feature_name, feature.name)) {
-                        switch (op) {
-                            '+' => set.addFeature(feat_index),
-                            '-' => set.removeFeature(feat_index),
-                            else => unreachable,
-                        }
-                        break;
-                    }
-                } else {
-                    diags.unknown_feature_name = feature_name;
-                    return error.UnknownCpuFeature;
-                }
-            }
-            set.populateDependencies(all_features);
-            break :blk .{
-                .arch = arch,
-                .model = cpu_model,
-                .features = set,
-            };
-        };
-        return Target{
-            .cpu = cpu,
-            .os = os,
-            .abi = abi,
-        };
     }
 
-    pub fn oFileExt(self: Target) []const u8 {
-        return switch (self.abi) {
-            .msvc => ".obj",
-            else => ".o",
-        };
-    }
-
-    pub fn exeFileExt(self: Target) []const u8 {
-        if (self.os.tag == .windows) {
-            return ".exe";
-        } else if (self.os.tag == .uefi) {
-            return ".efi";
-        } else if (self.cpu.arch.isWasm()) {
-            return ".wasm";
-        } else {
-            return "";
-        }
+    pub fn exeFileExt(self: Target) [:0]const u8 {
+        return exeFileExtSimple(self.cpu.arch, self.os.tag);
     }
 
-    pub fn staticLibSuffix(self: Target) []const u8 {
-        if (self.cpu.arch.isWasm()) {
+    pub fn staticLibSuffix_cpu_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) [:0]const u8 {
+        if (cpu_arch.isWasm()) {
             return ".wasm";
         }
-        switch (self.abi) {
+        switch (abi) {
             .msvc => return ".lib",
             else => return ".a",
         }
     }
 
-    pub fn dynamicLibSuffix(self: Target) []const u8 {
-        if (self.isDarwin()) {
-            return ".dylib";
-        }
-        switch (self.os) {
-            .windows => return ".dll",
-            else => return ".so",
-        }
+    pub fn staticLibSuffix(self: Target) [:0]const u8 {
+        return staticLibSuffix_cpu_arch_abi(self.cpu.arch, self.abi);
     }
 
-    pub fn libPrefix(self: Target) []const u8 {
-        if (self.cpu.arch.isWasm()) {
+    pub fn dynamicLibSuffix(self: Target) [:0]const u8 {
+        return self.os.tag.dynamicLibSuffix();
+    }
+
+    pub fn libPrefix_cpu_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) [:0]const u8 {
+        if (cpu_arch.isWasm()) {
             return "";
         }
-        switch (self.abi) {
+        switch (abi) {
             .msvc => return "",
             else => return "lib",
         }
     }
 
+    pub fn libPrefix(self: Target) [:0]const u8 {
+        return libPrefix_cpu_arch_abi(self.cpu.arch, self.abi);
+    }
+
     pub fn getObjectFormat(self: Target) ObjectFormat {
         if (self.os.tag == .windows or self.os.tag == .uefi) {
             return .coff;
@@ -1190,129 +1037,18 @@ pub const Target = struct {
         return self.os.tag.isDarwin();
     }
 
-    pub fn isGnuLibC(self: Target) bool {
-        return self.os.tag == .linux and self.abi.isGnu();
+    pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool {
+        return os_tag == .linux and abi.isGnu();
     }
 
-    pub fn wantSharedLibSymLinks(self: Target) bool {
-        return self.os.tag != .windows;
-    }
-
-    pub fn osRequiresLibC(self: Target) bool {
-        return self.isDarwin() or self.os.tag == .freebsd or self.os.tag == .netbsd;
-    }
-
-    pub fn getArchPtrBitWidth(self: Target) u32 {
-        switch (self.cpu.arch) {
-            .avr,
-            .msp430,
-            => return 16,
-
-            .arc,
-            .arm,
-            .armeb,
-            .hexagon,
-            .le32,
-            .mips,
-            .mipsel,
-            .powerpc,
-            .r600,
-            .riscv32,
-            .sparc,
-            .sparcel,
-            .tce,
-            .tcele,
-            .thumb,
-            .thumbeb,
-            .i386,
-            .xcore,
-            .nvptx,
-            .amdil,
-            .hsail,
-            .spir,
-            .kalimba,
-            .shave,
-            .lanai,
-            .wasm32,
-            .renderscript32,
-            .aarch64_32,
-            => return 32,
-
-            .aarch64,
-            .aarch64_be,
-            .mips64,
-            .mips64el,
-            .powerpc64,
-            .powerpc64le,
-            .riscv64,
-            .x86_64,
-            .nvptx64,
-            .le64,
-            .amdil64,
-            .hsail64,
-            .spir64,
-            .wasm64,
-            .renderscript64,
-            .amdgcn,
-            .bpfel,
-            .bpfeb,
-            .sparcv9,
-            .s390x,
-            => return 64,
-        }
+    pub fn isGnuLibC(self: Target) bool {
+        return isGnuLibC_os_tag_abi(self.os.tag, self.abi);
     }
 
     pub fn supportsNewStackCall(self: Target) bool {
         return !self.cpu.arch.isWasm();
     }
 
-    pub const Executor = union(enum) {
-        native,
-        qemu: []const u8,
-        wine: []const u8,
-        wasmtime: []const u8,
-        unavailable,
-    };
-
-    pub fn getExternalExecutor(self: Target) Executor {
-        // If the target OS matches the host OS, we can use QEMU to emulate a foreign architecture.
-        if (self.os.tag == builtin.os.tag) {
-            return switch (self.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 (self.os.tag) {
-            .windows => switch (self.getArchPtrBitWidth()) {
-                32 => return Executor{ .wine = "wine" },
-                64 => return Executor{ .wine = "wine64" },
-                else => return .unavailable,
-            },
-            .wasi => switch (self.getArchPtrBitWidth()) {
-                32 => return Executor{ .wasmtime = "wasmtime" },
-                else => return .unavailable,
-            },
-            else => return .unavailable,
-        }
-    }
-
     pub const FloatAbi = enum {
         hard,
         soft,
@@ -1359,7 +1095,7 @@ pub const Target = struct {
     }![:0]u8 {
         const a = allocator;
         if (self.isAndroid()) {
-            return mem.dupeZ(a, u8, if (self.getArchPtrBitWidth() == 64)
+            return mem.dupeZ(a, u8, if (self.cpu.arch.ptrBitWidth() == 64)
                 "/system/bin/linker64"
             else
                 "/system/bin/linker");
@@ -1477,52 +1213,3 @@ pub const Target = struct {
         }
     }
 };
-
-test "Target.parse" {
-    {
-        const target = try Target.parse(.{
-            .arch_os_abi = "x86_64-linux-gnu",
-            .cpu_features = "x86_64-sse-sse2-avx-cx8",
-        });
-
-        std.testing.expect(target.os.tag == .linux);
-        std.testing.expect(target.abi == .gnu);
-        std.testing.expect(target.cpu.arch == .x86_64);
-        std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse));
-        std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx));
-        std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8));
-        std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov));
-        std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr));
-    }
-    {
-        const target = try Target.parse(.{
-            .arch_os_abi = "arm-linux-musleabihf",
-            .cpu_features = "generic+v8a",
-        });
-
-        std.testing.expect(target.os.tag == .linux);
-        std.testing.expect(target.abi == .musleabihf);
-        std.testing.expect(target.cpu.arch == .arm);
-        std.testing.expect(target.cpu.model == &Target.arm.cpu.generic);
-        std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a));
-    }
-    {
-        const target = try Target.parse(.{
-            .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
-            .cpu_features = "generic+v8a",
-        });
-
-        std.testing.expect(target.cpu.arch == .aarch64);
-        std.testing.expect(target.os.tag == .linux);
-        std.testing.expect(target.os.version_range.linux.range.min.major == 3);
-        std.testing.expect(target.os.version_range.linux.range.min.minor == 10);
-        std.testing.expect(target.os.version_range.linux.range.min.patch == 0);
-        std.testing.expect(target.os.version_range.linux.range.max.major == 4);
-        std.testing.expect(target.os.version_range.linux.range.max.minor == 4);
-        std.testing.expect(target.os.version_range.linux.range.max.patch == 1);
-        std.testing.expect(target.os.version_range.linux.glibc.major == 2);
-        std.testing.expect(target.os.version_range.linux.glibc.minor == 27);
-        std.testing.expect(target.os.version_range.linux.glibc.patch == 0);
-        std.testing.expect(target.abi == .gnu);
-    }
-}
lib/std/testing.zig
@@ -1,5 +1,3 @@
-const builtin = @import("builtin");
-const TypeId = builtin.TypeId;
 const std = @import("std.zig");
 
 pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator;
@@ -65,16 +63,16 @@ pub fn expectEqual(expected: var, actual: @TypeOf(expected)) void {
 
         .Pointer => |pointer| {
             switch (pointer.size) {
-                builtin.TypeInfo.Pointer.Size.One,
-                builtin.TypeInfo.Pointer.Size.Many,
-                builtin.TypeInfo.Pointer.Size.C,
+                .One,
+                .Many,
+                .C,
                 => {
                     if (actual != expected) {
                         std.debug.panic("expected {*}, found {*}", .{ expected, actual });
                     }
                 },
 
-                builtin.TypeInfo.Pointer.Size.Slice => {
+                .Slice => {
                     if (actual.ptr != expected.ptr) {
                         std.debug.panic("expected slice ptr {}, found {}", .{ expected.ptr, actual.ptr });
                     }
lib/std/zig.zig
@@ -6,11 +6,8 @@ pub const parseStringLiteral = @import("zig/parse_string_literal.zig").parseStri
 pub const render = @import("zig/render.zig").render;
 pub const ast = @import("zig/ast.zig");
 pub const system = @import("zig/system.zig");
+pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
 
-test "std.zig tests" {
-    _ = @import("zig/ast.zig");
-    _ = @import("zig/parse.zig");
-    _ = @import("zig/render.zig");
-    _ = @import("zig/tokenizer.zig");
-    _ = @import("zig/parse_string_literal.zig");
+test "" {
+    @import("std").meta.refAllDecls(@This());
 }
src-self-hosted/stage2.zig
@@ -10,6 +10,7 @@ const Allocator = mem.Allocator;
 const ArrayList = std.ArrayList;
 const Buffer = std.Buffer;
 const Target = std.Target;
+const CrossTarget = std.zig.CrossTarget;
 const self_hosted_main = @import("main.zig");
 const errmsg = @import("errmsg.zig");
 const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer;
@@ -87,7 +88,7 @@ const Error = extern enum {
     NotLazy,
     IsAsync,
     ImportOutsidePkgPath,
-    UnknownCpu,
+    UnknownCpuModel,
     UnknownCpuFeature,
     InvalidCpuFeatures,
     InvalidLlvmCpuFeaturesFormat,
@@ -634,13 +635,9 @@ export fn stage2_cmd_targets(zig_triple: [*:0]const u8) c_int {
 }
 
 fn cmdTargets(zig_triple: [*:0]const u8) !void {
-    var target = try Target.parse(.{ .arch_os_abi = mem.toSliceConst(u8, zig_triple) });
-    target.cpu = blk: {
-        const llvm = @import("llvm.zig");
-        const llvm_cpu_name = llvm.GetHostCPUName();
-        const llvm_cpu_features = llvm.GetNativeFeatures();
-        break :blk try detectNativeCpuWithLLVM(target.cpu.arch, llvm_cpu_name, llvm_cpu_features);
-    };
+    var cross_target = try CrossTarget.parse(.{ .arch_os_abi = mem.toSliceConst(u8, zig_triple) });
+    var dynamic_linker: ?[*:0]u8 = null;
+    const target = try crossTargetToTarget(cross_target, &dynamic_linker);
     return @import("print_targets.zig").cmdTargets(
         std.heap.c_allocator,
         &[0][]u8{},
@@ -661,7 +658,6 @@ export fn stage2_target_parse(
         error.UnknownOperatingSystem => return .UnknownOperatingSystem,
         error.UnknownApplicationBinaryInterface => return .UnknownApplicationBinaryInterface,
         error.MissingOperatingSystem => return .MissingOperatingSystem,
-        error.MissingArchitecture => return .MissingArchitecture,
         error.InvalidLlvmCpuFeaturesFormat => return .InvalidLlvmCpuFeaturesFormat,
         error.UnexpectedExtraField => return .SemanticAnalyzeFail,
         error.InvalidAbiVersion => return .InvalidAbiVersion,
@@ -681,44 +677,42 @@ fn stage2TargetParse(
     zig_triple_oz: ?[*:0]const u8,
     mcpu_oz: ?[*:0]const u8,
 ) !void {
-    const target: std.build.Target = if (zig_triple_oz) |zig_triple_z| blk: {
+    const target: CrossTarget = if (zig_triple_oz) |zig_triple_z| blk: {
         const zig_triple = mem.toSliceConst(u8, zig_triple_z);
         const mcpu = if (mcpu_oz) |mcpu_z| mem.toSliceConst(u8, mcpu_z) else "baseline";
-        var diags: std.Target.ParseOptions.Diagnostics = .{};
-        break :blk std.build.Target{
-            .Cross = Target.parse(.{
-                .arch_os_abi = zig_triple,
-                .cpu_features = mcpu,
-                .diagnostics = &diags,
-            }) catch |err| switch (err) {
-                error.UnknownCpu => {
-                    std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
-                        diags.cpu_name.?,
-                        @tagName(diags.arch.?),
-                    });
-                    for (diags.arch.?.allCpuModels()) |cpu| {
-                        std.debug.warn(" {}\n", .{cpu.name});
-                    }
-                    process.exit(1);
-                },
-                error.UnknownCpuFeature => {
-                    std.debug.warn(
-                        \\Unknown CPU feature: '{}'
-                        \\Available CPU features for architecture '{}':
-                        \\
-                    , .{
-                        diags.unknown_feature_name,
-                        @tagName(diags.arch.?),
-                    });
-                    for (diags.arch.?.allFeaturesList()) |feature| {
-                        std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
-                    }
-                    process.exit(1);
-                },
-                else => |e| return e,
+        var diags: CrossTarget.ParseOptions.Diagnostics = .{};
+        break :blk CrossTarget.parse(.{
+            .arch_os_abi = zig_triple,
+            .cpu_features = mcpu,
+            .diagnostics = &diags,
+        }) catch |err| switch (err) {
+            error.UnknownCpuModel => {
+                std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
+                    diags.cpu_name.?,
+                    @tagName(diags.arch.?),
+                });
+                for (diags.arch.?.allCpuModels()) |cpu| {
+                    std.debug.warn(" {}\n", .{cpu.name});
+                }
+                process.exit(1);
             },
+            error.UnknownCpuFeature => {
+                std.debug.warn(
+                    \\Unknown CPU feature: '{}'
+                    \\Available CPU features for architecture '{}':
+                    \\
+                , .{
+                    diags.unknown_feature_name,
+                    @tagName(diags.arch.?),
+                });
+                for (diags.arch.?.allFeaturesList()) |feature| {
+                    std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
+                }
+                process.exit(1);
+            },
+            else => |e| return e,
         };
-    } else std.build.Target.Native;
+    } else .{};
 
     try stage1_target.fromTarget(target);
 }
@@ -908,8 +902,8 @@ const Stage2Target = extern struct {
 
     dynamic_linker: ?[*:0]const u8,
 
-    fn toTarget(in_target: Stage2Target) std.build.Target {
-        if (in_target.is_native) return .Native;
+    fn toTarget(in_target: Stage2Target) CrossTarget {
+        if (in_target.is_native) return .{};
 
         const in_arch = in_target.arch - 1; // skip over ZigLLVM_UnknownArch
         const in_os = in_target.os;
@@ -924,28 +918,11 @@ const Stage2Target = extern struct {
         };
     }
 
-    fn fromTarget(self: *Stage2Target, build_target: std.build.Target) !void {
+    fn fromTarget(self: *Stage2Target, cross_target: CrossTarget) !void {
         const allocator = std.heap.c_allocator;
-        var dynamic_linker: ?[*:0]u8 = null;
-        const target = switch (build_target) {
-            .Native => blk: {
-                const info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator);
-                if (info.dynamic_linker) |dl| {
-                    dynamic_linker = dl.ptr;
-                }
 
-                // TODO we want to just use 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;
-                var t = info.target;
-                t.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features);
-                break :blk t;
-            },
-            .Cross => |t| t,
-        };
+        var dynamic_linker: ?[*:0]u8 = null;
+        const target = try crossTargetToTarget(cross_target, &dynamic_linker);
 
         var cache_hash = try std.Buffer.allocPrint(allocator, "{}\n{}\n", .{
             target.cpu.model.name,
@@ -1145,7 +1122,7 @@ const Stage2Target = extern struct {
             .cpu_builtin_str = cpu_builtin_str_buffer.toOwnedSlice().ptr,
             .os_builtin_str = os_builtin_str_buffer.toOwnedSlice().ptr,
             .cache_hash = cache_hash.toOwnedSlice().ptr,
-            .is_native = build_target == .Native,
+            .is_native = cross_target.isNative(),
             .glibc_version = glibc_version,
             .dynamic_linker = dynamic_linker,
         };
@@ -1156,6 +1133,40 @@ 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();
+    if (cross_target.isNativeCpu() or cross_target.isNativeOs()) {
+        const detected_info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator);
+        if (cross_target.isNativeCpu()) {
+            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.isNativeOs()) {
+            adjusted_target.os = detected_info.target.os;
+
+            if (detected_info.dynamic_linker) |dl| {
+                dynamic_linker_ptr.* = dl.ptr;
+            }
+            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);
+        }
+    }
+    return adjusted_target;
+}
+
 // ABI warning
 const Stage2GLibCVersion = extern struct {
     major: u32,
test/src/translate_c.zig
@@ -7,6 +7,7 @@ const fmt = std.fmt;
 const mem = std.mem;
 const fs = std.fs;
 const warn = std.debug.warn;
+const CrossTarget = std.zig.CrossTarget;
 
 pub const TranslateCContext = struct {
     b: *build.Builder,
@@ -19,7 +20,7 @@ pub const TranslateCContext = struct {
         sources: ArrayList(SourceFile),
         expected_lines: ArrayList([]const u8),
         allow_warnings: bool,
-        target: build.Target = .Native,
+        target: CrossTarget = CrossTarget{},
 
         const SourceFile = struct {
             filename: []const u8,
@@ -75,7 +76,7 @@ pub const TranslateCContext = struct {
     pub fn addWithTarget(
         self: *TranslateCContext,
         name: []const u8,
-        target: build.Target,
+        target: CrossTarget,
         source: []const u8,
         expected_lines: []const []const u8,
     ) void {
test/compile_errors.zig
@@ -1,5 +1,5 @@
 const tests = @import("tests.zig");
-const Target = @import("std").Target;
+const std = @import("std");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
     cases.addTest("type mismatch with tuple concatenation",
@@ -386,12 +386,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         , &[_][]const u8{
             "tmp.zig:3:5: error: target arch 'wasm32' does not support calling with a new stack",
         });
-        tc.target = tests.Target{
-            .Cross = .{
-                .cpu = Target.Cpu.baseline(.wasm32),
-                .os = Target.Os.defaultVersionRange(.wasi),
-                .abi = .none,
-            },
+        tc.target = std.zig.CrossTarget{
+            .cpu_arch = .wasm32,
+            .os_tag = .wasi,
+            .abi = .none,
         };
         break :x tc;
     });
@@ -787,12 +785,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         , &[_][]const u8{
             "tmp.zig:2:14: error: could not find 'foo' in the inputs or outputs",
         });
-        tc.target = tests.Target{
-            .Cross = .{
-                .cpu = Target.Cpu.baseline(.x86_64),
-                .os = Target.Os.defaultVersionRange(.linux),
-                .abi = .gnu,
-            },
+        tc.target = std.zig.CrossTarget{
+            .cpu_arch = .x86_64,
+            .os_tag = .linux,
+            .abi = .gnu,
         };
         break :x tc;
     });
@@ -1452,7 +1448,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         "tmp.zig:2:18: error: invalid operands to binary expression: 'error{A}' and 'error{B}'",
     });
 
-    if (Target.current.os.tag == .linux) {
+    if (std.Target.current.os.tag == .linux) {
         cases.addTest("implicit dependency on libc",
             \\extern "c" fn exit(u8) void;
             \\export fn entry() void {
test/tests.zig
@@ -3,7 +3,7 @@ const builtin = std.builtin;
 const debug = std.debug;
 const warn = debug.warn;
 const build = std.build;
-pub const Target = build.Target;
+const CrossTarget = std.zig.CrossTarget;
 const Buffer = std.Buffer;
 const io = std.io;
 const fs = std.fs;
@@ -30,7 +30,7 @@ pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTransla
 pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext;
 
 const TestTarget = struct {
-    target: build.Target = .Native,
+    target: CrossTarget = @as(CrossTarget, .{}),
     mode: builtin.Mode = .Debug,
     link_libc: bool = false,
     single_threaded: bool = false,
@@ -52,105 +52,85 @@ const test_targets = blk: {
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.x86_64),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .none,
-                },
+            .target = .{
+                .cpu_arch = .x86_64,
+                .os_tag = .linux,
+                .abi = .none,
             },
         },
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.x86_64),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .gnu,
-                },
+            .target = .{
+                .cpu_arch = .x86_64,
+                .os_tag = .linux,
+                .abi = .gnu,
             },
             .link_libc = true,
         },
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.x86_64),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .musl,
-                },
+            .target = .{
+                .cpu_arch = .x86_64,
+                .os_tag = .linux,
+                .abi = .musl,
             },
             .link_libc = true,
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.i386),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .none,
-                },
+            .target = .{
+                .cpu_arch = .i386,
+                .os_tag = .linux,
+                .abi = .none,
             },
         },
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.i386),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .musl,
-                },
+            .target = .{
+                .cpu_arch = .i386,
+                .os_tag = .linux,
+                .abi = .musl,
             },
             .link_libc = true,
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.aarch64),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .none,
-                },
+            .target = .{
+                .cpu_arch = .aarch64,
+                .os_tag = .linux,
+                .abi = .none,
             },
         },
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.aarch64),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .musl,
-                },
+            .target = .{
+                .cpu_arch = .aarch64,
+                .os_tag = .linux,
+                .abi = .musl,
             },
             .link_libc = true,
         },
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.aarch64),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .gnu,
-                },
+            .target = .{
+                .cpu_arch = .aarch64,
+                .os_tag = .linux,
+                .abi = .gnu,
             },
             .link_libc = true,
         },
 
         TestTarget{
-            .target = .{
-                .Cross = std.Target.parse(.{
-                    .arch_os_abi = "arm-linux-none",
-                    .cpu_features = "generic+v8a",
-                }) catch unreachable,
-            },
+            .target = CrossTarget.parse(.{
+                .arch_os_abi = "arm-linux-none",
+                .cpu_features = "generic+v8a",
+            }) catch unreachable,
         },
         TestTarget{
-            .target = .{
-                .Cross = std.Target.parse(.{
-                    .arch_os_abi = "arm-linux-musleabihf",
-                    .cpu_features = "generic+v8a",
-                }) catch unreachable,
-            },
+            .target = CrossTarget.parse(.{
+                .arch_os_abi = "arm-linux-musleabihf",
+                .cpu_features = "generic+v8a",
+            }) catch unreachable,
             .link_libc = true,
         },
         // TODO https://github.com/ziglang/zig/issues/3287
         //TestTarget{
-        //    .target = std.Target.parse(.{
+        //    .target = CrossTarget.parse(.{
         //        .arch_os_abi = "arm-linux-gnueabihf",
         //        .cpu_features = "generic+v8a",
         //    }) catch unreachable,
@@ -158,75 +138,61 @@ const test_targets = blk: {
         //},
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.mipsel),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .none,
-                },
+            .target = .{
+                .cpu_arch = .mipsel,
+                .os_tag = .linux,
+                .abi = .none,
             },
         },
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.mipsel),
-                    .os = std.Target.Os.defaultVersionRange(.linux),
-                    .abi = .musl,
-                },
+            .target = .{
+                .cpu_arch = .mipsel,
+                .os_tag = .linux,
+                .abi = .musl,
             },
             .link_libc = true,
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.x86_64),
-                    .os = std.Target.Os.defaultVersionRange(.macosx),
-                    .abi = .gnu,
-                },
+            .target = .{
+                .cpu_arch = .x86_64,
+                .os_tag = .macosx,
+                .abi = .gnu,
             },
             // TODO https://github.com/ziglang/zig/issues/3295
             .disable_native = true,
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.i386),
-                    .os = std.Target.Os.defaultVersionRange(.windows),
-                    .abi = .msvc,
-                },
+            .target = .{
+                .cpu_arch = .i386,
+                .os_tag = .windows,
+                .abi = .msvc,
             },
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.x86_64),
-                    .os = std.Target.Os.defaultVersionRange(.windows),
-                    .abi = .msvc,
-                },
+            .target = .{
+                .cpu_arch = .x86_64,
+                .os_tag = .windows,
+                .abi = .msvc,
             },
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.i386),
-                    .os = std.Target.Os.defaultVersionRange(.windows),
-                    .abi = .gnu,
-                },
+            .target = .{
+                .cpu_arch = .i386,
+                .os_tag = .windows,
+                .abi = .gnu,
             },
             .link_libc = true,
         },
 
         TestTarget{
-            .target = Target{
-                .Cross = .{
-                    .cpu = std.Target.Cpu.baseline(.x86_64),
-                    .os = std.Target.Os.defaultVersionRange(.windows),
-                    .abi = .gnu,
-                },
+            .target = .{
+                .cpu_arch = .x86_64,
+                .os_tag = .windows,
+                .abi = .gnu,
             },
             .link_libc = true,
         },
@@ -435,13 +401,13 @@ pub fn addPkgTests(
     const step = b.step(b.fmt("test-{}", .{name}), desc);
 
     for (test_targets) |test_target| {
-        if (skip_non_native and test_target.target != .Native)
+        if (skip_non_native and !test_target.target.isNative())
             continue;
 
         if (skip_libc and test_target.link_libc)
             continue;
 
-        if (test_target.link_libc and test_target.target.getTarget().osRequiresLibC()) {
+        if (test_target.link_libc and test_target.target.getOs().requiresLibC()) {
             // This would be a redundant test.
             continue;
         }
@@ -451,8 +417,8 @@ pub fn addPkgTests(
 
         const ArchTag = @TagType(builtin.Arch);
         if (test_target.disable_native and
-            test_target.target.getOs() == std.Target.current.os.tag and
-            test_target.target.getArch() == std.Target.current.cpu.arch)
+            test_target.target.getOsTag() == std.Target.current.os.tag and
+            test_target.target.getCpuArch() == std.Target.current.cpu.arch)
         {
             continue;
         }
@@ -462,17 +428,14 @@ pub fn addPkgTests(
         } else false;
         if (!want_this_mode) continue;
 
-        const libc_prefix = if (test_target.target.getTarget().osRequiresLibC())
+        const libc_prefix = if (test_target.target.getOs().requiresLibC())
             ""
         else if (test_target.link_libc)
             "c"
         else
             "bare";
 
-        const triple_prefix = if (test_target.target == .Native)
-            @as([]const u8, "native")
-        else
-            test_target.target.zigTriple(b.allocator) catch unreachable;
+        const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable;
 
         const these_tests = b.addTest(root_src);
         const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";
@@ -486,7 +449,7 @@ pub fn addPkgTests(
         these_tests.single_threaded = test_target.single_threaded;
         these_tests.setFilter(test_filter);
         these_tests.setBuildMode(test_target.mode);
-        these_tests.setTheTarget(test_target.target);
+        these_tests.setTarget(test_target.target);
         if (test_target.link_libc) {
             these_tests.linkSystemLibrary("c");
         }
@@ -716,7 +679,7 @@ pub const CompileErrorContext = struct {
         link_libc: bool,
         is_exe: bool,
         is_test: bool,
-        target: Target = .Native,
+        target: CrossTarget = CrossTarget{},
 
         const SourceFile = struct {
             filename: []const u8,
@@ -808,12 +771,9 @@ pub const CompileErrorContext = struct {
             zig_args.append("--output-dir") catch unreachable;
             zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable;
 
-            switch (self.case.target) {
-                .Native => {},
-                .Cross => {
-                    try zig_args.append("-target");
-                    try zig_args.append(try self.case.target.zigTriple(b.allocator));
-                },
+            if (!self.case.target.isNative()) {
+                try zig_args.append("-target");
+                try zig_args.append(try self.case.target.zigTriple(b.allocator));
             }
 
             switch (self.build_mode) {
test/translate_c.zig
@@ -1,6 +1,6 @@
 const tests = @import("tests.zig");
 const std = @import("std");
-const Target = std.Target;
+const CrossTarget = std.zig.CrossTarget;
 
 pub fn addCases(cases: *tests.TranslateCContext) void {
     cases.add("macro line continuation",
@@ -665,7 +665,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
-    if (Target.current.os.tag != .windows) {
+    if (std.Target.current.os.tag != .windows) {
         // Windows treats this as an enum with type c_int
         cases.add("big negative enum init values when C ABI supports long long enums",
             \\enum EnumWithInits {
@@ -1064,7 +1064,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
-    if (Target.current.os.tag != .windows) {
+    if (std.Target.current.os.tag != .windows) {
         // sysv_abi not currently supported on windows
         cases.add("Macro qualified functions",
             \\void __attribute__((sysv_abi)) foo(void);
@@ -1094,11 +1094,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     });
 
     cases.addWithTarget("Calling convention", .{
-        .Cross = .{
-            .cpu = Target.Cpu.baseline(.i386),
-            .os = Target.Os.defaultVersionRange(.linux),
-            .abi = .none,
-        },
+        .cpu_arch = .i386,
+        .os_tag = .linux,
+        .abi = .none,
     },
         \\void __attribute__((fastcall)) foo1(float *a);
         \\void __attribute__((stdcall)) foo2(float *a);
@@ -1113,12 +1111,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub fn foo5(a: [*c]f32) callconv(.Thiscall) void;
     });
 
-    cases.addWithTarget("Calling convention", .{
-        .Cross = Target.parse(.{
-            .arch_os_abi = "arm-linux-none",
-            .cpu_features = "generic+v8_5a",
-        }) catch unreachable,
-    },
+    cases.addWithTarget("Calling convention", CrossTarget.parse(.{
+        .arch_os_abi = "arm-linux-none",
+        .cpu_features = "generic+v8_5a",
+    }) catch unreachable,
         \\void __attribute__((pcs("aapcs"))) foo1(float *a);
         \\void __attribute__((pcs("aapcs-vfp"))) foo2(float *a);
     , &[_][]const u8{
@@ -1126,12 +1122,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub fn foo2(a: [*c]f32) callconv(.AAPCSVFP) void;
     });
 
-    cases.addWithTarget("Calling convention", .{
-        .Cross = Target.parse(.{
-            .arch_os_abi = "aarch64-linux-none",
-            .cpu_features = "generic+v8_5a",
-        }) catch unreachable,
-    },
+    cases.addWithTarget("Calling convention", CrossTarget.parse(.{
+        .arch_os_abi = "aarch64-linux-none",
+        .cpu_features = "generic+v8_5a",
+    }) catch unreachable,
         \\void __attribute__((aarch64_vector_pcs)) foo1(float *a);
     , &[_][]const u8{
         \\pub fn foo1(a: [*c]f32) callconv(.Vectorcall) void;
@@ -1600,7 +1594,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
-    if (Target.current.os.tag != .windows) {
+    if (std.Target.current.os.tag != .windows) {
         // When clang uses the <arch>-windows-none triple it behaves as MSVC and
         // interprets the inner `struct Bar` as an anonymous structure
         cases.add("type referenced struct",
build.zig
@@ -298,7 +298,7 @@ fn configureStage2(b: *Builder, exe: var, ctx: Context) !void {
     }
     dependOnLib(b, exe, ctx.llvm);
 
-    if (exe.target.getOs() == .linux) {
+    if (exe.target.getOsTag() == .linux) {
         try addCxxKnownPath(b, ctx, exe, "libstdc++.a",
             \\Unable to determine path to libstdc++.a
             \\On Fedora, install libstdc++-static and try again.