zig/lib/std / zig/CrossTarget.zig

Contains all the same data as Target, additionally introducing the concept of "the native target". The purpose of this abstraction is to provide meaningful and unsurprising defaults. This struct does reference any resources and it is copyable.


const CrossTarget = @This();
const std = @import("../std.zig");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Target = std.Target;
const mem = std.mem;

cpu_arch: ?Target.Cpu.Arch = null,

cpu_model: CpuModel = CpuModel.determined_by_cpu_arch,

cpu_features_add: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty,

cpu_features_sub: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty,

os_tag: ?Target.Os.Tag = null,

os_version_min: ?OsVersion = null,

os_version_max: ?OsVersion = null,

glibc_version: ?SemanticVersion = null,

abi: ?Target.Abi = null,

dynamic_linker: DynamicLinker = DynamicLinker{},

ofmt: ?Target.ObjectFormat = null,

CpuModel

null means native. Sparse set of CPU features to add to the set from cpu_model. Sparse set of CPU features to remove from the set from cpu_model. null means native. null means the default version range for os_tag. If os_tag is null (native) then null for this field means native. When cross compiling, null means default (latest known OS version). When os_tag is native, null means equal to the native OS version. null means default when cross compiling, or native when os_tag is native. If isGnuLibC() is false, this must be null and is ignored. null means the native C ABI, if os_tag is native, otherwise it means the default C ABI. When os_tag is null, then null means native. Otherwise it means the standard path based on the os_tag. null means default for the cpu/arch/os combo.

pub const CpuModel = union(enum) {
    native,

    baseline,

    determined_by_cpu_arch,

    explicit: *const Target.Cpu.Model,
};

OsVersion

Always native Always baseline If CPU Architecture is native, then the CPU model will be native. Otherwise, it will be baseline.

pub const OsVersion = union(enum) {
    none: void,
    semver: SemanticVersion,
    windows: Target.Os.WindowsVersion,
};

SemanticVersion

pub const SemanticVersion = std.SemanticVersion;

DynamicLinker

pub const DynamicLinker = Target.DynamicLinker;

fromTarget()

pub fn fromTarget(target: Target) CrossTarget {
    var result: CrossTarget = .{
        .cpu_arch = target.cpu.arch,
        .cpu_model = .{ .explicit = target.cpu.model },
        .os_tag = target.os.tag,
        .os_version_min = undefined,
        .os_version_max = undefined,
        .abi = target.abi,
        .glibc_version = if (target.isGnuLibC())
            target.os.version_range.linux.glibc
        else
            null,
    };
    result.updateOsVersionRange(target.os);

    const all_features = target.cpu.arch.allFeaturesList();
    var cpu_model_set = target.cpu.model.features;
    cpu_model_set.populateDependencies(all_features);
    {
        // The "add" set is the full set with the CPU Model set removed.
        const add_set = &result.cpu_features_add;
        add_set.* = target.cpu.features;
        add_set.removeFeatureSet(cpu_model_set);
    }
    {
        // The "sub" set is the features that are on in CPU Model set and off in the full set.
        const sub_set = &result.cpu_features_sub;
        sub_set.* = cpu_model_set;
        sub_set.removeFeatureSet(target.cpu.features);
    }
    return result;
}

fn updateOsVersionRange(self: *CrossTarget, os: Target.Os) void {
    switch (os.tag) {
        .freestanding,
        .ananas,
        .cloudabi,
        .fuchsia,
        .kfreebsd,
        .lv2,
        .solaris,
        .illumos,
        .zos,
        .haiku,
        .minix,
        .rtems,
        .nacl,
        .aix,
        .cuda,
        .nvcl,
        .amdhsa,
        .ps4,
        .ps5,
        .elfiamcu,
        .mesa3d,
        .contiki,
        .amdpal,
        .hermit,
        .hurd,
        .wasi,
        .emscripten,
        .driverkit,
        .shadermodel,
        .liteos,
        .uefi,
        .opencl,
        .glsl450,
        .vulkan,
        .plan9,
        .other,
        => {
            self.os_version_min = .{ .none = {} };
            self.os_version_max = .{ .none = {} };
        },

        .freebsd,
        .macos,
        .ios,
        .tvos,
        .watchos,
        .netbsd,
        .openbsd,
        .dragonfly,
        => {
            self.os_version_min = .{ .semver = os.version_range.semver.min };
            self.os_version_max = .{ .semver = os.version_range.semver.max };
        },

        .linux => {
            self.os_version_min = .{ .semver = os.version_range.linux.range.min };
            self.os_version_max = .{ .semver = os.version_range.linux.range.max };
        },

        .windows => {
            self.os_version_min = .{ .windows = os.version_range.windows.min };
            self.os_version_max = .{ .windows = os.version_range.windows.max };
        },
    }
}

toTarget()

TODO deprecated, use std.zig.system.NativeTargetInfo.detect.

pub fn toTarget(self: CrossTarget) Target {
    return .{
        .cpu = self.getCpu(),
        .os = self.getOs(),
        .abi = self.getAbi(),
        .ofmt = self.getObjectFormat(),
    };
}

ParseOptions

pub const ParseOptions = struct {
    arch_os_abi: []const u8 = "native",

    cpu_features: ?[]const u8 = null,

    dynamic_linker: ?[]const u8 = null,

    object_format: ?[]const u8 = null,

    diagnostics: ?*Diagnostics = null,

    pub const Diagnostics = struct {
        arch: ?Target.Cpu.Arch = null,

        os_name: ?[]const u8 = null,

        os_tag: ?Target.Os.Tag = null,

        abi: ?Target.Abi = null,

        cpu_name: ?[]const u8 = null,

        unknown_feature_name: ?[]const u8 = null,
    };
};

parse()

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. 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". Absolute path to dynamic linker, to override the default, which is either a natively detected path, or a standard path. If this is provided, the function will populate some information about parsing failures, so that user-friendly error messages can be delivered. If the architecture was determined, this will be populated. If the OS name was determined, this will be populated. If the OS tag was determined, this will be populated. If the ABI was determined, this will be populated. If the CPU name was determined, this will be populated. If error.UnknownCpuFeature is returned, this will be populated.

pub fn parse(args: ParseOptions) !CrossTarget {
    var dummy_diags: ParseOptions.Diagnostics = undefined;
    const diags = args.diagnostics orelse &dummy_diags;

    var result: CrossTarget = .{
        .dynamic_linker = DynamicLinker.init(args.dynamic_linker),
    };

    var it = mem.splitScalar(u8, args.arch_os_abi, '-');
    const arch_name = it.first();
    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.splitScalar(u8, abi_text, '.');
        const abi = std.meta.stringToEnum(Target.Abi, abi_it.first()) orelse
            return error.UnknownApplicationBinaryInterface;
        result.abi = abi;
        diags.abi = abi;

        const abi_ver_text = abi_it.rest();
        if (abi_it.next() != null) {
            if (result.isGnuLibC()) {
                result.glibc_version = parseVersion(abi_ver_text) catch |err| switch (err) {
                    error.Overflow => return error.InvalidAbiVersion,
                    error.InvalidVersion => return error.InvalidAbiVersion,
                };
            } else {
                return error.InvalidAbiVersion;
            }
        }
    }

    if (it.next() != null) return error.UnexpectedExtraField;

    if (args.cpu_features) |cpu_features| {
        const all_features = arch.allFeaturesList();
        var index: usize = 0;
        while (index < cpu_features.len and
            cpu_features[index] != '+' and
            cpu_features[index] != '-')
        {
            index += 1;
        }
        const cpu_name = cpu_features[0..index];
        diags.cpu_name = cpu_name;

        const add_set = &result.cpu_features_add;
        const sub_set = &result.cpu_features_sub;
        if (mem.eql(u8, cpu_name, "native")) {
            result.cpu_model = .native;
        } else if (mem.eql(u8, cpu_name, "baseline")) {
            result.cpu_model = .baseline;
        } else {
            result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) };
        }

        while (index < cpu_features.len) {
            const op = cpu_features[index];
            const set = switch (op) {
                '+' => add_set,
                '-' => sub_set,
                else => unreachable,
            };
            index += 1;
            const start = index;
            while (index < cpu_features.len and
                cpu_features[index] != '+' and
                cpu_features[index] != '-')
            {
                index += 1;
            }
            const feature_name = cpu_features[start..index];
            for (all_features, 0..) |feature, feat_index_usize| {
                const feat_index = @as(Target.Cpu.Feature.Set.Index, @intCast(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;
            }
        }
    }

    if (args.object_format) |ofmt_name| {
        result.ofmt = std.meta.stringToEnum(Target.ObjectFormat, ofmt_name) orelse
            return error.UnknownObjectFormat;
    }

    return result;
}

parseCpuArch()

Similar to parse except instead of fully parsing, it only determines the CPU architecture and returns it if it can be determined, and returns null otherwise. This is intended to be used if the API user of CrossTarget needs to learn the target CPU architecture in order to fully populate ParseOptions.

pub fn parseCpuArch(args: ParseOptions) ?Target.Cpu.Arch {
    var it = mem.splitScalar(u8, args.arch_os_abi, '-');
    const arch_name = it.first();
    const arch_is_native = mem.eql(u8, arch_name, "native");
    if (arch_is_native) {
        return builtin.cpu.arch;
    } else {
        return std.meta.stringToEnum(Target.Cpu.Arch, arch_name);
    }
}

parseVersion()

Similar to SemanticVersion.parse, but with following changes: * Leading zeroes are allowed. * Supports only 2 or 3 version components (major, minor, [patch]). If 3-rd component is omitted, it will be 0.

pub fn parseVersion(ver: []const u8) error{ InvalidVersion, Overflow }!SemanticVersion {
    const parseVersionComponentFn = (struct {
        fn parseVersionComponentInner(component: []const u8) error{ InvalidVersion, Overflow }!usize {
            return std.fmt.parseUnsigned(usize, component, 10) catch |err| switch (err) {
                error.InvalidCharacter => return error.InvalidVersion,
                error.Overflow => return error.Overflow,
            };
        }
    }).parseVersionComponentInner;
    var version_components = mem.splitScalar(u8, ver, '.');
    const major = version_components.first();
    const minor = version_components.next() orelse return error.InvalidVersion;
    const patch = version_components.next() orelse "0";
    if (version_components.next() != null) return error.InvalidVersion;
    return .{
        .major = try parseVersionComponentFn(major),
        .minor = try parseVersionComponentFn(minor),
        .patch = try parseVersionComponentFn(patch),
    };
}

Test: parseVersion

test parseVersion {
    try std.testing.expectError(error.InvalidVersion, parseVersion("1"));
    try std.testing.expectEqual(SemanticVersion{ .major = 1, .minor = 2, .patch = 0 }, try parseVersion("1.2"));
    try std.testing.expectEqual(SemanticVersion{ .major = 1, .minor = 2, .patch = 3 }, try parseVersion("1.2.3"));
    try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3.4"));
}

getCpu()

TODO deprecated, use std.zig.system.NativeTargetInfo.detect.

pub fn getCpu(self: CrossTarget) Target.Cpu {
    switch (self.cpu_model) {
        .native => {
            // This works when doing `zig build` because Zig generates a build executable using
            // native CPU model & features. However this will not be accurate otherwise, and
            // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
            return builtin.cpu;
        },
        .baseline => {
            var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch());
            self.updateCpuFeatures(&adjusted_baseline.features);
            return adjusted_baseline;
        },
        .determined_by_cpu_arch => if (self.cpu_arch == null) {
            // This works when doing `zig build` because Zig generates a build executable using
            // native CPU model & features. However this will not be accurate otherwise, and
            // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
            return builtin.cpu;
        } else {
            var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch());
            self.updateCpuFeatures(&adjusted_baseline.features);
            return adjusted_baseline;
        },
        .explicit => |model| {
            var adjusted_model = model.toCpu(self.getCpuArch());
            self.updateCpuFeatures(&adjusted_model.features);
            return adjusted_model;
        },
    }
}

getCpuArch()

pub fn getCpuArch(self: CrossTarget) Target.Cpu.Arch {
    return self.cpu_arch orelse builtin.cpu.arch;
}

getCpuModel()

pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model {
    return switch (self.cpu_model) {
        .explicit => |cpu_model| cpu_model,
        else => self.getCpu().model,
    };
}

getCpuFeatures()

pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set {
    return self.getCpu().features;
}

getOs()

TODO deprecated, use std.zig.system.NativeTargetInfo.detect.

pub fn getOs(self: CrossTarget) Target.Os {
    // `builtin.os` works when doing `zig build` because Zig generates a build executable using
    // native OS version range. However this will not be accurate otherwise, and
    // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
    var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange(self.getCpuArch()) else builtin.os;

    if (self.os_version_min) |min| switch (min) {
        .none => {},
        .semver => |semver| switch (self.getOsTag()) {
            .linux => adjusted_os.version_range.linux.range.min = semver,
            else => adjusted_os.version_range.semver.min = semver,
        },
        .windows => |win_ver| adjusted_os.version_range.windows.min = win_ver,
    };

    if (self.os_version_max) |max| switch (max) {
        .none => {},
        .semver => |semver| switch (self.getOsTag()) {
            .linux => adjusted_os.version_range.linux.range.max = semver,
            else => adjusted_os.version_range.semver.max = semver,
        },
        .windows => |win_ver| adjusted_os.version_range.windows.max = win_ver,
    };

    if (self.glibc_version) |glibc| {
        assert(self.isGnuLibC());
        adjusted_os.version_range.linux.glibc = glibc;
    }

    return adjusted_os;
}

getOsTag()

pub fn getOsTag(self: CrossTarget) Target.Os.Tag {
    return self.os_tag orelse builtin.os.tag;
}

getOsVersionMin()

TODO deprecated, use std.zig.system.NativeTargetInfo.detect.

pub fn getOsVersionMin(self: CrossTarget) OsVersion {
    if (self.os_version_min) |version_min| return version_min;
    var tmp: CrossTarget = undefined;
    tmp.updateOsVersionRange(self.getOs());
    return tmp.os_version_min.?;
}

getOsVersionMax()

TODO deprecated, use std.zig.system.NativeTargetInfo.detect.

pub fn getOsVersionMax(self: CrossTarget) OsVersion {
    if (self.os_version_max) |version_max| return version_max;
    var tmp: CrossTarget = undefined;
    tmp.updateOsVersionRange(self.getOs());
    return tmp.os_version_max.?;
}

getAbi()

TODO deprecated, use std.zig.system.NativeTargetInfo.detect.

pub fn getAbi(self: CrossTarget) Target.Abi {
    if (self.abi) |abi| return abi;

    if (self.os_tag == null) {
        // This works when doing `zig build` because Zig generates a build executable using
        // native CPU model & features. However this will not be accurate otherwise, and
        // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
        return builtin.abi;
    }

    return Target.Abi.default(self.getCpuArch(), self.getOs());
}

isFreeBSD()

pub fn isFreeBSD(self: CrossTarget) bool {
    return self.getOsTag() == .freebsd;
}

isDarwin()

pub fn isDarwin(self: CrossTarget) bool {
    return self.getOsTag().isDarwin();
}

isNetBSD()

pub fn isNetBSD(self: CrossTarget) bool {
    return self.getOsTag() == .netbsd;
}

isOpenBSD()

pub fn isOpenBSD(self: CrossTarget) bool {
    return self.getOsTag() == .openbsd;
}

isUefi()

pub fn isUefi(self: CrossTarget) bool {
    return self.getOsTag() == .uefi;
}

isDragonFlyBSD()

pub fn isDragonFlyBSD(self: CrossTarget) bool {
    return self.getOsTag() == .dragonfly;
}

isLinux()

pub fn isLinux(self: CrossTarget) bool {
    return self.getOsTag() == .linux;
}

isWindows()

pub fn isWindows(self: CrossTarget) bool {
    return self.getOsTag() == .windows;
}

exeFileExt()

pub fn exeFileExt(self: CrossTarget) [:0]const u8 {
    return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag());
}

staticLibSuffix()

pub fn staticLibSuffix(self: CrossTarget) [:0]const u8 {
    return Target.staticLibSuffix_os_abi(self.getOsTag(), self.getAbi());
}

dynamicLibSuffix()

pub fn dynamicLibSuffix(self: CrossTarget) [:0]const u8 {
    return self.getOsTag().dynamicLibSuffix();
}

libPrefix()

pub fn libPrefix(self: CrossTarget) [:0]const u8 {
    return Target.libPrefix_os_abi(self.getOsTag(), self.getAbi());
}

isNativeCpu()

pub fn isNativeCpu(self: CrossTarget) bool {
    return self.cpu_arch == null and
        (self.cpu_model == .native or self.cpu_model == .determined_by_cpu_arch) and
        self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty();
}

isNativeOs()

pub fn isNativeOs(self: CrossTarget) bool {
    return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and
        self.dynamic_linker.get() == null and self.glibc_version == null;
}

isNativeAbi()

pub fn isNativeAbi(self: CrossTarget) bool {
    return self.os_tag == null and self.abi == null;
}

isNative()

pub fn isNative(self: CrossTarget) bool {
    return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi();
}

fn formatVersion(version: SemanticVersion, writer: anytype) !void {
    if (version.patch == 0) {
        try writer.print("{d}.{d}", .{ version.major, version.minor });
    } else {
        try writer.print("{d}.{d}.{d}", .{ version.major, version.minor, version.patch });
    }
}

zigTriple()

Formats a version with the patch component omitted if it is zero, unlike SemanticVersion.format which formats all its version components regardless.

pub fn zigTriple(self: CrossTarget, allocator: mem.Allocator) error{OutOfMemory}![]u8 {
    if (self.isNative()) {
        return allocator.dupe(u8, "native");
    }

    const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native";
    const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native";

    var result = std.ArrayList(u8).init(allocator);
    defer result.deinit();

    try result.writer().print("{s}-{s}", .{ arch_name, os_name });

    // The zig target syntax does not allow specifying a max os version with no min, so
    // if either are present, we need the min.
    if (self.os_version_min != null or self.os_version_max != null) {
        switch (self.getOsVersionMin()) {
            .none => {},
            .semver => |v| {
                try result.writer().writeAll(".");
                try formatVersion(v, result.writer());
            },
            .windows => |v| try result.writer().print("{s}", .{v}),
        }
    }
    if (self.os_version_max) |max| {
        switch (max) {
            .none => {},
            .semver => |v| {
                try result.writer().writeAll("...");
                try formatVersion(v, result.writer());
            },
            .windows => |v| try result.writer().print("..{s}", .{v}),
        }
    }

    if (self.glibc_version) |v| {
        try result.writer().print("-{s}.", .{@tagName(self.getAbi())});
        try formatVersion(v, result.writer());
    } else if (self.abi) |abi| {
        try result.writer().print("-{s}", .{@tagName(abi)});
    }

    return result.toOwnedSlice();
}

allocDescription()

pub fn allocDescription(self: CrossTarget, allocator: mem.Allocator) ![]u8 {
    // TODO is there anything else worthy of the description that is not
    // already captured in the triple?
    return self.zigTriple(allocator);
}

linuxTriple()

pub fn linuxTriple(self: CrossTarget, allocator: mem.Allocator) ![]u8 {
    return Target.linuxTripleSimple(allocator, self.getCpuArch(), self.getOsTag(), self.getAbi());
}

wantSharedLibSymLinks()

pub fn wantSharedLibSymLinks(self: CrossTarget) bool {
    return self.getOsTag() != .windows;
}

VcpkgLinkage

pub const VcpkgLinkage = std.builtin.LinkMode;

vcpkgTriplet()

Returned slice must be freed by the caller.

pub fn vcpkgTriplet(self: CrossTarget, allocator: mem.Allocator, linkage: VcpkgLinkage) ![]u8 {
    const arch = switch (self.getCpuArch()) {
        .x86 => "x86",
        .x86_64 => "x64",

        .arm,
        .armeb,
        .thumb,
        .thumbeb,
        .aarch64_32,
        => "arm",

        .aarch64,
        .aarch64_be,
        => "arm64",

        else => return error.UnsupportedVcpkgArchitecture,
    };

    const os = switch (self.getOsTag()) {
        .windows => "windows",
        .linux => "linux",
        .macos => "macos",
        else => return error.UnsupportedVcpkgOperatingSystem,
    };

    const static_suffix = switch (linkage) {
        .Static => "-static",
        .Dynamic => "",
    };

    return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix });
}

isGnuLibC()

pub fn isGnuLibC(self: CrossTarget) bool {
    return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi());
}

setGnuLibCVersion()

pub fn setGnuLibCVersion(self: *CrossTarget, major: u32, minor: u32, patch: u32) void {
    assert(self.isGnuLibC());
    self.glibc_version = SemanticVersion{ .major = major, .minor = minor, .patch = patch };
}

getObjectFormat()

pub fn getObjectFormat(self: CrossTarget) Target.ObjectFormat {
    return self.ofmt orelse Target.ObjectFormat.default(self.getOsTag(), self.getCpuArch());
}

updateCpuFeatures()

pub fn updateCpuFeatures(self: CrossTarget, set: *Target.Cpu.Feature.Set) void {
    set.removeFeatureSet(self.cpu_features_sub);
    set.addFeatureSet(self.cpu_features_add);
    set.populateDependencies(self.getCpuArch().allFeaturesList());
    set.removeFeatureSet(self.cpu_features_sub);
}

fn parseOs(result: *CrossTarget, diags: *ParseOptions.Diagnostics, text: []const u8) !void {
    var it = mem.splitScalar(u8, text, '.');
    const os_name = it.first();
    diags.os_name = os_name;
    const os_is_native = mem.eql(u8, os_name, "native");
    if (!os_is_native) {
        result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse
            return error.UnknownOperatingSystem;
    }
    const tag = result.getOsTag();
    diags.os_tag = tag;

    const version_text = it.rest();
    if (it.next() == null) return;

    switch (tag) {
        .freestanding,
        .ananas,
        .cloudabi,
        .fuchsia,
        .kfreebsd,
        .lv2,
        .solaris,
        .illumos,
        .zos,
        .haiku,
        .minix,
        .rtems,
        .nacl,
        .aix,
        .cuda,
        .nvcl,
        .amdhsa,
        .ps4,
        .ps5,
        .elfiamcu,
        .mesa3d,
        .contiki,
        .amdpal,
        .hermit,
        .hurd,
        .wasi,
        .emscripten,
        .uefi,
        .opencl,
        .glsl450,
        .vulkan,
        .plan9,
        .driverkit,
        .shadermodel,
        .liteos,
        .other,
        => return error.InvalidOperatingSystemVersion,

        .freebsd,
        .macos,
        .ios,
        .tvos,
        .watchos,
        .netbsd,
        .openbsd,
        .linux,
        .dragonfly,
        => {
            var range_it = mem.splitSequence(u8, version_text, "...");

            const min_text = range_it.next().?;
            const min_ver = parseVersion(min_text) catch |err| switch (err) {
                error.Overflow => 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 = parseVersion(max_text) catch |err| switch (err) {
                error.Overflow => return error.InvalidOperatingSystemVersion,
                error.InvalidVersion => return error.InvalidOperatingSystemVersion,
            };
            result.os_version_max = .{ .semver = max_ver };
        },

        .windows => {
            var range_it = mem.splitSequence(u8, version_text, "...");

            const min_text = range_it.first();
            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

test "CrossTarget.parse" {
    if (builtin.target.isGnuLibC()) {
        var cross_target = try CrossTarget.parse(.{});
        cross_target.setGnuLibCVersion(2, 1, 1);

        const text = try cross_target.zigTriple(std.testing.allocator);
        defer std.testing.allocator.free(text);

        var buf: [256]u8 = undefined;
        const triple = std.fmt.bufPrint(
            buf[0..],
            "native-native-{s}.2.1.1",
            .{@tagName(builtin.abi)},
        ) catch unreachable;

        try std.testing.expectEqualSlices(u8, triple, text);
    }
    {
        const cross_target = try CrossTarget.parse(.{
            .arch_os_abi = "aarch64-linux",
            .cpu_features = "native",
        });

        try std.testing.expect(cross_target.cpu_arch.? == .aarch64);
        try std.testing.expect(cross_target.cpu_model == .native);
    }
    {
        const cross_target = try CrossTarget.parse(.{ .arch_os_abi = "native" });

        try std.testing.expect(cross_target.cpu_arch == null);
        try std.testing.expect(cross_target.isNative());

        const text = try cross_target.zigTriple(std.testing.allocator);
        defer std.testing.allocator.free(text);
        try std.testing.expectEqualSlices(u8, "native", text);
    }
    {
        const cross_target = try CrossTarget.parse(.{
            .arch_os_abi = "x86_64-linux-gnu",
            .cpu_features = "x86_64-sse-sse2-avx-cx8",
        });
        const target = cross_target.toTarget();

        try std.testing.expect(target.os.tag == .linux);
        try std.testing.expect(target.abi == .gnu);
        try std.testing.expect(target.cpu.arch == .x86_64);
        try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse));
        try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx));
        try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8));
        try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov));
        try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr));

        try std.testing.expect(Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx, .cmov }));
        try std.testing.expect(!Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx }));
        try std.testing.expect(Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87 }));
        try std.testing.expect(!Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87, .sse }));

        const text = try cross_target.zigTriple(std.testing.allocator);
        defer std.testing.allocator.free(text);
        try std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text);
    }
    {
        const cross_target = try CrossTarget.parse(.{
            .arch_os_abi = "arm-linux-musleabihf",
            .cpu_features = "generic+v8a",
        });
        const target = cross_target.toTarget();

        try std.testing.expect(target.os.tag == .linux);
        try std.testing.expect(target.abi == .musleabihf);
        try std.testing.expect(target.cpu.arch == .arm);
        try std.testing.expect(target.cpu.model == &Target.arm.cpu.generic);
        try std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a));

        const text = try cross_target.zigTriple(std.testing.allocator);
        defer std.testing.allocator.free(text);
        try std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text);
    }
    {
        const cross_target = try CrossTarget.parse(.{
            .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
            .cpu_features = "generic+v8a",
        });
        const target = cross_target.toTarget();

        try std.testing.expect(target.cpu.arch == .aarch64);
        try std.testing.expect(target.os.tag == .linux);
        try std.testing.expect(target.os.version_range.linux.range.min.major == 3);
        try std.testing.expect(target.os.version_range.linux.range.min.minor == 10);
        try std.testing.expect(target.os.version_range.linux.range.min.patch == 0);
        try std.testing.expect(target.os.version_range.linux.range.max.major == 4);
        try std.testing.expect(target.os.version_range.linux.range.max.minor == 4);
        try std.testing.expect(target.os.version_range.linux.range.max.patch == 1);
        try std.testing.expect(target.os.version_range.linux.glibc.major == 2);
        try std.testing.expect(target.os.version_range.linux.glibc.minor == 27);
        try std.testing.expect(target.os.version_range.linux.glibc.patch == 0);
        try std.testing.expect(target.abi == .gnu);

        const text = try cross_target.zigTriple(std.testing.allocator);
        defer std.testing.allocator.free(text);
        try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text);
    }
}