master
  1//! Contains all the same data as `Target`, additionally introducing the
  2//! concept of "the native target". The purpose of this abstraction is to
  3//! provide meaningful and unsurprising defaults. This struct does reference
  4//! any resources and it is copyable.
  5
  6const Query = @This();
  7const std = @import("../std.zig");
  8const builtin = @import("builtin");
  9const assert = std.debug.assert;
 10const Target = std.Target;
 11const mem = std.mem;
 12const Allocator = std.mem.Allocator;
 13const ArrayList = std.ArrayList;
 14
 15/// `null` means native.
 16cpu_arch: ?Target.Cpu.Arch = null,
 17
 18cpu_model: CpuModel = .determined_by_arch_os,
 19
 20/// Sparse set of CPU features to add to the set from `cpu_model`.
 21cpu_features_add: Target.Cpu.Feature.Set = .empty,
 22
 23/// Sparse set of CPU features to remove from the set from `cpu_model`.
 24cpu_features_sub: Target.Cpu.Feature.Set = .empty,
 25
 26/// `null` means native.
 27os_tag: ?Target.Os.Tag = null,
 28
 29/// `null` means the default version range for `os_tag`. If `os_tag` is `null` (native)
 30/// then `null` for this field means native.
 31os_version_min: ?OsVersion = null,
 32
 33/// When cross compiling, `null` means default (latest known OS version).
 34/// When `os_tag` is native, `null` means equal to the native OS version.
 35os_version_max: ?OsVersion = null,
 36
 37/// `null` means default when cross compiling, or native when `os_tag` is native.
 38/// If `isGnu()` is `false`, this must be `null` and is ignored.
 39glibc_version: ?SemanticVersion = null,
 40
 41/// `null` means default when cross compiling, or native when `os_tag` is native.
 42/// If `isAndroid()` is `false`, this must be `null` and is ignored.
 43android_api_level: ?u32 = null,
 44
 45/// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI.
 46abi: ?Target.Abi = null,
 47
 48/// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path
 49/// based on the `os_tag`.  When `dynamic_linker` is a non-`null` empty string, no dynamic
 50/// linker is used regardless of `os_tag`.
 51dynamic_linker: ?Target.DynamicLinker = null,
 52
 53/// `null` means default for the cpu/arch/os combo.
 54ofmt: ?Target.ObjectFormat = null,
 55
 56pub const CpuModel = union(enum) {
 57    /// Always native
 58    native,
 59
 60    /// Always baseline
 61    baseline,
 62
 63    /// If CPU Architecture is native, then the CPU model will be native. Otherwise,
 64    /// it will be baseline.
 65    determined_by_arch_os,
 66
 67    explicit: *const Target.Cpu.Model,
 68
 69    pub fn eql(a: CpuModel, b: CpuModel) bool {
 70        const Tag = @typeInfo(CpuModel).@"union".tag_type.?;
 71        const a_tag: Tag = a;
 72        const b_tag: Tag = b;
 73        if (a_tag != b_tag) return false;
 74        return switch (a) {
 75            .native, .baseline, .determined_by_arch_os => true,
 76            .explicit => |a_model| a_model == b.explicit,
 77        };
 78    }
 79};
 80
 81pub const OsVersion = union(enum) {
 82    none: void,
 83    semver: SemanticVersion,
 84    windows: Target.Os.WindowsVersion,
 85
 86    pub fn eql(a: OsVersion, b: OsVersion) bool {
 87        const Tag = @typeInfo(OsVersion).@"union".tag_type.?;
 88        const a_tag: Tag = a;
 89        const b_tag: Tag = b;
 90        if (a_tag != b_tag) return false;
 91        return switch (a) {
 92            .none => true,
 93            .semver => |a_semver| a_semver.order(b.semver) == .eq,
 94            .windows => |a_windows| a_windows == b.windows,
 95        };
 96    }
 97
 98    pub fn eqlOpt(a: ?OsVersion, b: ?OsVersion) bool {
 99        if (a == null and b == null) return true;
100        if (a == null or b == null) return false;
101        return OsVersion.eql(a.?, b.?);
102    }
103};
104
105pub const SemanticVersion = std.SemanticVersion;
106
107pub fn fromTarget(target: *const Target) Query {
108    var result: Query = .{
109        .cpu_arch = target.cpu.arch,
110        .cpu_model = .{ .explicit = target.cpu.model },
111        .os_tag = target.os.tag,
112        .os_version_min = undefined,
113        .os_version_max = undefined,
114        .abi = target.abi,
115        .glibc_version = if (target.abi.isGnu()) target.os.versionRange().gnuLibCVersion() else null,
116        .android_api_level = if (target.abi.isAndroid()) target.os.version_range.linux.android else null,
117    };
118    result.updateOsVersionRange(target.os);
119
120    const all_features = target.cpu.arch.allFeaturesList();
121    var cpu_model_set = target.cpu.model.features;
122    cpu_model_set.populateDependencies(all_features);
123    {
124        // The "add" set is the full set with the CPU Model set removed.
125        const add_set = &result.cpu_features_add;
126        add_set.* = target.cpu.features;
127        add_set.removeFeatureSet(cpu_model_set);
128    }
129    {
130        // The "sub" set is the features that are on in CPU Model set and off in the full set.
131        const sub_set = &result.cpu_features_sub;
132        sub_set.* = cpu_model_set;
133        sub_set.removeFeatureSet(target.cpu.features);
134    }
135    return result;
136}
137
138fn updateOsVersionRange(self: *Query, os: Target.Os) void {
139    self.os_version_min, self.os_version_max = switch (os.tag.versionRangeTag()) {
140        .none => .{ .{ .none = {} }, .{ .none = {} } },
141        .semver => .{
142            .{ .semver = os.version_range.semver.min },
143            .{ .semver = os.version_range.semver.max },
144        },
145        inline .hurd, .linux => |t| .{
146            .{ .semver = @field(os.version_range, @tagName(t)).range.min },
147            .{ .semver = @field(os.version_range, @tagName(t)).range.max },
148        },
149        .windows => .{
150            .{ .windows = os.version_range.windows.min },
151            .{ .windows = os.version_range.windows.max },
152        },
153    };
154}
155
156pub const ParseOptions = struct {
157    /// This is sometimes called a "triple". It looks roughly like this:
158    ///     riscv64-linux-musl
159    /// The fields are, respectively:
160    /// * CPU Architecture
161    /// * Operating System (and optional version range)
162    /// * C ABI (optional, with optional glibc version or Android API level)
163    /// The string "native" can be used for CPU architecture as well as Operating System.
164    /// If the CPU Architecture is specified as "native", then the Operating System and C ABI may be omitted.
165    arch_os_abi: []const u8 = "native",
166
167    /// Looks like "name+a+b-c-d+e", where "name" is a CPU Model name, "a", "b", and "e"
168    /// are examples of CPU features to add to the set, and "c" and "d" are examples of CPU features
169    /// to remove from the set.
170    /// The following special strings are recognized for CPU Model name:
171    /// * "baseline" - The "default" set of CPU features for cross-compiling. A conservative set
172    ///                of features that is expected to be supported on most available hardware.
173    /// * "native"   - The native CPU model is to be detected when compiling.
174    /// If this field is not provided (`null`), then the value will depend on the
175    /// parsed CPU Architecture. If native, then this will be "native". Otherwise, it will be "baseline".
176    cpu_features: ?[]const u8 = null,
177
178    /// Absolute path to dynamic linker, to override the default, which is either a natively
179    /// detected path, or a standard path.
180    dynamic_linker: ?[]const u8 = null,
181
182    object_format: ?[]const u8 = null,
183
184    /// If this is provided, the function will populate some information about parsing failures,
185    /// so that user-friendly error messages can be delivered.
186    diagnostics: ?*Diagnostics = null,
187
188    pub const Diagnostics = struct {
189        /// If the architecture was determined, this will be populated.
190        arch: ?Target.Cpu.Arch = null,
191
192        /// If the OS name was determined, this will be populated.
193        os_name: ?[]const u8 = null,
194
195        /// If the OS tag was determined, this will be populated.
196        os_tag: ?Target.Os.Tag = null,
197
198        /// If the ABI was determined, this will be populated.
199        abi: ?Target.Abi = null,
200
201        /// If the CPU name was determined, this will be populated.
202        cpu_name: ?[]const u8 = null,
203
204        /// If error.UnknownCpuFeature is returned, this will be populated.
205        unknown_feature_name: ?[]const u8 = null,
206
207        /// If error.UnknownArchitecture is returned, this will be populated.
208        unknown_architecture_name: ?[]const u8 = null,
209    };
210};
211
212pub fn parse(args: ParseOptions) !Query {
213    var dummy_diags: ParseOptions.Diagnostics = undefined;
214    const diags = args.diagnostics orelse &dummy_diags;
215
216    var result: Query = .{
217        .dynamic_linker = if (args.dynamic_linker) |dynamic_linker| .init(dynamic_linker) else null,
218    };
219
220    var it = mem.splitScalar(u8, args.arch_os_abi, '-');
221    const arch_name = it.first();
222    const arch_is_native = mem.eql(u8, arch_name, "native");
223    if (!arch_is_native) {
224        result.cpu_arch = std.meta.stringToEnum(Target.Cpu.Arch, arch_name) orelse {
225            diags.unknown_architecture_name = arch_name;
226            return error.UnknownArchitecture;
227        };
228    }
229    const arch = result.cpu_arch orelse builtin.cpu.arch;
230    diags.arch = arch;
231
232    if (it.next()) |os_text| {
233        try parseOs(&result, diags, os_text);
234    } else if (!arch_is_native) {
235        return error.MissingOperatingSystem;
236    }
237
238    const opt_abi_text = it.next();
239    if (opt_abi_text) |abi_text| {
240        var abi_it = mem.splitScalar(u8, abi_text, '.');
241        const abi = std.meta.stringToEnum(Target.Abi, abi_it.first()) orelse
242            return error.UnknownApplicationBinaryInterface;
243        result.abi = abi;
244        diags.abi = abi;
245
246        const abi_ver_text = abi_it.rest();
247        if (abi_it.next() != null) {
248            if (abi.isGnu()) {
249                result.glibc_version = parseVersion(abi_ver_text) catch |err| switch (err) {
250                    error.Overflow => return error.InvalidAbiVersion,
251                    error.InvalidVersion => return error.InvalidAbiVersion,
252                };
253            } else if (abi.isAndroid()) {
254                result.android_api_level = std.fmt.parseUnsigned(u32, abi_ver_text, 10) catch |err| switch (err) {
255                    error.InvalidCharacter => return error.InvalidVersion,
256                    error.Overflow => return error.Overflow,
257                };
258            } else {
259                return error.InvalidAbiVersion;
260            }
261        }
262    }
263
264    if (it.next() != null) return error.UnexpectedExtraField;
265
266    if (args.cpu_features) |cpu_features| {
267        const all_features = arch.allFeaturesList();
268        var index: usize = 0;
269        while (index < cpu_features.len and
270            cpu_features[index] != '+' and
271            cpu_features[index] != '-')
272        {
273            index += 1;
274        }
275        const cpu_name = cpu_features[0..index];
276        diags.cpu_name = cpu_name;
277
278        const add_set = &result.cpu_features_add;
279        const sub_set = &result.cpu_features_sub;
280        if (mem.eql(u8, cpu_name, "native")) {
281            result.cpu_model = .native;
282        } else if (mem.eql(u8, cpu_name, "baseline")) {
283            result.cpu_model = .baseline;
284        } else {
285            result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) };
286        }
287
288        while (index < cpu_features.len) {
289            const op = cpu_features[index];
290            const set = switch (op) {
291                '+' => add_set,
292                '-' => sub_set,
293                else => unreachable,
294            };
295            index += 1;
296            const start = index;
297            while (index < cpu_features.len and
298                cpu_features[index] != '+' and
299                cpu_features[index] != '-')
300            {
301                index += 1;
302            }
303            const feature_name = cpu_features[start..index];
304            for (all_features, 0..) |feature, feat_index_usize| {
305                const feat_index = @as(Target.Cpu.Feature.Set.Index, @intCast(feat_index_usize));
306                if (mem.eql(u8, feature_name, feature.name)) {
307                    set.addFeature(feat_index);
308                    break;
309                }
310            } else {
311                diags.unknown_feature_name = feature_name;
312                return error.UnknownCpuFeature;
313            }
314        }
315    }
316
317    if (args.object_format) |ofmt_name| {
318        result.ofmt = std.meta.stringToEnum(Target.ObjectFormat, ofmt_name) orelse
319            return error.UnknownObjectFormat;
320    }
321
322    return result;
323}
324
325/// Similar to `parse` except instead of fully parsing, it only determines the CPU
326/// architecture and returns it if it can be determined, and returns `null` otherwise.
327/// This is intended to be used if the API user of Query needs to learn the
328/// target CPU architecture in order to fully populate `ParseOptions`.
329pub fn parseCpuArch(args: ParseOptions) ?Target.Cpu.Arch {
330    var it = mem.splitScalar(u8, args.arch_os_abi, '-');
331    const arch_name = it.first();
332    const arch_is_native = mem.eql(u8, arch_name, "native");
333    if (arch_is_native) {
334        return builtin.cpu.arch;
335    } else {
336        return std.meta.stringToEnum(Target.Cpu.Arch, arch_name);
337    }
338}
339
340/// Similar to `SemanticVersion.parse`, but with following changes:
341/// * Leading zeroes are allowed.
342/// * Supports only 2 or 3 version components (major, minor, [patch]). If 3-rd component is omitted, it will be 0.
343/// * Prerelease and build components are disallowed.
344pub fn parseVersion(ver: []const u8) error{ InvalidVersion, Overflow }!SemanticVersion {
345    const parseVersionComponentFn = (struct {
346        fn parseVersionComponentInner(component: []const u8) error{ InvalidVersion, Overflow }!usize {
347            return std.fmt.parseUnsigned(usize, component, 10) catch |err| switch (err) {
348                error.InvalidCharacter => return error.InvalidVersion,
349                error.Overflow => return error.Overflow,
350            };
351        }
352    }).parseVersionComponentInner;
353
354    var version_components = mem.splitScalar(u8, ver, '.');
355
356    const major = version_components.first();
357    const minor = version_components.next() orelse return error.InvalidVersion;
358    const patch = version_components.next() orelse "0";
359    if (version_components.next() != null) return error.InvalidVersion;
360
361    return .{
362        .major = try parseVersionComponentFn(major),
363        .minor = try parseVersionComponentFn(minor),
364        .patch = try parseVersionComponentFn(patch),
365    };
366}
367
368test parseVersion {
369    try std.testing.expectEqual(SemanticVersion{ .major = 1, .minor = 2, .patch = 0 }, try parseVersion("1.2"));
370    try std.testing.expectEqual(SemanticVersion{ .major = 1, .minor = 2, .patch = 3 }, try parseVersion("1.2.3"));
371
372    try std.testing.expectError(error.InvalidVersion, parseVersion("1"));
373    try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3.4"));
374    try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3-dev"));
375}
376
377pub fn isNativeCpu(self: Query) bool {
378    return self.cpu_arch == null and
379        (self.cpu_model == .native or self.cpu_model == .determined_by_arch_os) and
380        self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty();
381}
382
383pub fn isNativeOs(self: Query) bool {
384    return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and
385        self.dynamic_linker == null and self.glibc_version == null and self.android_api_level == null;
386}
387
388pub fn isNativeAbi(self: Query) bool {
389    return self.os_tag == null and self.abi == null;
390}
391
392pub fn isNativeTriple(self: Query) bool {
393    return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi();
394}
395
396pub fn isNative(self: Query) bool {
397    return self.isNativeTriple() and self.ofmt == null;
398}
399
400pub fn canDetectLibC(self: Query) bool {
401    if (self.isNativeOs()) return true;
402    if (self.os_tag) |os| {
403        if (builtin.os.tag == .macos and os.isDarwin()) return true;
404        if (os == .linux) {
405            if (self.abi) |abi| if (abi.isAndroid()) return true;
406        }
407    }
408    return false;
409}
410
411/// Formats a version with the patch component omitted if it is zero,
412/// unlike SemanticVersion.format which formats all its version components regardless.
413fn formatVersion(version: SemanticVersion, gpa: Allocator, list: *ArrayList(u8)) !void {
414    if (version.patch == 0) {
415        try list.print(gpa, "{d}.{d}", .{ version.major, version.minor });
416    } else {
417        try list.print(gpa, "{d}.{d}.{d}", .{ version.major, version.minor, version.patch });
418    }
419}
420
421pub fn zigTriple(self: Query, gpa: Allocator) Allocator.Error![]u8 {
422    if (self.isNativeTriple()) return gpa.dupe(u8, "native");
423
424    const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native";
425    const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native";
426
427    var result: ArrayList(u8) = .empty;
428    defer result.deinit(gpa);
429
430    try result.print(gpa, "{s}-{s}", .{ arch_name, os_name });
431
432    // The zig target syntax does not allow specifying a max os version with no min, so
433    // if either are present, we need the min.
434    if (self.os_version_min) |min| {
435        switch (min) {
436            .none => {},
437            .semver => |v| {
438                try result.appendSlice(gpa, ".");
439                try formatVersion(v, gpa, &result);
440            },
441            .windows => |v| {
442                try result.print(gpa, "{f}", .{v});
443            },
444        }
445    }
446    if (self.os_version_max) |max| {
447        switch (max) {
448            .none => {},
449            .semver => |v| {
450                try result.appendSlice(gpa, "...");
451                try formatVersion(v, gpa, &result);
452            },
453            .windows => |v| {
454                // This is counting on a custom format() function defined on `WindowsVersion`
455                // to add a prefix '.' and make there be a total of three dots.
456                try result.print(gpa, "..{f}", .{v});
457            },
458        }
459    }
460
461    if (self.glibc_version) |v| {
462        const name = if (self.abi) |abi| @tagName(abi) else "gnu";
463        try result.ensureUnusedCapacity(gpa, name.len + 2);
464        result.appendAssumeCapacity('-');
465        result.appendSliceAssumeCapacity(name);
466        result.appendAssumeCapacity('.');
467        try formatVersion(v, gpa, &result);
468    } else if (self.android_api_level) |lvl| {
469        const name = if (self.abi) |abi| @tagName(abi) else "android";
470        try result.ensureUnusedCapacity(gpa, name.len + 2);
471        result.appendAssumeCapacity('-');
472        result.appendSliceAssumeCapacity(name);
473        result.appendAssumeCapacity('.');
474        try result.print(gpa, "{d}", .{lvl});
475    } else if (self.abi) |abi| {
476        const name = @tagName(abi);
477        try result.ensureUnusedCapacity(gpa, name.len + 1);
478        result.appendAssumeCapacity('-');
479        result.appendSliceAssumeCapacity(name);
480    }
481
482    return result.toOwnedSlice(gpa);
483}
484
485/// Renders the query into a textual representation that can be parsed via the
486/// `-mcpu` flag passed to the Zig compiler.
487/// Appends the result to `buffer`.
488pub fn serializeCpu(q: Query, buffer: *std.array_list.Managed(u8)) Allocator.Error!void {
489    try buffer.ensureUnusedCapacity(8);
490    switch (q.cpu_model) {
491        .native => {
492            buffer.appendSliceAssumeCapacity("native");
493        },
494        .baseline => {
495            buffer.appendSliceAssumeCapacity("baseline");
496        },
497        .determined_by_arch_os => {
498            if (q.cpu_arch == null) {
499                buffer.appendSliceAssumeCapacity("native");
500            } else {
501                buffer.appendSliceAssumeCapacity("baseline");
502            }
503        },
504        .explicit => |model| {
505            try buffer.appendSlice(model.name);
506        },
507    }
508
509    if (q.cpu_features_add.isEmpty() and q.cpu_features_sub.isEmpty()) {
510        // The CPU name alone is sufficient.
511        return;
512    }
513
514    const cpu_arch = q.cpu_arch orelse builtin.cpu.arch;
515    const all_features = cpu_arch.allFeaturesList();
516
517    for (all_features, 0..) |feature, i_usize| {
518        const i: Target.Cpu.Feature.Set.Index = @intCast(i_usize);
519        try buffer.ensureUnusedCapacity(feature.name.len + 1);
520        if (q.cpu_features_sub.isEnabled(i)) {
521            buffer.appendAssumeCapacity('-');
522            buffer.appendSliceAssumeCapacity(feature.name);
523        } else if (q.cpu_features_add.isEnabled(i)) {
524            buffer.appendAssumeCapacity('+');
525            buffer.appendSliceAssumeCapacity(feature.name);
526        }
527    }
528}
529
530pub fn serializeCpuAlloc(q: Query, ally: Allocator) Allocator.Error![]u8 {
531    var buffer = std.array_list.Managed(u8).init(ally);
532    try serializeCpu(q, &buffer);
533    return buffer.toOwnedSlice();
534}
535
536pub fn allocDescription(self: Query, allocator: Allocator) ![]u8 {
537    // TODO is there anything else worthy of the description that is not
538    // already captured in the triple?
539    return self.zigTriple(allocator);
540}
541
542pub fn setGnuLibCVersion(self: *Query, major: u32, minor: u32, patch: u32) void {
543    self.glibc_version = SemanticVersion{ .major = major, .minor = minor, .patch = patch };
544}
545
546fn parseOs(result: *Query, diags: *ParseOptions.Diagnostics, text: []const u8) !void {
547    var it = mem.splitScalar(u8, text, '.');
548    const os_name = it.first();
549    diags.os_name = os_name;
550    const os_is_native = mem.eql(u8, os_name, "native");
551    if (!os_is_native) {
552        result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse
553            return error.UnknownOperatingSystem;
554    }
555    const tag = result.os_tag orelse builtin.os.tag;
556    diags.os_tag = tag;
557
558    const version_text = it.rest();
559    if (version_text.len > 0) switch (tag.versionRangeTag()) {
560        .none => return error.InvalidOperatingSystemVersion,
561        .semver, .hurd, .linux => {
562            var range_it = mem.splitSequence(u8, version_text, "...");
563            result.os_version_min = .{
564                .semver = parseVersion(range_it.first()) catch |err| switch (err) {
565                    error.Overflow => return error.InvalidOperatingSystemVersion,
566                    error.InvalidVersion => return error.InvalidOperatingSystemVersion,
567                },
568            };
569            if (range_it.next()) |v| {
570                result.os_version_max = .{
571                    .semver = parseVersion(v) catch |err| switch (err) {
572                        error.Overflow => return error.InvalidOperatingSystemVersion,
573                        error.InvalidVersion => return error.InvalidOperatingSystemVersion,
574                    },
575                };
576            }
577        },
578        .windows => {
579            var range_it = mem.splitSequence(u8, version_text, "...");
580            result.os_version_min = .{
581                .windows = try Target.Os.WindowsVersion.parse(range_it.first()),
582            };
583            if (range_it.next()) |v| {
584                result.os_version_max = .{
585                    .windows = try Target.Os.WindowsVersion.parse(v),
586                };
587            }
588        },
589    };
590}
591
592pub fn eql(a: Query, b: Query) bool {
593    if (a.cpu_arch != b.cpu_arch) return false;
594    if (!a.cpu_model.eql(b.cpu_model)) return false;
595    if (!a.cpu_features_add.eql(b.cpu_features_add)) return false;
596    if (!a.cpu_features_sub.eql(b.cpu_features_sub)) return false;
597    if (a.os_tag != b.os_tag) return false;
598    if (!OsVersion.eqlOpt(a.os_version_min, b.os_version_min)) return false;
599    if (!OsVersion.eqlOpt(a.os_version_max, b.os_version_max)) return false;
600    if (!versionEqualOpt(a.glibc_version, b.glibc_version)) return false;
601    if (a.android_api_level != b.android_api_level) return false;
602    if (a.abi != b.abi) return false;
603    if (!dynamicLinkerEqualOpt(a.dynamic_linker, b.dynamic_linker)) return false;
604    if (a.ofmt != b.ofmt) return false;
605
606    return true;
607}
608
609fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool {
610    if (a == null and b == null) return true;
611    if (a == null or b == null) return false;
612    return SemanticVersion.order(a.?, b.?) == .eq;
613}
614
615fn dynamicLinkerEqualOpt(a: ?Target.DynamicLinker, b: ?Target.DynamicLinker) bool {
616    if (a == null and b == null) return true;
617    if (a == null or b == null) return false;
618    return a.?.eql(b.?);
619}
620
621test parse {
622    const io = std.testing.io;
623
624    if (builtin.target.isGnuLibC()) {
625        var query = try Query.parse(.{});
626        query.setGnuLibCVersion(2, 1, 1);
627
628        const text = try query.zigTriple(std.testing.allocator);
629        defer std.testing.allocator.free(text);
630
631        try std.testing.expectEqualSlices(u8, "native-native-gnu.2.1.1", text);
632    }
633    if (builtin.target.abi.isAndroid()) {
634        var query = try Query.parse(.{});
635        query.android_api_level = 30;
636
637        const text = try query.zigTriple(std.testing.allocator);
638        defer std.testing.allocator.free(text);
639
640        try std.testing.expectEqualSlices(u8, "native-native-android.30", text);
641    }
642    {
643        const query = try Query.parse(.{
644            .arch_os_abi = "aarch64-linux",
645            .cpu_features = "native",
646        });
647
648        try std.testing.expect(query.cpu_arch.? == .aarch64);
649        try std.testing.expect(query.cpu_model == .native);
650    }
651    {
652        const query = try Query.parse(.{ .arch_os_abi = "native" });
653
654        try std.testing.expect(query.cpu_arch == null);
655        try std.testing.expect(query.isNative());
656
657        const text = try query.zigTriple(std.testing.allocator);
658        defer std.testing.allocator.free(text);
659        try std.testing.expectEqualSlices(u8, "native", text);
660    }
661    {
662        const query = try Query.parse(.{
663            .arch_os_abi = "x86_64-linux-gnu",
664            .cpu_features = "x86_64-sse-sse2-avx-cx8",
665        });
666        const target = try std.zig.system.resolveTargetQuery(io, query);
667
668        try std.testing.expect(target.os.tag == .linux);
669        try std.testing.expect(target.abi == .gnu);
670        try std.testing.expect(target.cpu.arch == .x86_64);
671        try std.testing.expect(!target.cpu.has(.x86, .sse));
672        try std.testing.expect(!target.cpu.has(.x86, .avx));
673        try std.testing.expect(!target.cpu.has(.x86, .cx8));
674        try std.testing.expect(target.cpu.has(.x86, .cmov));
675        try std.testing.expect(target.cpu.has(.x86, .fxsr));
676
677        try std.testing.expect(target.cpu.hasAny(.x86, &.{ .sse, .avx, .cmov }));
678        try std.testing.expect(!target.cpu.hasAny(.x86, &.{ .sse, .avx }));
679        try std.testing.expect(target.cpu.hasAll(.x86, &.{ .mmx, .x87 }));
680        try std.testing.expect(!target.cpu.hasAll(.x86, &.{ .mmx, .x87, .sse }));
681
682        const text = try query.zigTriple(std.testing.allocator);
683        defer std.testing.allocator.free(text);
684        try std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text);
685    }
686    {
687        const query = try Query.parse(.{
688            .arch_os_abi = "arm-linux-musleabihf",
689            .cpu_features = "generic+v8a",
690        });
691        const target = try std.zig.system.resolveTargetQuery(io, query);
692
693        try std.testing.expect(target.os.tag == .linux);
694        try std.testing.expect(target.abi == .musleabihf);
695        try std.testing.expect(target.cpu.arch == .arm);
696        try std.testing.expect(target.cpu.model == &Target.arm.cpu.generic);
697        try std.testing.expect(target.cpu.has(.arm, .v8a));
698
699        const text = try query.zigTriple(std.testing.allocator);
700        defer std.testing.allocator.free(text);
701        try std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text);
702    }
703    {
704        const query = try Query.parse(.{
705            .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
706            .cpu_features = "generic+v8a",
707        });
708        const target = try std.zig.system.resolveTargetQuery(io, query);
709
710        try std.testing.expect(target.cpu.arch == .aarch64);
711        try std.testing.expect(target.os.tag == .linux);
712        try std.testing.expect(target.os.version_range.linux.range.min.major == 3);
713        try std.testing.expect(target.os.version_range.linux.range.min.minor == 10);
714        try std.testing.expect(target.os.version_range.linux.range.min.patch == 0);
715        try std.testing.expect(target.os.version_range.linux.range.max.major == 4);
716        try std.testing.expect(target.os.version_range.linux.range.max.minor == 4);
717        try std.testing.expect(target.os.version_range.linux.range.max.patch == 1);
718        try std.testing.expect(target.os.version_range.linux.glibc.major == 2);
719        try std.testing.expect(target.os.version_range.linux.glibc.minor == 27);
720        try std.testing.expect(target.os.version_range.linux.glibc.patch == 0);
721        try std.testing.expect(target.abi == .gnu);
722
723        const text = try query.zigTriple(std.testing.allocator);
724        defer std.testing.allocator.free(text);
725        try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text);
726    }
727    {
728        const query = try Query.parse(.{
729            .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30",
730        });
731        const target = try std.zig.system.resolveTargetQuery(io, query);
732
733        try std.testing.expect(target.cpu.arch == .aarch64);
734        try std.testing.expect(target.os.tag == .linux);
735        try std.testing.expect(target.os.version_range.linux.range.min.major == 3);
736        try std.testing.expect(target.os.version_range.linux.range.min.minor == 10);
737        try std.testing.expect(target.os.version_range.linux.range.min.patch == 0);
738        try std.testing.expect(target.os.version_range.linux.range.max.major == 4);
739        try std.testing.expect(target.os.version_range.linux.range.max.minor == 4);
740        try std.testing.expect(target.os.version_range.linux.range.max.patch == 1);
741        try std.testing.expect(target.os.version_range.linux.android == 30);
742        try std.testing.expect(target.abi == .android);
743
744        const text = try query.zigTriple(std.testing.allocator);
745        defer std.testing.allocator.free(text);
746        try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-android.30", text);
747    }
748    {
749        const query = try Query.parse(.{
750            .arch_os_abi = "x86-windows.xp...win8-msvc",
751        });
752        const target = try std.zig.system.resolveTargetQuery(io, query);
753
754        try std.testing.expect(target.cpu.arch == .x86);
755        try std.testing.expect(target.os.tag == .windows);
756        try std.testing.expect(target.os.version_range.windows.min == .xp);
757        try std.testing.expect(target.os.version_range.windows.max == .win8);
758        try std.testing.expect(target.abi == .msvc);
759
760        const text = try query.zigTriple(std.testing.allocator);
761        defer std.testing.allocator.free(text);
762        try std.testing.expectEqualSlices(u8, "x86-windows.xp...win8-msvc", text);
763    }
764}