| 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 | |
| 6 | const Query = @This(); |
| 7 | const std = @import("../std.zig"); |
| 8 | const builtin = @import("builtin"); |
| 9 | const assert = std.debug.assert; |
| 10 | const Target = std.Target; |
| 11 | const mem = std.mem; |
| 12 | const Allocator = std.mem.Allocator; |
| 13 | const ArrayList = std.ArrayList; |
| 14 | |
| 15 | /// `null` means native. |
| 16 | cpu_arch: ?Target.Cpu.Arch = null, |
| 17 | |
| 18 | cpu_model: CpuModel = .determined_by_arch_os, |
| 19 | |
| 20 | /// Sparse set of CPU features to add to the set from `cpu_model`. |
| 21 | cpu_features_add: Target.Cpu.Feature.Set = .empty, |
| 22 | |
| 23 | /// Sparse set of CPU features to remove from the set from `cpu_model`. |
| 24 | cpu_features_sub: Target.Cpu.Feature.Set = .empty, |
| 25 | |
| 26 | /// `null` means native. |
| 27 | os_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. |
| 31 | os_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. |
| 35 | os_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. |
| 39 | glibc_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. |
| 43 | android_api_level: ?u32 = null, |
| 44 | |
| 45 | /// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI. |
| 46 | abi: ?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`. |
| 51 | dynamic_linker: ?Target.DynamicLinker = null, |
| 52 | |
| 53 | /// `null` means default for the cpu/arch/os combo. |
| 54 | ofmt: ?Target.ObjectFormat = null, |
| 55 | |
| 56 | pub 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 | |
| 81 | pub 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 | |
| 105 | pub const SemanticVersion = std.SemanticVersion; |
| 106 | |
| 107 | pub 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 | |
| 138 | fn 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 | |
| 156 | pub 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 | |
| 212 | pub 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 => |e| return e, |
| 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 = arch.parseCpuModel(cpu_name) orelse return error.UnknownCpuModel }; |
| 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`. |
| 329 | pub 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. |
| 344 | pub 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 => |e| return e, |
| 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 | |
| 368 | test 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 | |
| 377 | pub 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 | |
| 383 | pub 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 | |
| 388 | pub fn isNativeAbi(self: Query) bool { |
| 389 | return self.os_tag == null and self.abi == null; |
| 390 | } |
| 391 | |
| 392 | pub fn isNativeTriple(self: Query) bool { |
| 393 | return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi(); |
| 394 | } |
| 395 | |
| 396 | pub fn isNative(self: Query) bool { |
| 397 | return self.isNativeTriple() and self.ofmt == null; |
| 398 | } |
| 399 | |
| 400 | pub 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. |
| 413 | fn 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 | |
| 421 | pub 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`. |
| 488 | pub 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 | |
| 530 | pub 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 | |
| 536 | /// Deprecated; use `zigTriple` instead. Will be removed in 0.18.0. |
| 537 | pub fn allocDescription(self: Query, allocator: Allocator) ![]u8 { |
| 538 | return self.zigTriple(allocator); |
| 539 | } |
| 540 | |
| 541 | pub fn setGnuLibCVersion(self: *Query, major: u32, minor: u32, patch: u32) void { |
| 542 | self.glibc_version = SemanticVersion{ .major = major, .minor = minor, .patch = patch }; |
| 543 | } |
| 544 | |
| 545 | fn parseOs(result: *Query, diags: *ParseOptions.Diagnostics, text: []const u8) !void { |
| 546 | var it = mem.splitScalar(u8, text, '.'); |
| 547 | const os_name = it.first(); |
| 548 | diags.os_name = os_name; |
| 549 | const os_is_native = mem.eql(u8, os_name, "native"); |
| 550 | if (!os_is_native) { |
| 551 | result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse |
| 552 | return error.UnknownOperatingSystem; |
| 553 | } |
| 554 | const tag = result.os_tag orelse builtin.os.tag; |
| 555 | diags.os_tag = tag; |
| 556 | |
| 557 | const version_text = it.rest(); |
| 558 | if (version_text.len > 0) switch (tag.versionRangeTag()) { |
| 559 | .none => return error.InvalidOperatingSystemVersion, |
| 560 | .semver, .hurd, .linux => { |
| 561 | var range_it = mem.splitSequence(u8, version_text, "..."); |
| 562 | result.os_version_min = .{ |
| 563 | .semver = parseVersion(range_it.first()) catch |err| switch (err) { |
| 564 | error.Overflow => return error.InvalidOperatingSystemVersion, |
| 565 | error.InvalidVersion => return error.InvalidOperatingSystemVersion, |
| 566 | }, |
| 567 | }; |
| 568 | if (range_it.next()) |v| { |
| 569 | result.os_version_max = .{ |
| 570 | .semver = parseVersion(v) catch |err| switch (err) { |
| 571 | error.Overflow => return error.InvalidOperatingSystemVersion, |
| 572 | error.InvalidVersion => return error.InvalidOperatingSystemVersion, |
| 573 | }, |
| 574 | }; |
| 575 | } |
| 576 | }, |
| 577 | .windows => { |
| 578 | var range_it = mem.splitSequence(u8, version_text, "..."); |
| 579 | result.os_version_min = .{ |
| 580 | .windows = try Target.Os.WindowsVersion.parse(range_it.first()), |
| 581 | }; |
| 582 | if (range_it.next()) |v| { |
| 583 | result.os_version_max = .{ |
| 584 | .windows = try Target.Os.WindowsVersion.parse(v), |
| 585 | }; |
| 586 | } |
| 587 | }, |
| 588 | }; |
| 589 | } |
| 590 | |
| 591 | pub fn eql(a: Query, b: Query) bool { |
| 592 | if (a.cpu_arch != b.cpu_arch) return false; |
| 593 | if (!a.cpu_model.eql(b.cpu_model)) return false; |
| 594 | if (!a.cpu_features_add.eql(b.cpu_features_add)) return false; |
| 595 | if (!a.cpu_features_sub.eql(b.cpu_features_sub)) return false; |
| 596 | if (a.os_tag != b.os_tag) return false; |
| 597 | if (!OsVersion.eqlOpt(a.os_version_min, b.os_version_min)) return false; |
| 598 | if (!OsVersion.eqlOpt(a.os_version_max, b.os_version_max)) return false; |
| 599 | if (!versionEqualOpt(a.glibc_version, b.glibc_version)) return false; |
| 600 | if (a.android_api_level != b.android_api_level) return false; |
| 601 | if (a.abi != b.abi) return false; |
| 602 | if (!dynamicLinkerEqualOpt(a.dynamic_linker, b.dynamic_linker)) return false; |
| 603 | if (a.ofmt != b.ofmt) return false; |
| 604 | |
| 605 | return true; |
| 606 | } |
| 607 | |
| 608 | fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool { |
| 609 | if (a == null and b == null) return true; |
| 610 | if (a == null or b == null) return false; |
| 611 | return SemanticVersion.order(a.?, b.?) == .eq; |
| 612 | } |
| 613 | |
| 614 | fn dynamicLinkerEqualOpt(a: ?Target.DynamicLinker, b: ?Target.DynamicLinker) bool { |
| 615 | if (a == null and b == null) return true; |
| 616 | if (a == null or b == null) return false; |
| 617 | return a.?.eql(b.?); |
| 618 | } |
| 619 | |
| 620 | test parse { |
| 621 | const io = std.testing.io; |
| 622 | |
| 623 | if (builtin.target.isGnuLibC()) { |
| 624 | var query = try Query.parse(.{}); |
| 625 | query.setGnuLibCVersion(2, 1, 1); |
| 626 | |
| 627 | const text = try query.zigTriple(std.testing.allocator); |
| 628 | defer std.testing.allocator.free(text); |
| 629 | |
| 630 | try std.testing.expectEqualSlices(u8, "native-native-gnu.2.1.1", text); |
| 631 | } |
| 632 | if (builtin.target.abi.isAndroid()) { |
| 633 | var query = try Query.parse(.{}); |
| 634 | query.android_api_level = 30; |
| 635 | |
| 636 | const text = try query.zigTriple(std.testing.allocator); |
| 637 | defer std.testing.allocator.free(text); |
| 638 | |
| 639 | try std.testing.expectEqualSlices(u8, "native-native-android.30", text); |
| 640 | } |
| 641 | { |
| 642 | const query = try Query.parse(.{ |
| 643 | .arch_os_abi = "aarch64-linux", |
| 644 | .cpu_features = "native", |
| 645 | }); |
| 646 | |
| 647 | try std.testing.expect(query.cpu_arch.? == .aarch64); |
| 648 | try std.testing.expect(query.cpu_model == .native); |
| 649 | } |
| 650 | { |
| 651 | const query = try Query.parse(.{ .arch_os_abi = "native" }); |
| 652 | |
| 653 | try std.testing.expect(query.cpu_arch == null); |
| 654 | try std.testing.expect(query.isNative()); |
| 655 | |
| 656 | const text = try query.zigTriple(std.testing.allocator); |
| 657 | defer std.testing.allocator.free(text); |
| 658 | try std.testing.expectEqualSlices(u8, "native", text); |
| 659 | } |
| 660 | { |
| 661 | const query = try Query.parse(.{ |
| 662 | .arch_os_abi = "x86_64-linux-gnu", |
| 663 | .cpu_features = "x86_64-sse-sse2-avx-cx8", |
| 664 | }); |
| 665 | const target = try std.zig.system.resolveTargetQuery(io, query); |
| 666 | |
| 667 | try std.testing.expect(target.os.tag == .linux); |
| 668 | try std.testing.expect(target.abi == .gnu); |
| 669 | try std.testing.expect(target.cpu.arch == .x86_64); |
| 670 | try std.testing.expect(!target.cpu.has(.x86, .sse)); |
| 671 | try std.testing.expect(!target.cpu.has(.x86, .avx)); |
| 672 | try std.testing.expect(!target.cpu.has(.x86, .cx8)); |
| 673 | try std.testing.expect(target.cpu.has(.x86, .cmov)); |
| 674 | try std.testing.expect(target.cpu.has(.x86, .fxsr)); |
| 675 | |
| 676 | try std.testing.expect(target.cpu.hasAny(.x86, &.{ .sse, .avx, .cmov })); |
| 677 | try std.testing.expect(!target.cpu.hasAny(.x86, &.{ .sse, .avx })); |
| 678 | try std.testing.expect(target.cpu.hasAll(.x86, &.{ .mmx, .x87 })); |
| 679 | try std.testing.expect(!target.cpu.hasAll(.x86, &.{ .mmx, .x87, .sse })); |
| 680 | |
| 681 | const text = try query.zigTriple(std.testing.allocator); |
| 682 | defer std.testing.allocator.free(text); |
| 683 | try std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text); |
| 684 | } |
| 685 | { |
| 686 | const query = try Query.parse(.{ |
| 687 | .arch_os_abi = "arm-linux-musleabihf", |
| 688 | .cpu_features = "generic+v8a", |
| 689 | }); |
| 690 | const target = try std.zig.system.resolveTargetQuery(io, query); |
| 691 | |
| 692 | try std.testing.expect(target.os.tag == .linux); |
| 693 | try std.testing.expect(target.abi == .musleabihf); |
| 694 | try std.testing.expect(target.cpu.arch == .arm); |
| 695 | try std.testing.expect(target.cpu.model == &Target.arm.cpu.generic); |
| 696 | try std.testing.expect(target.cpu.has(.arm, .v8a)); |
| 697 | |
| 698 | const text = try query.zigTriple(std.testing.allocator); |
| 699 | defer std.testing.allocator.free(text); |
| 700 | try std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text); |
| 701 | } |
| 702 | { |
| 703 | const query = try Query.parse(.{ |
| 704 | .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27", |
| 705 | .cpu_features = "generic+v8a", |
| 706 | }); |
| 707 | const target = try std.zig.system.resolveTargetQuery(io, query); |
| 708 | |
| 709 | try std.testing.expect(target.cpu.arch == .aarch64); |
| 710 | try std.testing.expect(target.os.tag == .linux); |
| 711 | try std.testing.expect(target.os.version_range.linux.range.min.major == 3); |
| 712 | try std.testing.expect(target.os.version_range.linux.range.min.minor == 10); |
| 713 | try std.testing.expect(target.os.version_range.linux.range.min.patch == 0); |
| 714 | try std.testing.expect(target.os.version_range.linux.range.max.major == 4); |
| 715 | try std.testing.expect(target.os.version_range.linux.range.max.minor == 4); |
| 716 | try std.testing.expect(target.os.version_range.linux.range.max.patch == 1); |
| 717 | try std.testing.expect(target.os.version_range.linux.glibc.major == 2); |
| 718 | try std.testing.expect(target.os.version_range.linux.glibc.minor == 27); |
| 719 | try std.testing.expect(target.os.version_range.linux.glibc.patch == 0); |
| 720 | try std.testing.expect(target.abi == .gnu); |
| 721 | |
| 722 | const text = try query.zigTriple(std.testing.allocator); |
| 723 | defer std.testing.allocator.free(text); |
| 724 | try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text); |
| 725 | } |
| 726 | { |
| 727 | const query = try Query.parse(.{ |
| 728 | .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30", |
| 729 | }); |
| 730 | const target = try std.zig.system.resolveTargetQuery(io, query); |
| 731 | |
| 732 | try std.testing.expect(target.cpu.arch == .aarch64); |
| 733 | try std.testing.expect(target.os.tag == .linux); |
| 734 | try std.testing.expect(target.os.version_range.linux.range.min.major == 3); |
| 735 | try std.testing.expect(target.os.version_range.linux.range.min.minor == 10); |
| 736 | try std.testing.expect(target.os.version_range.linux.range.min.patch == 0); |
| 737 | try std.testing.expect(target.os.version_range.linux.range.max.major == 4); |
| 738 | try std.testing.expect(target.os.version_range.linux.range.max.minor == 4); |
| 739 | try std.testing.expect(target.os.version_range.linux.range.max.patch == 1); |
| 740 | try std.testing.expect(target.os.version_range.linux.android == 30); |
| 741 | try std.testing.expect(target.abi == .android); |
| 742 | |
| 743 | const text = try query.zigTriple(std.testing.allocator); |
| 744 | defer std.testing.allocator.free(text); |
| 745 | try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-android.30", text); |
| 746 | } |
| 747 | { |
| 748 | const query = try Query.parse(.{ |
| 749 | .arch_os_abi = "x86-windows.xp...win8-msvc", |
| 750 | }); |
| 751 | const target = try std.zig.system.resolveTargetQuery(io, query); |
| 752 | |
| 753 | try std.testing.expect(target.cpu.arch == .x86); |
| 754 | try std.testing.expect(target.os.tag == .windows); |
| 755 | try std.testing.expect(target.os.version_range.windows.min == .xp); |
| 756 | try std.testing.expect(target.os.version_range.windows.max == .win8); |
| 757 | try std.testing.expect(target.abi == .msvc); |
| 758 | |
| 759 | const text = try query.zigTriple(std.testing.allocator); |
| 760 | defer std.testing.allocator.free(text); |
| 761 | try std.testing.expectEqualSlices(u8, "x86-windows.xp...win8-msvc", text); |
| 762 | } |
| 763 | } |