1//! Contains all the same data as `Target`, additionally introducing the concept of "the native target".
2//! The purpose of this abstraction is to provide meaningful and unsurprising defaults.
3//! This struct does reference any resources and it is copyable.
4
5const CrossTarget = @This();
6const std = @import("../std.zig");
7const builtin = @import("builtin");
8const assert = std.debug.assert;
9const Target = std.Target;
10const mem = std.mem;
11
12/// `null` means native.
13cpu_arch: ?Target.Cpu.Arch = null,
14
15cpu_model: CpuModel = CpuModel.determined_by_cpu_arch,
16
17/// Sparse set of CPU features to add to the set from `cpu_model`.
18cpu_features_add: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty,
19
20/// Sparse set of CPU features to remove from the set from `cpu_model`.
21cpu_features_sub: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty,
22
23/// `null` means native.
24os_tag: ?Target.Os.Tag = null,
25
26/// `null` means the default version range for `os_tag`. If `os_tag` is `null` (native)
27/// then `null` for this field means native.
28os_version_min: ?OsVersion = null,
29
30/// When cross compiling, `null` means default (latest known OS version).
31/// When `os_tag` is native, `null` means equal to the native OS version.
32os_version_max: ?OsVersion = null,
33
34/// `null` means default when cross compiling, or native when os_tag is native.
35/// If `isGnuLibC()` is `false`, this must be `null` and is ignored.
36glibc_version: ?SemVer = null,
37
38/// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI.
39abi: ?Target.Abi = null,
40
41/// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path
42/// based on the `os_tag`.
43dynamic_linker: DynamicLinker = DynamicLinker{},
44
45pub const CpuModel = union(enum) {
46    /// Always native
47    native,
48
49    /// Always baseline
50    baseline,
51
52    /// If CPU Architecture is native, then the CPU model will be native. Otherwise,
53    /// it will be baseline.
54    determined_by_cpu_arch,
55
56    explicit: *const Target.Cpu.Model,
57};
58
59pub const OsVersion = union(enum) {
60    none: void,
61    semver: SemVer,
62    windows: Target.Os.WindowsVersion,
63};
64
65pub const SemVer = std.builtin.Version;
66
67pub const DynamicLinker = Target.DynamicLinker;
68
69pub fn fromTarget(target: Target) CrossTarget {
70    var result: CrossTarget = .{
71        .cpu_arch = target.cpu.arch,
72        .cpu_model = .{ .explicit = target.cpu.model },
73        .os_tag = target.os.tag,
74        .os_version_min = undefined,
75        .os_version_max = undefined,
76        .abi = target.abi,
77        .glibc_version = if (target.isGnuLibC())
78            target.os.version_range.linux.glibc
79        else
80            null,
81    };
82    result.updateOsVersionRange(target.os);
83
84    const all_features = target.cpu.arch.allFeaturesList();
85    var cpu_model_set = target.cpu.model.features;
86    cpu_model_set.populateDependencies(all_features);
87    {
88        // The "add" set is the full set with the CPU Model set removed.
89        const add_set = &result.cpu_features_add;
90        add_set.* = target.cpu.features;
91        add_set.removeFeatureSet(cpu_model_set);
92    }
93    {
94        // The "sub" set is the features that are on in CPU Model set and off in the full set.
95        const sub_set = &result.cpu_features_sub;
96        sub_set.* = cpu_model_set;
97        sub_set.removeFeatureSet(target.cpu.features);
98    }
99    return result;
100}
101
102fn updateOsVersionRange(self: *CrossTarget, os: Target.Os) void {
103    switch (os.tag) {
104        .freestanding,
105        .ananas,
106        .cloudabi,
107        .fuchsia,
108        .kfreebsd,
109        .lv2,
110        .solaris,
111        .zos,
112        .haiku,
113        .minix,
114        .rtems,
115        .nacl,
116        .aix,
117        .cuda,
118        .nvcl,
119        .amdhsa,
120        .ps4,
121        .elfiamcu,
122        .mesa3d,
123        .contiki,
124        .amdpal,
125        .hermit,
126        .hurd,
127        .wasi,
128        .emscripten,
129        .uefi,
130        .opencl,
131        .glsl450,
132        .vulkan,
133        .plan9,
134        .other,
135        => {
136            self.os_version_min = .{ .none = {} };
137            self.os_version_max = .{ .none = {} };
138        },
139
140        .freebsd,
141        .macos,
142        .ios,
143        .tvos,
144        .watchos,
145        .netbsd,
146        .openbsd,
147        .dragonfly,
148        => {
149            self.os_version_min = .{ .semver = os.version_range.semver.min };
150            self.os_version_max = .{ .semver = os.version_range.semver.max };
151        },
152
153        .linux => {
154            self.os_version_min = .{ .semver = os.version_range.linux.range.min };
155            self.os_version_max = .{ .semver = os.version_range.linux.range.max };
156        },
157
158        .windows => {
159            self.os_version_min = .{ .windows = os.version_range.windows.min };
160            self.os_version_max = .{ .windows = os.version_range.windows.max };
161        },
162    }
163}
164
165/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
166pub fn toTarget(self: CrossTarget) Target {
167    return .{
168        .cpu = self.getCpu(),
169        .os = self.getOs(),
170        .abi = self.getAbi(),
171    };
172}
173
174pub const ParseOptions = struct {
175    /// This is sometimes called a "triple". It looks roughly like this:
176    ///     riscv64-linux-musl
177    /// The fields are, respectively:
178    /// * CPU Architecture
179    /// * Operating System (and optional version range)
180    /// * C ABI (optional, with optional glibc version)
181    /// The string "native" can be used for CPU architecture as well as Operating System.
182    /// If the CPU Architecture is specified as "native", then the Operating System and C ABI may be omitted.
183    arch_os_abi: []const u8 = "native",
184
185    /// Looks like "name+a+b-c-d+e", where "name" is a CPU Model name, "a", "b", and "e"
186    /// are examples of CPU features to add to the set, and "c" and "d" are examples of CPU features
187    /// to remove from the set.
188    /// The following special strings are recognized for CPU Model name:
189    /// * "baseline" - The "default" set of CPU features for cross-compiling. A conservative set
190    ///                of features that is expected to be supported on most available hardware.
191    /// * "native"   - The native CPU model is to be detected when compiling.
192    /// If this field is not provided (`null`), then the value will depend on the
193    /// parsed CPU Architecture. If native, then this will be "native". Otherwise, it will be "baseline".
194    cpu_features: ?[]const u8 = null,
195
196    /// Absolute path to dynamic linker, to override the default, which is either a natively
197    /// detected path, or a standard path.
198    dynamic_linker: ?[]const u8 = null,
199
200    /// If this is provided, the function will populate some information about parsing failures,
201    /// so that user-friendly error messages can be delivered.
202    diagnostics: ?*Diagnostics = null,
203
204    pub const Diagnostics = struct {
205        /// If the architecture was determined, this will be populated.
206        arch: ?Target.Cpu.Arch = null,
207
208        /// If the OS name was determined, this will be populated.
209        os_name: ?[]const u8 = null,
210
211        /// If the OS tag was determined, this will be populated.
212        os_tag: ?Target.Os.Tag = null,
213
214        /// If the ABI was determined, this will be populated.
215        abi: ?Target.Abi = null,
216
217        /// If the CPU name was determined, this will be populated.
218        cpu_name: ?[]const u8 = null,
219
220        /// If error.UnknownCpuFeature is returned, this will be populated.
221        unknown_feature_name: ?[]const u8 = null,
222    };
223};
224
225pub fn parse(args: ParseOptions) !CrossTarget {
226    var dummy_diags: ParseOptions.Diagnostics = undefined;
227    const diags = args.diagnostics orelse &dummy_diags;
228
229    var result: CrossTarget = .{
230        .dynamic_linker = DynamicLinker.init(args.dynamic_linker),
231    };
232
233    var it = mem.split(u8, args.arch_os_abi, "-");
234    const arch_name = it.next().?;
235    const arch_is_native = mem.eql(u8, arch_name, "native");
236    if (!arch_is_native) {
237        result.cpu_arch = std.meta.stringToEnum(Target.Cpu.Arch, arch_name) orelse
238            return error.UnknownArchitecture;
239    }
240    const arch = result.getCpuArch();
241    diags.arch = arch;
242
243    if (it.next()) |os_text| {
244        try parseOs(&result, diags, os_text);
245    } else if (!arch_is_native) {
246        return error.MissingOperatingSystem;
247    }
248
249    const opt_abi_text = it.next();
250    if (opt_abi_text) |abi_text| {
251        var abi_it = mem.split(u8, abi_text, ".");
252        const abi = std.meta.stringToEnum(Target.Abi, abi_it.next().?) orelse
253            return error.UnknownApplicationBinaryInterface;
254        result.abi = abi;
255        diags.abi = abi;
256
257        const abi_ver_text = abi_it.rest();
258        if (abi_it.next() != null) {
259            if (result.isGnuLibC()) {
260                result.glibc_version = SemVer.parse(abi_ver_text) catch |err| switch (err) {
261                    error.Overflow => return error.InvalidAbiVersion,
262                    error.InvalidCharacter => return error.InvalidAbiVersion,
263                    error.InvalidVersion => return error.InvalidAbiVersion,
264                };
265            } else {
266                return error.InvalidAbiVersion;
267            }
268        }
269    }
270
271    if (it.next() != null) return error.UnexpectedExtraField;
272
273    if (args.cpu_features) |cpu_features| {
274        const all_features = arch.allFeaturesList();
275        var index: usize = 0;
276        while (index < cpu_features.len and
277            cpu_features[index] != '+' and
278            cpu_features[index] != '-')
279        {
280            index += 1;
281        }
282        const cpu_name = cpu_features[0..index];
283        diags.cpu_name = cpu_name;
284
285        const add_set = &result.cpu_features_add;
286        const sub_set = &result.cpu_features_sub;
287        if (mem.eql(u8, cpu_name, "native")) {
288            result.cpu_model = .native;
289        } else if (mem.eql(u8, cpu_name, "baseline")) {
290            result.cpu_model = .baseline;
291        } else {
292            result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) };
293        }
294
295        while (index < cpu_features.len) {
296            const op = cpu_features[index];
297            const set = switch (op) {
298                '+' => add_set,
299                '-' => sub_set,
300                else => unreachable,
301            };
302            index += 1;
303            const start = index;
304            while (index < cpu_features.len and
305                cpu_features[index] != '+' and
306                cpu_features[index] != '-')
307            {
308                index += 1;
309            }
310            const feature_name = cpu_features[start..index];
311            for (all_features) |feature, feat_index_usize| {
312                const feat_index = @intCast(Target.Cpu.Feature.Set.Index, feat_index_usize);
313                if (mem.eql(u8, feature_name, feature.name)) {
314                    set.addFeature(feat_index);
315                    break;
316                }
317            } else {
318                diags.unknown_feature_name = feature_name;
319                return error.UnknownCpuFeature;
320            }
321        }
322    }
323
324    return result;
325}
326
327/// Similar to `parse` except instead of fully parsing, it only determines the CPU
328/// architecture and returns it if it can be determined, and returns `null` otherwise.
329/// This is intended to be used if the API user of CrossTarget needs to learn the
330/// target CPU architecture in order to fully populate `ParseOptions`.
331pub fn parseCpuArch(args: ParseOptions) ?Target.Cpu.Arch {
332    var it = mem.split(u8, args.arch_os_abi, "-");
333    const arch_name = it.next().?;
334    const arch_is_native = mem.eql(u8, arch_name, "native");
335    if (arch_is_native) {
336        return builtin.cpu.arch;
337    } else {
338        return std.meta.stringToEnum(Target.Cpu.Arch, arch_name);
339    }
340}
341
342/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
343pub fn getCpu(self: CrossTarget) Target.Cpu {
344    switch (self.cpu_model) {
345        .native => {
346            // This works when doing `zig build` because Zig generates a build executable using
347            // native CPU model & features. However this will not be accurate otherwise, and
348            // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
349            return builtin.cpu;
350        },
351        .baseline => {
352            var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch());
353            self.updateCpuFeatures(&adjusted_baseline.features);
354            return adjusted_baseline;
355        },
356        .determined_by_cpu_arch => if (self.cpu_arch == null) {
357            // This works when doing `zig build` because Zig generates a build executable using
358            // native CPU model & features. However this will not be accurate otherwise, and
359            // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
360            return builtin.cpu;
361        } else {
362            var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch());
363            self.updateCpuFeatures(&adjusted_baseline.features);
364            return adjusted_baseline;
365        },
366        .explicit => |model| {
367            var adjusted_model = model.toCpu(self.getCpuArch());
368            self.updateCpuFeatures(&adjusted_model.features);
369            return adjusted_model;
370        },
371    }
372}
373
374pub fn getCpuArch(self: CrossTarget) Target.Cpu.Arch {
375    return self.cpu_arch orelse builtin.cpu.arch;
376}
377
378pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model {
379    return switch (self.cpu_model) {
380        .explicit => |cpu_model| cpu_model,
381        else => self.getCpu().model,
382    };
383}
384
385pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set {
386    return self.getCpu().features;
387}
388
389/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
390pub fn getOs(self: CrossTarget) Target.Os {
391    // `builtin.os` works when doing `zig build` because Zig generates a build executable using
392    // native OS version range. However this will not be accurate otherwise, and
393    // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
394    var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange(self.getCpuArch()) else builtin.os;
395
396    if (self.os_version_min) |min| switch (min) {
397        .none => {},
398        .semver => |semver| switch (self.getOsTag()) {
399            .linux => adjusted_os.version_range.linux.range.min = semver,
400            else => adjusted_os.version_range.semver.min = semver,
401        },
402        .windows => |win_ver| adjusted_os.version_range.windows.min = win_ver,
403    };
404
405    if (self.os_version_max) |max| switch (max) {
406        .none => {},
407        .semver => |semver| switch (self.getOsTag()) {
408            .linux => adjusted_os.version_range.linux.range.max = semver,
409            else => adjusted_os.version_range.semver.max = semver,
410        },
411        .windows => |win_ver| adjusted_os.version_range.windows.max = win_ver,
412    };
413
414    if (self.glibc_version) |glibc| {
415        assert(self.isGnuLibC());
416        adjusted_os.version_range.linux.glibc = glibc;
417    }
418
419    return adjusted_os;
420}
421
422pub fn getOsTag(self: CrossTarget) Target.Os.Tag {
423    return self.os_tag orelse builtin.os.tag;
424}
425
426/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
427pub fn getOsVersionMin(self: CrossTarget) OsVersion {
428    if (self.os_version_min) |version_min| return version_min;
429    var tmp: CrossTarget = undefined;
430    tmp.updateOsVersionRange(self.getOs());
431    return tmp.os_version_min.?;
432}
433
434/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
435pub fn getOsVersionMax(self: CrossTarget) OsVersion {
436    if (self.os_version_max) |version_max| return version_max;
437    var tmp: CrossTarget = undefined;
438    tmp.updateOsVersionRange(self.getOs());
439    return tmp.os_version_max.?;
440}
441
442/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
443pub fn getAbi(self: CrossTarget) Target.Abi {
444    if (self.abi) |abi| return abi;
445
446    if (self.os_tag == null) {
447        // This works when doing `zig build` because Zig generates a build executable using
448        // native CPU model & features. However this will not be accurate otherwise, and
449        // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
450        return builtin.abi;
451    }
452
453    return Target.Abi.default(self.getCpuArch(), self.getOs());
454}
455
456pub fn isFreeBSD(self: CrossTarget) bool {
457    return self.getOsTag() == .freebsd;
458}
459
460pub fn isDarwin(self: CrossTarget) bool {
461    return self.getOsTag().isDarwin();
462}
463
464pub fn isNetBSD(self: CrossTarget) bool {
465    return self.getOsTag() == .netbsd;
466}
467
468pub fn isOpenBSD(self: CrossTarget) bool {
469    return self.getOsTag() == .openbsd;
470}
471
472pub fn isUefi(self: CrossTarget) bool {
473    return self.getOsTag() == .uefi;
474}
475
476pub fn isDragonFlyBSD(self: CrossTarget) bool {
477    return self.getOsTag() == .dragonfly;
478}
479
480pub fn isLinux(self: CrossTarget) bool {
481    return self.getOsTag() == .linux;
482}
483
484pub fn isWindows(self: CrossTarget) bool {
485    return self.getOsTag() == .windows;
486}
487
488pub fn exeFileExt(self: CrossTarget) [:0]const u8 {
489    return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag());
490}
491
492pub fn staticLibSuffix(self: CrossTarget) [:0]const u8 {
493    return Target.staticLibSuffix_os_abi(self.getOsTag(), self.getAbi());
494}
495
496pub fn dynamicLibSuffix(self: CrossTarget) [:0]const u8 {
497    return self.getOsTag().dynamicLibSuffix();
498}
499
500pub fn libPrefix(self: CrossTarget) [:0]const u8 {
501    return Target.libPrefix_os_abi(self.getOsTag(), self.getAbi());
502}
503
504pub fn isNativeCpu(self: CrossTarget) bool {
505    return self.cpu_arch == null and
506        (self.cpu_model == .native or self.cpu_model == .determined_by_cpu_arch) and
507        self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty();
508}
509
510pub fn isNativeOs(self: CrossTarget) bool {
511    return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and
512        self.dynamic_linker.get() == null and self.glibc_version == null;
513}
514
515pub fn isNativeAbi(self: CrossTarget) bool {
516    return self.os_tag == null and self.abi == null;
517}
518
519pub fn isNative(self: CrossTarget) bool {
520    return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi();
521}
522
523pub fn zigTriple(self: CrossTarget, allocator: mem.Allocator) error{OutOfMemory}![]u8 {
524    if (self.isNative()) {
525        return allocator.dupe(u8, "native");
526    }
527
528    const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native";
529    const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native";
530
531    var result = std.ArrayList(u8).init(allocator);
532    defer result.deinit();
533
534    try result.writer().print("{s}-{s}", .{ arch_name, os_name });
535
536    // The zig target syntax does not allow specifying a max os version with no min, so
537    // if either are present, we need the min.
538    if (self.os_version_min != null or self.os_version_max != null) {
539        switch (self.getOsVersionMin()) {
540            .none => {},
541            .semver => |v| try result.writer().print(".{}", .{v}),
542            .windows => |v| try result.writer().print("{s}", .{v}),
543        }
544    }
545    if (self.os_version_max) |max| {
546        switch (max) {
547            .none => {},
548            .semver => |v| try result.writer().print("...{}", .{v}),
549            .windows => |v| try result.writer().print("..{s}", .{v}),
550        }
551    }
552
553    if (self.glibc_version) |v| {
554        try result.writer().print("-{s}.{}", .{ @tagName(self.getAbi()), v });
555    } else if (self.abi) |abi| {
556        try result.writer().print("-{s}", .{@tagName(abi)});
557    }
558
559    return result.toOwnedSlice();
560}
561
562pub fn allocDescription(self: CrossTarget, allocator: mem.Allocator) ![]u8 {
563    // TODO is there anything else worthy of the description that is not
564    // already captured in the triple?
565    return self.zigTriple(allocator);
566}
567
568pub fn linuxTriple(self: CrossTarget, allocator: mem.Allocator) ![]u8 {
569    return Target.linuxTripleSimple(allocator, self.getCpuArch(), self.getOsTag(), self.getAbi());
570}
571
572pub fn wantSharedLibSymLinks(self: CrossTarget) bool {
573    return self.getOsTag() != .windows;
574}
575
576pub const VcpkgLinkage = std.builtin.LinkMode;
577
578/// Returned slice must be freed by the caller.
579pub fn vcpkgTriplet(self: CrossTarget, allocator: mem.Allocator, linkage: VcpkgLinkage) ![]u8 {
580    const arch = switch (self.getCpuArch()) {
581        .i386 => "x86",
582        .x86_64 => "x64",
583
584        .arm,
585        .armeb,
586        .thumb,
587        .thumbeb,
588        .aarch64_32,
589        => "arm",
590
591        .aarch64,
592        .aarch64_be,
593        => "arm64",
594
595        else => return error.UnsupportedVcpkgArchitecture,
596    };
597
598    const os = switch (self.getOsTag()) {
599        .windows => "windows",
600        .linux => "linux",
601        .macos => "macos",
602        else => return error.UnsupportedVcpkgOperatingSystem,
603    };
604
605    const static_suffix = switch (linkage) {
606        .Static => "-static",
607        .Dynamic => "",
608    };
609
610    return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix });
611}
612
613pub fn isGnuLibC(self: CrossTarget) bool {
614    return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi());
615}
616
617pub fn setGnuLibCVersion(self: *CrossTarget, major: u32, minor: u32, patch: u32) void {
618    assert(self.isGnuLibC());
619    self.glibc_version = SemVer{ .major = major, .minor = minor, .patch = patch };
620}
621
622pub fn getObjectFormat(self: CrossTarget) Target.ObjectFormat {
623    return Target.getObjectFormatSimple(self.getOsTag(), self.getCpuArch());
624}
625
626pub fn updateCpuFeatures(self: CrossTarget, set: *Target.Cpu.Feature.Set) void {
627    set.removeFeatureSet(self.cpu_features_sub);
628    set.addFeatureSet(self.cpu_features_add);
629    set.populateDependencies(self.getCpuArch().allFeaturesList());
630    set.removeFeatureSet(self.cpu_features_sub);
631}
632
633fn parseOs(result: *CrossTarget, diags: *ParseOptions.Diagnostics, text: []const u8) !void {
634    var it = mem.split(u8, text, ".");
635    const os_name = it.next().?;
636    diags.os_name = os_name;
637    const os_is_native = mem.eql(u8, os_name, "native");
638    if (!os_is_native) {
639        result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse
640            return error.UnknownOperatingSystem;
641    }
642    const tag = result.getOsTag();
643    diags.os_tag = tag;
644
645    const version_text = it.rest();
646    if (it.next() == null) return;
647
648    switch (tag) {
649        .freestanding,
650        .ananas,
651        .cloudabi,
652        .fuchsia,
653        .kfreebsd,
654        .lv2,
655        .solaris,
656        .zos,
657        .haiku,
658        .minix,
659        .rtems,
660        .nacl,
661        .aix,
662        .cuda,
663        .nvcl,
664        .amdhsa,
665        .ps4,
666        .elfiamcu,
667        .mesa3d,
668        .contiki,
669        .amdpal,
670        .hermit,
671        .hurd,
672        .wasi,
673        .emscripten,
674        .uefi,
675        .opencl,
676        .glsl450,
677        .vulkan,
678        .plan9,
679        .other,
680        => return error.InvalidOperatingSystemVersion,
681
682        .freebsd,
683        .macos,
684        .ios,
685        .tvos,
686        .watchos,
687        .netbsd,
688        .openbsd,
689        .linux,
690        .dragonfly,
691        => {
692            var range_it = mem.split(u8, version_text, "...");
693
694            const min_text = range_it.next().?;
695            const min_ver = SemVer.parse(min_text) catch |err| switch (err) {
696                error.Overflow => return error.InvalidOperatingSystemVersion,
697                error.InvalidCharacter => return error.InvalidOperatingSystemVersion,
698                error.InvalidVersion => return error.InvalidOperatingSystemVersion,
699            };
700            result.os_version_min = .{ .semver = min_ver };
701
702            const max_text = range_it.next() orelse return;
703            const max_ver = SemVer.parse(max_text) catch |err| switch (err) {
704                error.Overflow => return error.InvalidOperatingSystemVersion,
705                error.InvalidCharacter => return error.InvalidOperatingSystemVersion,
706                error.InvalidVersion => return error.InvalidOperatingSystemVersion,
707            };
708            result.os_version_max = .{ .semver = max_ver };
709        },
710
711        .windows => {
712            var range_it = mem.split(u8, version_text, "...");
713
714            const min_text = range_it.next().?;
715            const min_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, min_text) orelse
716                return error.InvalidOperatingSystemVersion;
717            result.os_version_min = .{ .windows = min_ver };
718
719            const max_text = range_it.next() orelse return;
720            const max_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, max_text) orelse
721                return error.InvalidOperatingSystemVersion;
722            result.os_version_max = .{ .windows = max_ver };
723        },
724    }
725}
726
727test "CrossTarget.parse" {
728    if (builtin.target.isGnuLibC()) {
729        var cross_target = try CrossTarget.parse(.{});
730        cross_target.setGnuLibCVersion(2, 1, 1);
731
732        const text = try cross_target.zigTriple(std.testing.allocator);
733        defer std.testing.allocator.free(text);
734
735        var buf: [256]u8 = undefined;
736        const triple = std.fmt.bufPrint(
737            buf[0..],
738            "native-native-{s}.2.1.1",
739            .{@tagName(builtin.abi)},
740        ) catch unreachable;
741
742        try std.testing.expectEqualSlices(u8, triple, text);
743    }
744    {
745        const cross_target = try CrossTarget.parse(.{
746            .arch_os_abi = "aarch64-linux",
747            .cpu_features = "native",
748        });
749
750        try std.testing.expect(cross_target.cpu_arch.? == .aarch64);
751        try std.testing.expect(cross_target.cpu_model == .native);
752    }
753    {
754        const cross_target = try CrossTarget.parse(.{ .arch_os_abi = "native" });
755
756        try std.testing.expect(cross_target.cpu_arch == null);
757        try std.testing.expect(cross_target.isNative());
758
759        const text = try cross_target.zigTriple(std.testing.allocator);
760        defer std.testing.allocator.free(text);
761        try std.testing.expectEqualSlices(u8, "native", text);
762    }
763    {
764        const cross_target = try CrossTarget.parse(.{
765            .arch_os_abi = "x86_64-linux-gnu",
766            .cpu_features = "x86_64-sse-sse2-avx-cx8",
767        });
768        const target = cross_target.toTarget();
769
770        try std.testing.expect(target.os.tag == .linux);
771        try std.testing.expect(target.abi == .gnu);
772        try std.testing.expect(target.cpu.arch == .x86_64);
773        try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse));
774        try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx));
775        try std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8));
776        try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov));
777        try std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr));
778
779        try std.testing.expect(Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx, .cmov }));
780        try std.testing.expect(!Target.x86.featureSetHasAny(target.cpu.features, .{ .sse, .avx }));
781        try std.testing.expect(Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87 }));
782        try std.testing.expect(!Target.x86.featureSetHasAll(target.cpu.features, .{ .mmx, .x87, .sse }));
783
784        const text = try cross_target.zigTriple(std.testing.allocator);
785        defer std.testing.allocator.free(text);
786        try std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text);
787    }
788    {
789        const cross_target = try CrossTarget.parse(.{
790            .arch_os_abi = "arm-linux-musleabihf",
791            .cpu_features = "generic+v8a",
792        });
793        const target = cross_target.toTarget();
794
795        try std.testing.expect(target.os.tag == .linux);
796        try std.testing.expect(target.abi == .musleabihf);
797        try std.testing.expect(target.cpu.arch == .arm);
798        try std.testing.expect(target.cpu.model == &Target.arm.cpu.generic);
799        try std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a));
800
801        const text = try cross_target.zigTriple(std.testing.allocator);
802        defer std.testing.allocator.free(text);
803        try std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text);
804    }
805    {
806        const cross_target = try CrossTarget.parse(.{
807            .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
808            .cpu_features = "generic+v8a",
809        });
810        const target = cross_target.toTarget();
811
812        try std.testing.expect(target.cpu.arch == .aarch64);
813        try std.testing.expect(target.os.tag == .linux);
814        try std.testing.expect(target.os.version_range.linux.range.min.major == 3);
815        try std.testing.expect(target.os.version_range.linux.range.min.minor == 10);
816        try std.testing.expect(target.os.version_range.linux.range.min.patch == 0);
817        try std.testing.expect(target.os.version_range.linux.range.max.major == 4);
818        try std.testing.expect(target.os.version_range.linux.range.max.minor == 4);
819        try std.testing.expect(target.os.version_range.linux.range.max.patch == 1);
820        try std.testing.expect(target.os.version_range.linux.glibc.major == 2);
821        try std.testing.expect(target.os.version_range.linux.glibc.minor == 27);
822        try std.testing.expect(target.os.version_range.linux.glibc.patch == 0);
823        try std.testing.expect(target.abi == .gnu);
824
825        const text = try cross_target.zigTriple(std.testing.allocator);
826        defer std.testing.allocator.free(text);
827        try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text);
828    }
829}
830