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