authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-12-04 15:26:57-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-01 17:51:18-07:00
logdbdb87502d6936597424fe86842c15978ba86918
tree8e9fc73fad1d855368a85b7bceab4a725494b47b
parent3179f58c414b5e4845b9bf3acdf276fe8e2b88a0

std.Target: add DynamicLinker


16 files changed, 1280 insertions(+), 1295 deletions(-)

CMakeLists.txt-1
......@@ -516,7 +516,6 @@ set(ZIG_STAGE2_SOURCES
516516 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
517517 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
518518 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
519 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativeTargetInfo.zig"
520519 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig"
521520 "${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig"
522521 "${CMAKE_SOURCE_DIR}/src/Air.zig"
lib/build_runner.zig+1-3
......@@ -46,11 +46,9 @@ pub fn main() !void {
4646 return error.InvalidArgs;
4747 };
4848
49 const detected = try std.zig.system.NativeTargetInfo.detect(.{});
5049 const host: std.Build.ResolvedTarget = .{
5150 .query = .{},
52 .target = detected.target,
53 .dynamic_linker = detected.dynamic_linker,
51 .target = try std.zig.system.resolveTargetQuery(.{}),
5452 };
5553
5654 const build_root_directory: std.Build.Cache.Directory = .{
lib/std/Build.zig+2-13
......@@ -2129,14 +2129,6 @@ pub fn hex64(x: u64) [16]u8 {
21292129pub const ResolvedTarget = struct {
21302130 query: Target.Query,
21312131 target: Target,
2132 dynamic_linker: Target.DynamicLinker,
2133
2134 pub fn toNativeTargetInfo(self: ResolvedTarget) std.zig.system.NativeTargetInfo {
2135 return .{
2136 .target = self.target,
2137 .dynamic_linker = self.dynamic_linker,
2138 };
2139 }
21402132};
21412133
21422134/// Converts a target query into a fully resolved target that can be passed to
......@@ -2146,13 +2138,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
21462138 // resolved via a WASI API or via the build protocol.
21472139 _ = b;
21482140
2149 const result = std.zig.system.NativeTargetInfo.detect(query) catch
2150 @panic("unable to resolve target query");
2151
21522141 return .{
21532142 .query = query,
2154 .target = result.target,
2155 .dynamic_linker = result.dynamic_linker,
2143 .target = std.zig.system.resolveTargetQuery(query) catch
2144 @panic("unable to resolve target query"),
21562145 };
21572146}
21582147
lib/std/Build/Module.zig-1
......@@ -746,5 +746,4 @@ const Module = @This();
746746const std = @import("std");
747747const assert = std.debug.assert;
748748const LazyPath = std.Build.LazyPath;
749const NativeTargetInfo = std.zig.system.NativeTargetInfo;
750749const Step = std.Build.Step;
lib/std/Build/Step/Compile.zig-1
......@@ -9,7 +9,6 @@ const StringHashMap = std.StringHashMap;
99const Sha256 = std.crypto.hash.sha2.Sha256;
1010const Allocator = mem.Allocator;
1111const Step = std.Build.Step;
12const NativeTargetInfo = std.zig.system.NativeTargetInfo;
1312const LazyPath = std.Build.LazyPath;
1413const PkgConfigPkg = std.Build.PkgConfigPkg;
1514const PkgConfigError = std.Build.PkgConfigError;
lib/std/Build/Step/Options.zig+1-3
......@@ -294,11 +294,9 @@ test Options {
294294 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
295295 defer arena.deinit();
296296
297 const detected = try std.zig.system.NativeTargetInfo.detect(.{});
298297 const host: std.Build.ResolvedTarget = .{
299298 .query = .{},
300 .target = detected.target,
301 .dynamic_linker = detected.dynamic_linker,
299 .target = try std.zig.system.resolveTargetQuery(.{}),
302300 };
303301
304302 var cache: std.Build.Cache = .{
lib/std/Build/Step/Run.zig+3-3
......@@ -678,8 +678,8 @@ fn runCommand(
678678
679679 const need_cross_glibc = exe.rootModuleTarget().isGnuLibC() and
680680 exe.is_linking_libc;
681 const other_target_info = exe.root_module.target.?.toNativeTargetInfo();
682 switch (b.host.toNativeTargetInfo().getExternalExecutor(&other_target_info, .{
681 const other_target = exe.root_module.target.?.target;
682 switch (std.zig.system.getExternalExecutor(b.host.target, &other_target, .{
683683 .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null,
684684 .link_libc = exe.is_linking_libc,
685685 })) {
......@@ -752,7 +752,7 @@ fn runCommand(
752752 .bad_dl => |foreign_dl| {
753753 if (allow_skip) return error.MakeSkipped;
754754
755 const host_dl = b.host.dynamic_linker.get() orelse "(none)";
755 const host_dl = b.host.target.dynamic_linker.get() orelse "(none)";
756756
757757 return step.fail(
758758 \\the host system is unable to execute binaries from the target
lib/std/Target.zig+46-26
......@@ -1,7 +1,13 @@
1//! All the details about the machine that will be executing code.
2//! Unlike `Query` which might leave some things as "default" or "host", this
3//! data is fully resolved into a concrete set of OS versions, CPU features,
4//! etc.
5
16cpu: Cpu,
27os: Os,
38abi: Abi,
49ofmt: ObjectFormat,
10dynamic_linker: DynamicLinker = DynamicLinker.none,
511
612pub const Query = @import("Target/Query.zig");
713
......@@ -1529,13 +1535,19 @@ pub inline fn hasDynamicLinker(self: Target) bool {
15291535}
15301536
15311537pub const DynamicLinker = struct {
1532 /// Contains the memory used to store the dynamic linker path. This field should
1533 /// not be used directly. See `get` and `set`. This field exists so that this API requires no allocator.
1534 buffer: [255]u8 = undefined,
1538 /// Contains the memory used to store the dynamic linker path. This field
1539 /// should not be used directly. See `get` and `set`. This field exists so
1540 /// that this API requires no allocator.
1541 buffer: [255]u8,
15351542
15361543 /// Used to construct the dynamic linker path. This field should not be used
15371544 /// directly. See `get` and `set`.
1538 max_byte: ?u8 = null,
1545 max_byte: ?u8,
1546
1547 pub const none: DynamicLinker = .{
1548 .buffer = undefined,
1549 .max_byte = null,
1550 };
15391551
15401552 /// Asserts that the length is less than or equal to 255 bytes.
15411553 pub fn init(dl_or_null: ?[]const u8) DynamicLinker {
......@@ -1561,8 +1573,12 @@ pub const DynamicLinker = struct {
15611573 }
15621574};
15631575
1564pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
1565 var result: DynamicLinker = .{};
1576pub fn standardDynamicLinkerPath(target: Target) DynamicLinker {
1577 return standardDynamicLinkerPath_cpu_os_abi(target.cpu, target.os.tag, target.abi);
1578}
1579
1580pub fn standardDynamicLinkerPath_cpu_os_abi(cpu: Cpu, os_tag: Os.Tag, abi: Abi) DynamicLinker {
1581 var result = DynamicLinker.none;
15661582 const S = struct {
15671583 fn print(r: *DynamicLinker, comptime fmt: []const u8, args: anytype) DynamicLinker {
15681584 r.max_byte = @as(u8, @intCast((std.fmt.bufPrint(&r.buffer, fmt, args) catch unreachable).len - 1));
......@@ -1577,32 +1593,32 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
15771593 const print = S.print;
15781594 const copy = S.copy;
15791595
1580 if (self.abi == .android) {
1581 const suffix = if (self.ptrBitWidth() == 64) "64" else "";
1596 if (abi == .android) {
1597 const suffix = if (ptrBitWidth_cpu_abi(cpu, abi) == 64) "64" else "";
15821598 return print(&result, "/system/bin/linker{s}", .{suffix});
15831599 }
15841600
1585 if (self.abi.isMusl()) {
1586 const is_arm = switch (self.cpu.arch) {
1601 if (abi.isMusl()) {
1602 const is_arm = switch (cpu.arch) {
15871603 .arm, .armeb, .thumb, .thumbeb => true,
15881604 else => false,
15891605 };
1590 const arch_part = switch (self.cpu.arch) {
1606 const arch_part = switch (cpu.arch) {
15911607 .arm, .thumb => "arm",
15921608 .armeb, .thumbeb => "armeb",
15931609 else => |arch| @tagName(arch),
15941610 };
1595 const arch_suffix = if (is_arm and self.abi.floatAbi() == .hard) "hf" else "";
1611 const arch_suffix = if (is_arm and abi.floatAbi() == .hard) "hf" else "";
15961612 return print(&result, "/lib/ld-musl-{s}{s}.so.1", .{ arch_part, arch_suffix });
15971613 }
15981614
1599 switch (self.os.tag) {
1615 switch (os_tag) {
16001616 .freebsd => return copy(&result, "/libexec/ld-elf.so.1"),
16011617 .netbsd => return copy(&result, "/libexec/ld.elf_so"),
16021618 .openbsd => return copy(&result, "/usr/libexec/ld.so"),
16031619 .dragonfly => return copy(&result, "/libexec/ld-elf.so.2"),
16041620 .solaris, .illumos => return copy(&result, "/lib/64/ld.so.1"),
1605 .linux => switch (self.cpu.arch) {
1621 .linux => switch (cpu.arch) {
16061622 .x86,
16071623 .sparc,
16081624 .sparcel,
......@@ -1616,7 +1632,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
16161632 .armeb,
16171633 .thumb,
16181634 .thumbeb,
1619 => return copy(&result, switch (self.abi.floatAbi()) {
1635 => return copy(&result, switch (abi.floatAbi()) {
16201636 .hard => "/lib/ld-linux-armhf.so.3",
16211637 else => "/lib/ld-linux.so.3",
16221638 }),
......@@ -1626,12 +1642,12 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
16261642 .mips64,
16271643 .mips64el,
16281644 => {
1629 const lib_suffix = switch (self.abi) {
1645 const lib_suffix = switch (abi) {
16301646 .gnuabin32, .gnux32 => "32",
16311647 .gnuabi64 => "64",
16321648 else => "",
16331649 };
1634 const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008);
1650 const is_nan_2008 = mips.featureSetHas(cpu.features, .nan2008);
16351651 const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1";
16361652 return print(&result, "/lib{s}/{s}", .{ lib_suffix, loader });
16371653 },
......@@ -1640,7 +1656,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
16401656 .powerpc64, .powerpc64le => return copy(&result, "/lib64/ld64.so.2"),
16411657 .s390x => return copy(&result, "/lib64/ld64.so.1"),
16421658 .sparc64 => return copy(&result, "/lib64/ld-linux.so.2"),
1643 .x86_64 => return copy(&result, switch (self.abi) {
1659 .x86_64 => return copy(&result, switch (abi) {
16441660 .gnux32 => "/libx32/ld-linux-x32.so.2",
16451661 else => "/lib64/ld-linux-x86-64.so.2",
16461662 }),
......@@ -1862,17 +1878,17 @@ pub fn maxIntAlignment(target: Target) u16 {
18621878 };
18631879}
18641880
1865pub fn ptrBitWidth(target: Target) u16 {
1866 switch (target.abi) {
1881pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 {
1882 switch (abi) {
18671883 .gnux32, .muslx32, .gnuabin32, .gnuilp32 => return 32,
18681884 .gnuabi64 => return 64,
18691885 else => {},
18701886 }
1871 switch (target.cpu.arch) {
1887 return switch (cpu.arch) {
18721888 .avr,
18731889 .msp430,
18741890 .spu_2,
1875 => return 16,
1891 => 16,
18761892
18771893 .arc,
18781894 .arm,
......@@ -1908,7 +1924,7 @@ pub fn ptrBitWidth(target: Target) u16 {
19081924 .loongarch32,
19091925 .dxil,
19101926 .xtensa,
1911 => return 32,
1927 => 32,
19121928
19131929 .aarch64,
19141930 .aarch64_be,
......@@ -1933,10 +1949,14 @@ pub fn ptrBitWidth(target: Target) u16 {
19331949 .ve,
19341950 .spirv64,
19351951 .loongarch64,
1936 => return 64,
1952 => 64,
19371953
1938 .sparc => return if (std.Target.sparc.featureSetHas(target.cpu.features, .v9)) 64 else 32,
1939 }
1954 .sparc => if (std.Target.sparc.featureSetHas(cpu.features, .v9)) 64 else 32,
1955 };
1956}
1957
1958pub fn ptrBitWidth(target: Target) u16 {
1959 return ptrBitWidth_cpu_abi(target.cpu, target.abi);
19401960}
19411961
19421962pub fn stackAlignment(target: Target) u16 {
lib/std/Target/Query.zig+12-14
......@@ -34,7 +34,7 @@ abi: ?Target.Abi = null,
3434
3535/// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path
3636/// based on the `os_tag`.
37dynamic_linker: DynamicLinker = DynamicLinker{},
37dynamic_linker: Target.DynamicLinker = Target.DynamicLinker.none,
3838
3939/// `null` means default for the cpu/arch/os combo.
4040ofmt: ?Target.ObjectFormat = null,
......@@ -61,8 +61,6 @@ pub const OsVersion = union(enum) {
6161
6262pub const SemanticVersion = std.SemanticVersion;
6363
64pub const DynamicLinker = Target.DynamicLinker;
65
6664pub fn fromTarget(target: Target) Query {
6765 var result: Query = .{
6866 .cpu_arch = target.cpu.arch,
......@@ -164,7 +162,7 @@ fn updateOsVersionRange(self: *Query, os: Target.Os) void {
164162 }
165163}
166164
167/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
165/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
168166pub fn toTarget(self: Query) Target {
169167 return .{
170168 .cpu = self.getCpu(),
......@@ -232,7 +230,7 @@ pub fn parse(args: ParseOptions) !Query {
232230 const diags = args.diagnostics orelse &dummy_diags;
233231
234232 var result: Query = .{
235 .dynamic_linker = DynamicLinker.init(args.dynamic_linker),
233 .dynamic_linker = Target.DynamicLinker.init(args.dynamic_linker),
236234 };
237235
238236 var it = mem.splitScalar(u8, args.arch_os_abi, '-');
......@@ -379,13 +377,13 @@ test parseVersion {
379377 try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3.4"));
380378}
381379
382/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
380/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
383381pub fn getCpu(self: Query) Target.Cpu {
384382 switch (self.cpu_model) {
385383 .native => {
386384 // This works when doing `zig build` because Zig generates a build executable using
387385 // native CPU model & features. However this will not be accurate otherwise, and
388 // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
386 // will need to be integrated with `std.zig.system.resolveTargetQuery`.
389387 return builtin.cpu;
390388 },
391389 .baseline => {
......@@ -396,7 +394,7 @@ pub fn getCpu(self: Query) Target.Cpu {
396394 .determined_by_cpu_arch => if (self.cpu_arch == null) {
397395 // This works when doing `zig build` because Zig generates a build executable using
398396 // native CPU model & features. However this will not be accurate otherwise, and
399 // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
397 // will need to be integrated with `std.zig.system.resolveTargetQuery`.
400398 return builtin.cpu;
401399 } else {
402400 var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch());
......@@ -426,11 +424,11 @@ pub fn getCpuFeatures(self: Query) Target.Cpu.Feature.Set {
426424 return self.getCpu().features;
427425}
428426
429/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
427/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
430428pub fn getOs(self: Query) Target.Os {
431429 // `builtin.os` works when doing `zig build` because Zig generates a build executable using
432430 // native OS version range. However this will not be accurate otherwise, and
433 // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
431 // will need to be integrated with `std.zig.system.resolveTargetQuery`.
434432 var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange(self.getCpuArch()) else builtin.os;
435433
436434 if (self.os_version_min) |min| switch (min) {
......@@ -463,7 +461,7 @@ pub fn getOsTag(self: Query) Target.Os.Tag {
463461 return self.os_tag orelse builtin.os.tag;
464462}
465463
466/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
464/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
467465pub fn getOsVersionMin(self: Query) OsVersion {
468466 if (self.os_version_min) |version_min| return version_min;
469467 var tmp: Query = undefined;
......@@ -471,7 +469,7 @@ pub fn getOsVersionMin(self: Query) OsVersion {
471469 return tmp.os_version_min.?;
472470}
473471
474/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
472/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
475473pub fn getOsVersionMax(self: Query) OsVersion {
476474 if (self.os_version_max) |version_max| return version_max;
477475 var tmp: Query = undefined;
......@@ -479,14 +477,14 @@ pub fn getOsVersionMax(self: Query) OsVersion {
479477 return tmp.os_version_max.?;
480478}
481479
482/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
480/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
483481pub fn getAbi(self: Query) Target.Abi {
484482 if (self.abi) |abi| return abi;
485483
486484 if (self.os_tag == null) {
487485 // This works when doing `zig build` because Zig generates a build executable using
488486 // native CPU model & features. However this will not be accurate otherwise, and
489 // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
487 // will need to be integrated with `std.zig.system.resolveTargetQuery`.
490488 return builtin.abi;
491489 }
492490
lib/std/zig/system.zig+1116-2
......@@ -1,13 +1,1127 @@
11pub const NativePaths = @import("system/NativePaths.zig");
2pub const NativeTargetInfo = @import("system/NativeTargetInfo.zig");
32
43pub const windows = @import("system/windows.zig");
54pub const darwin = @import("system/darwin.zig");
65pub const linux = @import("system/linux.zig");
76
7pub const Executor = union(enum) {
8 native,
9 rosetta,
10 qemu: []const u8,
11 wine: []const u8,
12 wasmtime: []const u8,
13 darling: []const u8,
14 bad_dl: []const u8,
15 bad_os_or_cpu,
16};
17
18pub const GetExternalExecutorOptions = struct {
19 allow_darling: bool = true,
20 allow_qemu: bool = true,
21 allow_rosetta: bool = true,
22 allow_wasmtime: bool = true,
23 allow_wine: bool = true,
24 qemu_fixes_dl: bool = false,
25 link_libc: bool = false,
26};
27
28/// Return whether or not the given host is capable of running executables of
29/// the other target.
30pub fn getExternalExecutor(
31 host: std.Target,
32 candidate: *const std.Target,
33 options: GetExternalExecutorOptions,
34) Executor {
35 const os_match = host.os.tag == candidate.os.tag;
36 const cpu_ok = cpu_ok: {
37 if (host.cpu.arch == candidate.cpu.arch)
38 break :cpu_ok true;
39
40 if (host.cpu.arch == .x86_64 and candidate.cpu.arch == .x86)
41 break :cpu_ok true;
42
43 if (host.cpu.arch == .aarch64 and candidate.cpu.arch == .arm)
44 break :cpu_ok true;
45
46 if (host.cpu.arch == .aarch64_be and candidate.cpu.arch == .armeb)
47 break :cpu_ok true;
48
49 // TODO additionally detect incompatible CPU features.
50 // Note that in some cases the OS kernel will emulate missing CPU features
51 // when an illegal instruction is encountered.
52
53 break :cpu_ok false;
54 };
55
56 var bad_result: Executor = .bad_os_or_cpu;
57
58 if (os_match and cpu_ok) native: {
59 if (options.link_libc) {
60 if (candidate.dynamic_linker.get()) |candidate_dl| {
61 fs.cwd().access(candidate_dl, .{}) catch {
62 bad_result = .{ .bad_dl = candidate_dl };
63 break :native;
64 };
65 }
66 }
67 return .native;
68 }
69
70 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
71 // to emulate the foreign architecture.
72 if (options.allow_rosetta and os_match and
73 host.os.tag == .macos and host.cpu.arch == .aarch64)
74 {
75 switch (candidate.cpu.arch) {
76 .x86_64 => return .rosetta,
77 else => return bad_result,
78 }
79 }
80
81 // If the OS matches, we can use QEMU to emulate a foreign architecture.
82 if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
83 return switch (candidate.cpu.arch) {
84 .aarch64 => Executor{ .qemu = "qemu-aarch64" },
85 .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
86 .arm => Executor{ .qemu = "qemu-arm" },
87 .armeb => Executor{ .qemu = "qemu-armeb" },
88 .hexagon => Executor{ .qemu = "qemu-hexagon" },
89 .x86 => Executor{ .qemu = "qemu-i386" },
90 .m68k => Executor{ .qemu = "qemu-m68k" },
91 .mips => Executor{ .qemu = "qemu-mips" },
92 .mipsel => Executor{ .qemu = "qemu-mipsel" },
93 .mips64 => Executor{ .qemu = "qemu-mips64" },
94 .mips64el => Executor{ .qemu = "qemu-mips64el" },
95 .powerpc => Executor{ .qemu = "qemu-ppc" },
96 .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
97 .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
98 .riscv32 => Executor{ .qemu = "qemu-riscv32" },
99 .riscv64 => Executor{ .qemu = "qemu-riscv64" },
100 .s390x => Executor{ .qemu = "qemu-s390x" },
101 .sparc => Executor{ .qemu = "qemu-sparc" },
102 .sparc64 => Executor{ .qemu = "qemu-sparc64" },
103 .x86_64 => Executor{ .qemu = "qemu-x86_64" },
104 else => return bad_result,
105 };
106 }
107
108 switch (candidate.os.tag) {
109 .windows => {
110 if (options.allow_wine) {
111 // x86_64 wine does not support emulating aarch64-windows and
112 // vice versa.
113 if (candidate.cpu.arch != builtin.cpu.arch) {
114 return bad_result;
115 }
116 switch (candidate.ptrBitWidth()) {
117 32 => return Executor{ .wine = "wine" },
118 64 => return Executor{ .wine = "wine64" },
119 else => return bad_result,
120 }
121 }
122 return bad_result;
123 },
124 .wasi => {
125 if (options.allow_wasmtime) {
126 switch (candidate.ptrBitWidth()) {
127 32 => return Executor{ .wasmtime = "wasmtime" },
128 else => return bad_result,
129 }
130 }
131 return bad_result;
132 },
133 .macos => {
134 if (options.allow_darling) {
135 // This check can be loosened once darling adds a QEMU-based emulation
136 // layer for non-host architectures:
137 // https://github.com/darlinghq/darling/issues/863
138 if (candidate.cpu.arch != builtin.cpu.arch) {
139 return bad_result;
140 }
141 return Executor{ .darling = "darling" };
142 }
143 return bad_result;
144 },
145 else => return bad_result,
146 }
147}
148
149pub const DetectError = error{
150 FileSystem,
151 SystemResources,
152 SymLinkLoop,
153 ProcessFdQuotaExceeded,
154 SystemFdQuotaExceeded,
155 DeviceBusy,
156 OSVersionDetectionFail,
157 Unexpected,
158};
159
160/// Given a `Target.Query`, which specifies in detail which parts of the
161/// target should be detected natively, which should be standard or default,
162/// and which are provided explicitly, this function resolves the native
163/// components by detecting the native system, and then resolves
164/// standard/default parts relative to that.
165pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
166 var os = query.getOsTag().defaultVersionRange(query.getCpuArch());
167 if (query.os_tag == null) {
168 switch (builtin.target.os.tag) {
169 .linux => {
170 const uts = std.os.uname();
171 const release = mem.sliceTo(&uts.release, 0);
172 // The release field sometimes has a weird format,
173 // `Version.parse` will attempt to find some meaningful interpretation.
174 if (std.SemanticVersion.parse(release)) |ver| {
175 os.version_range.linux.range.min = ver;
176 os.version_range.linux.range.max = ver;
177 } else |err| switch (err) {
178 error.Overflow => {},
179 error.InvalidVersion => {},
180 }
181 },
182 .solaris, .illumos => {
183 const uts = std.os.uname();
184 const release = mem.sliceTo(&uts.release, 0);
185 if (std.SemanticVersion.parse(release)) |ver| {
186 os.version_range.semver.min = ver;
187 os.version_range.semver.max = ver;
188 } else |err| switch (err) {
189 error.Overflow => {},
190 error.InvalidVersion => {},
191 }
192 },
193 .windows => {
194 const detected_version = windows.detectRuntimeVersion();
195 os.version_range.windows.min = detected_version;
196 os.version_range.windows.max = detected_version;
197 },
198 .macos => try darwin.macos.detect(&os),
199 .freebsd, .netbsd, .dragonfly => {
200 const key = switch (builtin.target.os.tag) {
201 .freebsd => "kern.osreldate",
202 .netbsd, .dragonfly => "kern.osrevision",
203 else => unreachable,
204 };
205 var value: u32 = undefined;
206 var len: usize = @sizeOf(@TypeOf(value));
207
208 std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
209 error.NameTooLong => unreachable, // constant, known good value
210 error.PermissionDenied => unreachable, // only when setting values,
211 error.SystemResources => unreachable, // memory already on the stack
212 error.UnknownName => unreachable, // constant, known good value
213 error.Unexpected => return error.OSVersionDetectionFail,
214 };
215
216 switch (builtin.target.os.tag) {
217 .freebsd => {
218 // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
219 // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
220 // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
221 // e.g. 492101 = 4.11-STABLE = 4.(9+2)
222 const major = value / 100_000;
223 const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
224 const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
225 const patch = value % 1_000;
226 os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
227 os.version_range.semver.max = os.version_range.semver.min;
228 },
229 .netbsd => {
230 // #define __NetBSD_Version__ MMmmrrpp00
231 //
232 // M = major version
233 // m = minor version; a minor number of 99 indicates current.
234 // r = 0 (*)
235 // p = patchlevel
236 const major = value / 100_000_000;
237 const minor = value % 100_000_000 / 1_000_000;
238 const patch = value % 10_000 / 100;
239 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
240 os.version_range.semver.max = os.version_range.semver.min;
241 },
242 .dragonfly => {
243 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
244 // flat base10 format: Mmmmpp
245 // M = major
246 // m = minor; odd-numbers indicate current dev branch
247 // p = patch
248 const major = value / 100_000;
249 const minor = value % 100_000 / 100;
250 const patch = value % 100;
251 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
252 os.version_range.semver.max = os.version_range.semver.min;
253 },
254 else => unreachable,
255 }
256 },
257 .openbsd => {
258 const mib: [2]c_int = [_]c_int{
259 std.os.CTL.KERN,
260 std.os.KERN.OSRELEASE,
261 };
262 var buf: [64]u8 = undefined;
263 // consider that sysctl result includes null-termination
264 // reserve 1 byte to ensure we never overflow when appending ".0"
265 var len: usize = buf.len - 1;
266
267 std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
268 error.NameTooLong => unreachable, // constant, known good value
269 error.PermissionDenied => unreachable, // only when setting values,
270 error.SystemResources => unreachable, // memory already on the stack
271 error.UnknownName => unreachable, // constant, known good value
272 error.Unexpected => return error.OSVersionDetectionFail,
273 };
274
275 // append ".0" to satisfy semver
276 buf[len - 1] = '.';
277 buf[len] = '0';
278 len += 1;
279
280 if (std.SemanticVersion.parse(buf[0..len])) |ver| {
281 os.version_range.semver.min = ver;
282 os.version_range.semver.max = ver;
283 } else |_| {
284 return error.OSVersionDetectionFail;
285 }
286 },
287 else => {
288 // Unimplemented, fall back to default version range.
289 },
290 }
291 }
292
293 if (query.os_version_min) |min| switch (min) {
294 .none => {},
295 .semver => |semver| switch (query.getOsTag()) {
296 .linux => os.version_range.linux.range.min = semver,
297 else => os.version_range.semver.min = semver,
298 },
299 .windows => |win_ver| os.version_range.windows.min = win_ver,
300 };
301
302 if (query.os_version_max) |max| switch (max) {
303 .none => {},
304 .semver => |semver| switch (query.getOsTag()) {
305 .linux => os.version_range.linux.range.max = semver,
306 else => os.version_range.semver.max = semver,
307 },
308 .windows => |win_ver| os.version_range.windows.max = win_ver,
309 };
310
311 if (query.glibc_version) |glibc| {
312 assert(query.isGnuLibC());
313 os.version_range.linux.glibc = glibc;
314 }
315
316 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
317 // native CPU architecture as being different than the current target), we use this:
318 const cpu_arch = query.getCpuArch();
319
320 const cpu = switch (query.cpu_model) {
321 .native => detectNativeCpuAndFeatures(cpu_arch, os, query),
322 .baseline => Target.Cpu.baseline(cpu_arch),
323 .determined_by_cpu_arch => if (query.cpu_arch == null)
324 detectNativeCpuAndFeatures(cpu_arch, os, query)
325 else
326 Target.Cpu.baseline(cpu_arch),
327 .explicit => |model| model.toCpu(cpu_arch),
328 } orelse backup_cpu_detection: {
329 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
330 };
331 var result = try detectAbiAndDynamicLinker(cpu, os, query);
332 // For x86, we need to populate some CPU feature flags depending on architecture
333 // and mode:
334 // * 16bit_mode => if the abi is code16
335 // * 32bit_mode => if the arch is x86
336 // However, the "mode" flags can be used as overrides, so if the user explicitly
337 // sets one of them, that takes precedence.
338 switch (cpu_arch) {
339 .x86 => {
340 if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{
341 .@"16bit_mode", .@"32bit_mode",
342 })) {
343 switch (result.abi) {
344 .code16 => result.cpu.features.addFeature(
345 @intFromEnum(Target.x86.Feature.@"16bit_mode"),
346 ),
347 else => result.cpu.features.addFeature(
348 @intFromEnum(Target.x86.Feature.@"32bit_mode"),
349 ),
350 }
351 }
352 },
353 .arm, .armeb => {
354 // XXX What do we do if the target has the noarm feature?
355 // What do we do if the user specifies +thumb_mode?
356 },
357 .thumb, .thumbeb => {
358 result.cpu.features.addFeature(
359 @intFromEnum(Target.arm.Feature.thumb_mode),
360 );
361 },
362 else => {},
363 }
364 query.updateCpuFeatures(&result.cpu.features);
365 return result;
366}
367
368fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu {
369 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
370 // although it is a runtime value, is guaranteed to be one of the architectures in the set
371 // of the respective switch prong.
372 switch (builtin.cpu.arch) {
373 .x86_64, .x86 => {
374 return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query);
375 },
376 else => {},
377 }
378
379 switch (builtin.os.tag) {
380 .linux => return linux.detectNativeCpuAndFeatures(),
381 .macos => return darwin.macos.detectNativeCpuAndFeatures(),
382 .windows => return windows.detectNativeCpuAndFeatures(),
383 else => {},
384 }
385
386 // This architecture does not have CPU model & feature detection yet.
387 // See https://github.com/ziglang/zig/issues/4591
388 return null;
389}
390
391pub const AbiAndDynamicLinkerFromFileError = error{
392 FileSystem,
393 SystemResources,
394 SymLinkLoop,
395 ProcessFdQuotaExceeded,
396 SystemFdQuotaExceeded,
397 UnableToReadElfFile,
398 InvalidElfClass,
399 InvalidElfVersion,
400 InvalidElfEndian,
401 InvalidElfFile,
402 InvalidElfMagic,
403 Unexpected,
404 UnexpectedEndOfFile,
405 NameTooLong,
406};
407
408pub fn abiAndDynamicLinkerFromFile(
409 file: fs.File,
410 cpu: Target.Cpu,
411 os: Target.Os,
412 ld_info_list: []const LdInfo,
413 query: Target.Query,
414) AbiAndDynamicLinkerFromFileError!Target {
415 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
416 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
417 const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
418 const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf));
419 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
420 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
421 elf.ELFDATA2LSB => .little,
422 elf.ELFDATA2MSB => .big,
423 else => return error.InvalidElfEndian,
424 };
425 const need_bswap = elf_endian != native_endian;
426 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
427
428 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
429 elf.ELFCLASS32 => false,
430 elf.ELFCLASS64 => true,
431 else => return error.InvalidElfClass,
432 };
433 var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
434 const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
435 const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
436
437 var result: Target = .{
438 .cpu = cpu,
439 .os = os,
440 .abi = query.abi orelse Target.Abi.default(cpu.arch, os),
441 .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
442 .dynamic_linker = query.dynamic_linker,
443 };
444 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
445 const look_for_ld = query.dynamic_linker.get() == null;
446
447 var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
448 if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
449
450 var ph_i: u16 = 0;
451 while (ph_i < phnum) {
452 // Reserve some bytes so that we can deref the 64-bit struct fields
453 // even when the ELF file is 32-bits.
454 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
455 const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
456 var ph_buf_i: usize = 0;
457 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
458 ph_i += 1;
459 phoff += phentsize;
460 ph_buf_i += phentsize;
461 }) {
462 const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
463 const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
464 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
465 switch (p_type) {
466 elf.PT_INTERP => if (look_for_ld) {
467 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
468 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
469 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
470 const filesz = @as(usize, @intCast(p_filesz));
471 _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
472 // PT_INTERP includes a null byte in filesz.
473 const len = filesz - 1;
474 // dynamic_linker.max_byte is "max", not "len".
475 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
476 result.dynamic_linker.max_byte = @as(u8, @intCast(len - 1));
477
478 // Use it to determine ABI.
479 const full_ld_path = result.dynamic_linker.buffer[0..len];
480 for (ld_info_list) |ld_info| {
481 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
482 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
483 result.abi = ld_info.abi;
484 break;
485 }
486 }
487 },
488 // We only need this for detecting glibc version.
489 elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.isGnuLibC() and
490 query.glibc_version == null)
491 {
492 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
493 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
494 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
495 const dyn_num = p_filesz / dyn_size;
496 var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
497 var dyn_i: usize = 0;
498 dyn: while (dyn_i < dyn_num) {
499 // Reserve some bytes so that we can deref the 64-bit struct fields
500 // even when the ELF file is 32-bits.
501 const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
502 const dyn_read_byte_len = try preadMin(
503 file,
504 dyn_buf[0 .. dyn_buf.len - dyn_reserve],
505 dyn_off,
506 dyn_size,
507 );
508 var dyn_buf_i: usize = 0;
509 while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
510 dyn_i += 1;
511 dyn_off += dyn_size;
512 dyn_buf_i += dyn_size;
513 }) {
514 const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
515 const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
516 const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
517 const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
518 if (tag == elf.DT_RUNPATH) {
519 rpath_offset = val;
520 break :dyn;
521 }
522 }
523 }
524 },
525 else => continue,
526 }
527 }
528 }
529
530 if (builtin.target.os.tag == .linux and result.isGnuLibC() and
531 query.glibc_version == null)
532 {
533 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
534
535 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
536 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
537 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
538
539 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
540 if (sh_buf.len < shentsize) return error.InvalidElfFile;
541
542 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
543 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
544 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
545 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
546 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
547 var strtab_buf: [4096:0]u8 = undefined;
548 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
549 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
550 const shstrtab = strtab_buf[0..shstrtab_read_len];
551
552 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
553 var sh_i: u16 = 0;
554 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
555 // Reserve some bytes so that we can deref the 64-bit struct fields
556 // even when the ELF file is 32-bits.
557 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
558 const sh_read_byte_len = try preadMin(
559 file,
560 sh_buf[0 .. sh_buf.len - sh_reserve],
561 shoff,
562 shentsize,
563 );
564 var sh_buf_i: usize = 0;
565 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
566 sh_i += 1;
567 shoff += shentsize;
568 sh_buf_i += shentsize;
569 }) {
570 const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
571 const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
572 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
573 const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
574 if (mem.eql(u8, sh_name, ".dynstr")) {
575 break :find_dyn_str .{
576 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
577 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
578 };
579 }
580 }
581 } else null;
582
583 if (dynstr) |ds| {
584 if (rpath_offset) |rpoff| {
585 if (rpoff > ds.size) return error.InvalidElfFile;
586 const rpoff_file = ds.offset + rpoff;
587 const rp_max_size = ds.size - rpoff;
588
589 const strtab_len = @min(rp_max_size, strtab_buf.len);
590 const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len);
591 const strtab = strtab_buf[0..strtab_read_len];
592
593 const rpath_list = mem.sliceTo(strtab, 0);
594 var it = mem.tokenizeScalar(u8, rpath_list, ':');
595 while (it.next()) |rpath| {
596 if (glibcVerFromRPath(rpath)) |ver| {
597 result.os.version_range.linux.glibc = ver;
598 return result;
599 } else |err| switch (err) {
600 error.GLibCNotFound => continue,
601 else => |e| return e,
602 }
603 }
604 }
605 }
606
607 if (result.dynamic_linker.get()) |dl_path| glibc_ver: {
608 // There is no DT_RUNPATH so we try to find libc.so.6 inside the same
609 // directory as the dynamic linker.
610 if (fs.path.dirname(dl_path)) |rpath| {
611 if (glibcVerFromRPath(rpath)) |ver| {
612 result.os.version_range.linux.glibc = ver;
613 return result;
614 } else |err| switch (err) {
615 error.GLibCNotFound => {},
616 else => |e| return e,
617 }
618 }
619
620 // So far, no luck. Next we try to see if the information is
621 // present in the symlink data for the dynamic linker path.
622 var link_buf: [std.os.PATH_MAX]u8 = undefined;
623 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
624 error.NameTooLong => unreachable,
625 error.InvalidUtf8 => unreachable, // Windows only
626 error.BadPathName => unreachable, // Windows only
627 error.UnsupportedReparsePointType => unreachable, // Windows only
628 error.NetworkNotFound => unreachable, // Windows only
629
630 error.AccessDenied,
631 error.FileNotFound,
632 error.NotLink,
633 error.NotDir,
634 => break :glibc_ver,
635
636 error.SystemResources,
637 error.FileSystem,
638 error.SymLinkLoop,
639 error.Unexpected,
640 => |e| return e,
641 };
642 result.os.version_range.linux.glibc = glibcVerFromLinkName(
643 fs.path.basename(link_name),
644 "ld-",
645 ) catch |err| switch (err) {
646 error.UnrecognizedGnuLibCFileName,
647 error.InvalidGnuLibCVersion,
648 => break :glibc_ver,
649 };
650 return result;
651 }
652
653 // Nothing worked so far. Finally we fall back to hard-coded search paths.
654 // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`.
655 var path_buf: [std.os.PATH_MAX]u8 = undefined;
656 var index: usize = 0;
657 const prefix = "/lib/";
658 const cpu_arch = @tagName(result.cpu.arch);
659 const os_tag = @tagName(result.os.tag);
660 const abi = @tagName(result.abi);
661 @memcpy(path_buf[index..][0..prefix.len], prefix);
662 index += prefix.len;
663 @memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch);
664 index += cpu_arch.len;
665 path_buf[index] = '-';
666 index += 1;
667 @memcpy(path_buf[index..][0..os_tag.len], os_tag);
668 index += os_tag.len;
669 path_buf[index] = '-';
670 index += 1;
671 @memcpy(path_buf[index..][0..abi.len], abi);
672 index += abi.len;
673 const rpath = path_buf[0..index];
674 if (glibcVerFromRPath(rpath)) |ver| {
675 result.os.version_range.linux.glibc = ver;
676 return result;
677 } else |err| switch (err) {
678 error.GLibCNotFound => {},
679 else => |e| return e,
680 }
681 }
682
683 return result;
684}
685
686fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion {
687 // example: "libc-2.3.4.so"
688 // example: "libc-2.27.so"
689 // example: "ld-2.33.so"
690 const suffix = ".so";
691 if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
692 return error.UnrecognizedGnuLibCFileName;
693 }
694 // chop off "libc-" and ".so"
695 const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
696 return Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) {
697 error.Overflow => return error.InvalidGnuLibCVersion,
698 error.InvalidVersion => return error.InvalidGnuLibCVersion,
699 };
700}
701
702test glibcVerFromLinkName {
703 try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist"));
704 try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-"));
705
706 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-"));
707 try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-"));
708 try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-"));
709 try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-"));
710 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
711}
712
713fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
714 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
715 error.NameTooLong => unreachable,
716 error.InvalidUtf8 => unreachable,
717 error.BadPathName => unreachable,
718 error.DeviceBusy => unreachable,
719 error.NetworkNotFound => unreachable, // Windows-only
720
721 error.FileNotFound,
722 error.NotDir,
723 error.InvalidHandle,
724 error.AccessDenied,
725 error.NoDevice,
726 => return error.GLibCNotFound,
727
728 error.ProcessFdQuotaExceeded,
729 error.SystemFdQuotaExceeded,
730 error.SystemResources,
731 error.SymLinkLoop,
732 error.Unexpected,
733 => |e| return e,
734 };
735 defer dir.close();
736
737 // Now we have a candidate for the path to libc shared object. In
738 // the past, we used readlink() here because the link name would
739 // reveal the glibc version. However, in more recent GNU/Linux
740 // installations, there is no symlink. Thus we instead use a more
741 // robust check of opening the libc shared object and looking at the
742 // .dynstr section, and finding the max version number of symbols
743 // that start with "GLIBC_2.".
744 const glibc_so_basename = "libc.so.6";
745 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
746 error.NameTooLong => unreachable,
747 error.InvalidUtf8 => unreachable, // Windows only
748 error.BadPathName => unreachable, // Windows only
749 error.PipeBusy => unreachable, // Windows-only
750 error.SharingViolation => unreachable, // Windows-only
751 error.NetworkNotFound => unreachable, // Windows-only
752 error.FileLocksNotSupported => unreachable, // No lock requested.
753 error.NoSpaceLeft => unreachable, // read-only
754 error.PathAlreadyExists => unreachable, // read-only
755 error.DeviceBusy => unreachable, // read-only
756 error.FileBusy => unreachable, // read-only
757 error.InvalidHandle => unreachable, // should not be in the error set
758 error.WouldBlock => unreachable, // not using O_NONBLOCK
759 error.NoDevice => unreachable, // not asking for a special device
760
761 error.AccessDenied,
762 error.FileNotFound,
763 error.NotDir,
764 error.IsDir,
765 => return error.GLibCNotFound,
766
767 error.FileTooBig => return error.Unexpected,
768
769 error.ProcessFdQuotaExceeded,
770 error.SystemFdQuotaExceeded,
771 error.SystemResources,
772 error.SymLinkLoop,
773 error.Unexpected,
774 => |e| return e,
775 };
776 defer f.close();
777
778 return glibcVerFromSoFile(f) catch |err| switch (err) {
779 error.InvalidElfMagic,
780 error.InvalidElfEndian,
781 error.InvalidElfClass,
782 error.InvalidElfFile,
783 error.InvalidElfVersion,
784 error.InvalidGnuLibCVersion,
785 error.UnexpectedEndOfFile,
786 => return error.GLibCNotFound,
787
788 error.SystemResources,
789 error.UnableToReadElfFile,
790 error.Unexpected,
791 error.FileSystem,
792 => |e| return e,
793 };
794}
795
796fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
797 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
798 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
799 const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
800 const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf));
801 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
802 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
803 elf.ELFDATA2LSB => .little,
804 elf.ELFDATA2MSB => .big,
805 else => return error.InvalidElfEndian,
806 };
807 const need_bswap = elf_endian != native_endian;
808 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
809
810 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
811 elf.ELFCLASS32 => false,
812 elf.ELFCLASS64 => true,
813 else => return error.InvalidElfClass,
814 };
815 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
816 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
817 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
818 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
819 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
820 if (sh_buf.len < shentsize) return error.InvalidElfFile;
821
822 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
823 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
824 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
825 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
826 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
827 var strtab_buf: [4096:0]u8 = undefined;
828 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
829 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
830 const shstrtab = strtab_buf[0..shstrtab_read_len];
831 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
832 var sh_i: u16 = 0;
833 const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
834 // Reserve some bytes so that we can deref the 64-bit struct fields
835 // even when the ELF file is 32-bits.
836 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
837 const sh_read_byte_len = try preadMin(
838 file,
839 sh_buf[0 .. sh_buf.len - sh_reserve],
840 shoff,
841 shentsize,
842 );
843 var sh_buf_i: usize = 0;
844 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
845 sh_i += 1;
846 shoff += shentsize;
847 sh_buf_i += shentsize;
848 }) {
849 const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
850 const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
851 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
852 const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
853 if (mem.eql(u8, sh_name, ".dynstr")) {
854 break :find_dyn_str .{
855 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
856 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
857 };
858 }
859 }
860 } else return error.InvalidGnuLibCVersion;
861
862 // Here we loop over all the strings in the dynstr string table, assuming that any
863 // strings that start with "GLIBC_2." indicate the existence of such a glibc version,
864 // and furthermore, that the system-installed glibc is at minimum that version.
865
866 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
867 // Here I use double this value plus some headroom. This makes it only need
868 // a single read syscall here.
869 var buf: [80000]u8 = undefined;
870 if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
871
872 const dynstr_size: usize = @intCast(dynstr.size);
873 const dynstr_bytes = buf[0..dynstr_size];
874 _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
875 var it = mem.splitScalar(u8, dynstr_bytes, 0);
876 var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
877 while (it.next()) |s| {
878 if (mem.startsWith(u8, s, "GLIBC_2.")) {
879 const chopped = s["GLIBC_".len..];
880 const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
881 error.Overflow => return error.InvalidGnuLibCVersion,
882 error.InvalidVersion => return error.InvalidGnuLibCVersion,
883 };
884 switch (ver.order(max_ver)) {
885 .gt => max_ver = ver,
886 .lt, .eq => continue,
887 }
888 }
889 }
890 return max_ver;
891}
892
893/// In the past, this function attempted to use the executable's own binary if it was dynamically
894/// linked to answer both the C ABI question and the dynamic linker question. However, this
895/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
896/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc
897/// version. The problem is that libc.so.6 glibc version will match that of the system while
898/// the dynamic linker will match that of the compiler binary. Executables with these versions
899/// mismatching will fail to run.
900///
901/// Therefore, this function works the same regardless of whether the compiler binary is
902/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
903/// answer to these questions, or if there is a shebang line, then it chases the referenced
904/// file recursively. If that does not provide the answer, then the function falls back to
905/// defaults.
906fn detectAbiAndDynamicLinker(
907 cpu: Target.Cpu,
908 os: Target.Os,
909 query: Target.Query,
910) DetectError!Target {
911 const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
912 const is_linux = builtin.target.os.tag == .linux;
913 const is_solarish = builtin.target.os.tag.isSolarish();
914 const have_all_info = query.dynamic_linker.get() != null and
915 query.abi != null and (!is_linux or query.abi.?.isGnu());
916 const os_is_non_native = query.os_tag != null;
917 // The Solaris/illumos environment is always the same.
918 if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) {
919 return defaultAbiAndDynamicLinker(cpu, os, query);
920 }
921 if (query.abi) |abi| {
922 if (abi.isMusl()) {
923 // musl implies static linking.
924 return defaultAbiAndDynamicLinker(cpu, os, query);
925 }
926 }
927 // The current target's ABI cannot be relied on for this. For example, we may build the zig
928 // compiler for target riscv64-linux-musl and provide a tarball for users to download.
929 // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
930 // and supported by Zig. But that means that we must detect the system ABI here rather than
931 // relying on `builtin.target`.
932 const all_abis = comptime blk: {
933 assert(@intFromEnum(Target.Abi.none) == 0);
934 const fields = std.meta.fields(Target.Abi)[1..];
935 var array: [fields.len]Target.Abi = undefined;
936 for (fields, 0..) |field, i| {
937 array[i] = @field(Target.Abi, field.name);
938 }
939 break :blk array;
940 };
941 var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
942 var ld_info_list_len: usize = 0;
943 const ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch);
944
945 for (all_abis) |abi| {
946 // This may be a nonsensical parameter. We detect this with
947 // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`.
948 const target: Target = .{
949 .cpu = cpu,
950 .os = os,
951 .abi = abi,
952 .ofmt = ofmt,
953 };
954 const ld = target.standardDynamicLinkerPath();
955 if (ld.get() == null) continue;
956
957 ld_info_list_buffer[ld_info_list_len] = .{
958 .ld = ld,
959 .abi = abi,
960 };
961 ld_info_list_len += 1;
962 }
963 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
964
965 // Best case scenario: the executable is dynamically linked, and we can iterate
966 // over our own shared objects and find a dynamic linker.
967 const elf_file = blk: {
968 // This block looks for a shebang line in /usr/bin/env,
969 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
970 // doing the same logic recursively in case it finds another shebang line.
971
972 // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
973 // reasonably reliable path to start with.
974 var file_name: []const u8 = "/usr/bin/env";
975 // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
976 var buffer: [258]u8 = undefined;
977 while (true) {
978 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
979 error.NoSpaceLeft => unreachable,
980 error.NameTooLong => unreachable,
981 error.PathAlreadyExists => unreachable,
982 error.SharingViolation => unreachable,
983 error.InvalidUtf8 => unreachable,
984 error.BadPathName => unreachable,
985 error.PipeBusy => unreachable,
986 error.FileLocksNotSupported => unreachable,
987 error.WouldBlock => unreachable,
988 error.FileBusy => unreachable, // opened without write permissions
989
990 error.IsDir,
991 error.NotDir,
992 error.InvalidHandle,
993 error.AccessDenied,
994 error.NoDevice,
995 error.FileNotFound,
996 error.NetworkNotFound,
997 error.FileTooBig,
998 error.Unexpected,
999 => |e| {
1000 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
1001 return defaultAbiAndDynamicLinker(cpu, os, query);
1002 },
1003
1004 else => |e| return e,
1005 };
1006 errdefer file.close();
1007
1008 const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) {
1009 error.UnexpectedEndOfFile,
1010 error.UnableToReadElfFile,
1011 => break :blk file,
1012
1013 else => |e| return e,
1014 };
1015 const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;
1016 const line = buffer[0..newline];
1017 if (!mem.startsWith(u8, line, "#!")) break :blk file;
1018 var it = mem.tokenizeScalar(u8, line[2..], ' ');
1019 file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);
1020 file.close();
1021 }
1022 };
1023 defer elf_file.close();
1024
1025 // If Zig is statically linked, such as via distributed binary static builds, the above
1026 // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file.
1027 // TODO: inline this function and combine the buffer we already read above to find
1028 // the possible shebang line with the buffer we use for the ELF header.
1029 return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {
1030 error.FileSystem,
1031 error.SystemResources,
1032 error.SymLinkLoop,
1033 error.ProcessFdQuotaExceeded,
1034 error.SystemFdQuotaExceeded,
1035 => |e| return e,
1036
1037 error.UnableToReadElfFile,
1038 error.InvalidElfClass,
1039 error.InvalidElfVersion,
1040 error.InvalidElfEndian,
1041 error.InvalidElfFile,
1042 error.InvalidElfMagic,
1043 error.Unexpected,
1044 error.UnexpectedEndOfFile,
1045 error.NameTooLong,
1046 // Finally, we fall back on the standard path.
1047 => |e| {
1048 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
1049 return defaultAbiAndDynamicLinker(cpu, os, query);
1050 },
1051 };
1052}
1053
1054fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
1055 const abi = query.abi orelse Target.Abi.default(cpu.arch, os);
1056 return .{
1057 .cpu = cpu,
1058 .os = os,
1059 .abi = abi,
1060 .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
1061 .dynamic_linker = if (query.dynamic_linker.get() == null)
1062 Target.standardDynamicLinkerPath_cpu_os_abi(cpu, os.tag, abi)
1063 else
1064 query.dynamic_linker,
1065 };
1066}
1067
1068const LdInfo = struct {
1069 ld: Target.DynamicLinker,
1070 abi: Target.Abi,
1071};
1072
1073fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
1074 var i: usize = 0;
1075 while (i < min_read_len) {
1076 const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
1077 error.OperationAborted => unreachable, // Windows-only
1078 error.WouldBlock => unreachable, // Did not request blocking mode
1079 error.NotOpenForReading => unreachable,
1080 error.SystemResources => return error.SystemResources,
1081 error.IsDir => return error.UnableToReadElfFile,
1082 error.BrokenPipe => return error.UnableToReadElfFile,
1083 error.Unseekable => return error.UnableToReadElfFile,
1084 error.ConnectionResetByPeer => return error.UnableToReadElfFile,
1085 error.ConnectionTimedOut => return error.UnableToReadElfFile,
1086 error.SocketNotConnected => return error.UnableToReadElfFile,
1087 error.NetNameDeleted => return error.UnableToReadElfFile,
1088 error.Unexpected => return error.Unexpected,
1089 error.InputOutput => return error.FileSystem,
1090 error.AccessDenied => return error.Unexpected,
1091 };
1092 if (len == 0) return error.UnexpectedEndOfFile;
1093 i += len;
1094 }
1095 return i;
1096}
1097
1098fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
1099 if (is_64) {
1100 if (need_bswap) {
1101 return @byteSwap(int_64);
1102 } else {
1103 return int_64;
1104 }
1105 } else {
1106 if (need_bswap) {
1107 return @byteSwap(int_32);
1108 } else {
1109 return int_32;
1110 }
1111 }
1112}
1113
1114const builtin = @import("builtin");
1115const std = @import("../std.zig");
1116const mem = std.mem;
1117const elf = std.elf;
1118const fs = std.fs;
1119const assert = std.debug.assert;
1120const Target = std.Target;
1121const native_endian = builtin.cpu.arch.endian();
1122
81123test {
91124 _ = NativePaths;
10 _ = NativeTargetInfo;
111125
121126 _ = darwin;
131127 _ = linux;
lib/std/zig/system/NativePaths.zig+1-3
......@@ -5,7 +5,6 @@ const process = std.process;
55const mem = std.mem;
66
77const NativePaths = @This();
8const NativeTargetInfo = std.zig.system.NativeTargetInfo;
98
109arena: Allocator,
1110include_dirs: std.ArrayListUnmanaged([]const u8) = .{},
......@@ -14,8 +13,7 @@ framework_dirs: std.ArrayListUnmanaged([]const u8) = .{},
1413rpaths: std.ArrayListUnmanaged([]const u8) = .{},
1514warnings: std.ArrayListUnmanaged([]const u8) = .{},
1615
17pub fn detect(arena: Allocator, native_info: NativeTargetInfo) !NativePaths {
18 const native_target = native_info.target;
16pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
1917 var self: NativePaths = .{ .arena = arena };
2018 var is_nix = false;
2119 if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
lib/std/zig/system/NativeTargetInfo.zig deleted-1130
......@@ -1,1130 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const mem = std.mem;
4const assert = std.debug.assert;
5const fs = std.fs;
6const elf = std.elf;
7const native_endian = builtin.cpu.arch.endian();
8
9const NativeTargetInfo = @This();
10const Target = std.Target;
11const Allocator = std.mem.Allocator;
12const windows = std.zig.system.windows;
13const darwin = std.zig.system.darwin;
14const linux = std.zig.system.linux;
15
16target: Target,
17dynamic_linker: DynamicLinker = DynamicLinker{},
18
19pub const DynamicLinker = Target.DynamicLinker;
20
21pub const DetectError = error{
22 FileSystem,
23 SystemResources,
24 SymLinkLoop,
25 ProcessFdQuotaExceeded,
26 SystemFdQuotaExceeded,
27 DeviceBusy,
28 OSVersionDetectionFail,
29 Unexpected,
30};
31
32/// Given a `Target.Query`, which specifies in detail which parts of the
33/// target should be detected natively, which should be standard or default,
34/// and which are provided explicitly, this function resolves the native
35/// components by detecting the native system, and then resolves
36/// standard/default parts relative to that.
37pub fn detect(query: Target.Query) DetectError!NativeTargetInfo {
38 var os = query.getOsTag().defaultVersionRange(query.getCpuArch());
39 if (query.os_tag == null) {
40 switch (builtin.target.os.tag) {
41 .linux => {
42 const uts = std.os.uname();
43 const release = mem.sliceTo(&uts.release, 0);
44 // The release field sometimes has a weird format,
45 // `Version.parse` will attempt to find some meaningful interpretation.
46 if (std.SemanticVersion.parse(release)) |ver| {
47 os.version_range.linux.range.min = ver;
48 os.version_range.linux.range.max = ver;
49 } else |err| switch (err) {
50 error.Overflow => {},
51 error.InvalidVersion => {},
52 }
53 },
54 .solaris, .illumos => {
55 const uts = std.os.uname();
56 const release = mem.sliceTo(&uts.release, 0);
57 if (std.SemanticVersion.parse(release)) |ver| {
58 os.version_range.semver.min = ver;
59 os.version_range.semver.max = ver;
60 } else |err| switch (err) {
61 error.Overflow => {},
62 error.InvalidVersion => {},
63 }
64 },
65 .windows => {
66 const detected_version = windows.detectRuntimeVersion();
67 os.version_range.windows.min = detected_version;
68 os.version_range.windows.max = detected_version;
69 },
70 .macos => try darwin.macos.detect(&os),
71 .freebsd, .netbsd, .dragonfly => {
72 const key = switch (builtin.target.os.tag) {
73 .freebsd => "kern.osreldate",
74 .netbsd, .dragonfly => "kern.osrevision",
75 else => unreachable,
76 };
77 var value: u32 = undefined;
78 var len: usize = @sizeOf(@TypeOf(value));
79
80 std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
81 error.NameTooLong => unreachable, // constant, known good value
82 error.PermissionDenied => unreachable, // only when setting values,
83 error.SystemResources => unreachable, // memory already on the stack
84 error.UnknownName => unreachable, // constant, known good value
85 error.Unexpected => return error.OSVersionDetectionFail,
86 };
87
88 switch (builtin.target.os.tag) {
89 .freebsd => {
90 // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
91 // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
92 // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
93 // e.g. 492101 = 4.11-STABLE = 4.(9+2)
94 const major = value / 100_000;
95 const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
96 const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
97 const patch = value % 1_000;
98 os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
99 os.version_range.semver.max = os.version_range.semver.min;
100 },
101 .netbsd => {
102 // #define __NetBSD_Version__ MMmmrrpp00
103 //
104 // M = major version
105 // m = minor version; a minor number of 99 indicates current.
106 // r = 0 (*)
107 // p = patchlevel
108 const major = value / 100_000_000;
109 const minor = value % 100_000_000 / 1_000_000;
110 const patch = value % 10_000 / 100;
111 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
112 os.version_range.semver.max = os.version_range.semver.min;
113 },
114 .dragonfly => {
115 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
116 // flat base10 format: Mmmmpp
117 // M = major
118 // m = minor; odd-numbers indicate current dev branch
119 // p = patch
120 const major = value / 100_000;
121 const minor = value % 100_000 / 100;
122 const patch = value % 100;
123 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
124 os.version_range.semver.max = os.version_range.semver.min;
125 },
126 else => unreachable,
127 }
128 },
129 .openbsd => {
130 const mib: [2]c_int = [_]c_int{
131 std.os.CTL.KERN,
132 std.os.KERN.OSRELEASE,
133 };
134 var buf: [64]u8 = undefined;
135 // consider that sysctl result includes null-termination
136 // reserve 1 byte to ensure we never overflow when appending ".0"
137 var len: usize = buf.len - 1;
138
139 std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
140 error.NameTooLong => unreachable, // constant, known good value
141 error.PermissionDenied => unreachable, // only when setting values,
142 error.SystemResources => unreachable, // memory already on the stack
143 error.UnknownName => unreachable, // constant, known good value
144 error.Unexpected => return error.OSVersionDetectionFail,
145 };
146
147 // append ".0" to satisfy semver
148 buf[len - 1] = '.';
149 buf[len] = '0';
150 len += 1;
151
152 if (std.SemanticVersion.parse(buf[0..len])) |ver| {
153 os.version_range.semver.min = ver;
154 os.version_range.semver.max = ver;
155 } else |_| {
156 return error.OSVersionDetectionFail;
157 }
158 },
159 else => {
160 // Unimplemented, fall back to default version range.
161 },
162 }
163 }
164
165 if (query.os_version_min) |min| switch (min) {
166 .none => {},
167 .semver => |semver| switch (query.getOsTag()) {
168 .linux => os.version_range.linux.range.min = semver,
169 else => os.version_range.semver.min = semver,
170 },
171 .windows => |win_ver| os.version_range.windows.min = win_ver,
172 };
173
174 if (query.os_version_max) |max| switch (max) {
175 .none => {},
176 .semver => |semver| switch (query.getOsTag()) {
177 .linux => os.version_range.linux.range.max = semver,
178 else => os.version_range.semver.max = semver,
179 },
180 .windows => |win_ver| os.version_range.windows.max = win_ver,
181 };
182
183 if (query.glibc_version) |glibc| {
184 assert(query.isGnuLibC());
185 os.version_range.linux.glibc = glibc;
186 }
187
188 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
189 // native CPU architecture as being different than the current target), we use this:
190 const cpu_arch = query.getCpuArch();
191
192 const cpu = switch (query.cpu_model) {
193 .native => detectNativeCpuAndFeatures(cpu_arch, os, query),
194 .baseline => Target.Cpu.baseline(cpu_arch),
195 .determined_by_cpu_arch => if (query.cpu_arch == null)
196 detectNativeCpuAndFeatures(cpu_arch, os, query)
197 else
198 Target.Cpu.baseline(cpu_arch),
199 .explicit => |model| model.toCpu(cpu_arch),
200 } orelse backup_cpu_detection: {
201 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
202 };
203 var result = try detectAbiAndDynamicLinker(cpu, os, query);
204 // For x86, we need to populate some CPU feature flags depending on architecture
205 // and mode:
206 // * 16bit_mode => if the abi is code16
207 // * 32bit_mode => if the arch is x86
208 // However, the "mode" flags can be used as overrides, so if the user explicitly
209 // sets one of them, that takes precedence.
210 switch (cpu_arch) {
211 .x86 => {
212 if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{
213 .@"16bit_mode", .@"32bit_mode",
214 })) {
215 switch (result.target.abi) {
216 .code16 => result.target.cpu.features.addFeature(
217 @intFromEnum(Target.x86.Feature.@"16bit_mode"),
218 ),
219 else => result.target.cpu.features.addFeature(
220 @intFromEnum(Target.x86.Feature.@"32bit_mode"),
221 ),
222 }
223 }
224 },
225 .arm, .armeb => {
226 // XXX What do we do if the target has the noarm feature?
227 // What do we do if the user specifies +thumb_mode?
228 },
229 .thumb, .thumbeb => {
230 result.target.cpu.features.addFeature(
231 @intFromEnum(Target.arm.Feature.thumb_mode),
232 );
233 },
234 else => {},
235 }
236 query.updateCpuFeatures(&result.target.cpu.features);
237 return result;
238}
239
240/// In the past, this function attempted to use the executable's own binary if it was dynamically
241/// linked to answer both the C ABI question and the dynamic linker question. However, this
242/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
243/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc
244/// version. The problem is that libc.so.6 glibc version will match that of the system while
245/// the dynamic linker will match that of the compiler binary. Executables with these versions
246/// mismatching will fail to run.
247///
248/// Therefore, this function works the same regardless of whether the compiler binary is
249/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
250/// answer to these questions, or if there is a shebang line, then it chases the referenced
251/// file recursively. If that does not provide the answer, then the function falls back to
252/// defaults.
253fn detectAbiAndDynamicLinker(
254 cpu: Target.Cpu,
255 os: Target.Os,
256 query: Target.Query,
257) DetectError!NativeTargetInfo {
258 const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
259 const is_linux = builtin.target.os.tag == .linux;
260 const is_solarish = builtin.target.os.tag.isSolarish();
261 const have_all_info = query.dynamic_linker.get() != null and
262 query.abi != null and (!is_linux or query.abi.?.isGnu());
263 const os_is_non_native = query.os_tag != null;
264 // The Solaris/illumos environment is always the same.
265 if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) {
266 return defaultAbiAndDynamicLinker(cpu, os, query);
267 }
268 if (query.abi) |abi| {
269 if (abi.isMusl()) {
270 // musl implies static linking.
271 return defaultAbiAndDynamicLinker(cpu, os, query);
272 }
273 }
274 // The current target's ABI cannot be relied on for this. For example, we may build the zig
275 // compiler for target riscv64-linux-musl and provide a tarball for users to download.
276 // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
277 // and supported by Zig. But that means that we must detect the system ABI here rather than
278 // relying on `builtin.target`.
279 const all_abis = comptime blk: {
280 assert(@intFromEnum(Target.Abi.none) == 0);
281 const fields = std.meta.fields(Target.Abi)[1..];
282 var array: [fields.len]Target.Abi = undefined;
283 for (fields, 0..) |field, i| {
284 array[i] = @field(Target.Abi, field.name);
285 }
286 break :blk array;
287 };
288 var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
289 var ld_info_list_len: usize = 0;
290 const ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch);
291
292 for (all_abis) |abi| {
293 // This may be a nonsensical parameter. We detect this with
294 // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`.
295 const target: Target = .{
296 .cpu = cpu,
297 .os = os,
298 .abi = abi,
299 .ofmt = ofmt,
300 };
301 const ld = target.standardDynamicLinkerPath();
302 if (ld.get() == null) continue;
303
304 ld_info_list_buffer[ld_info_list_len] = .{
305 .ld = ld,
306 .abi = abi,
307 };
308 ld_info_list_len += 1;
309 }
310 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
311
312 // Best case scenario: the executable is dynamically linked, and we can iterate
313 // over our own shared objects and find a dynamic linker.
314 const elf_file = blk: {
315 // This block looks for a shebang line in /usr/bin/env,
316 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
317 // doing the same logic recursively in case it finds another shebang line.
318
319 // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
320 // reasonably reliable path to start with.
321 var file_name: []const u8 = "/usr/bin/env";
322 // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
323 var buffer: [258]u8 = undefined;
324 while (true) {
325 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
326 error.NoSpaceLeft => unreachable,
327 error.NameTooLong => unreachable,
328 error.PathAlreadyExists => unreachable,
329 error.SharingViolation => unreachable,
330 error.InvalidUtf8 => unreachable,
331 error.BadPathName => unreachable,
332 error.PipeBusy => unreachable,
333 error.FileLocksNotSupported => unreachable,
334 error.WouldBlock => unreachable,
335 error.FileBusy => unreachable, // opened without write permissions
336
337 error.IsDir,
338 error.NotDir,
339 error.InvalidHandle,
340 error.AccessDenied,
341 error.NoDevice,
342 error.FileNotFound,
343 error.NetworkNotFound,
344 error.FileTooBig,
345 error.Unexpected,
346 => |e| {
347 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
348 return defaultAbiAndDynamicLinker(cpu, os, query);
349 },
350
351 else => |e| return e,
352 };
353 errdefer file.close();
354
355 const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) {
356 error.UnexpectedEndOfFile,
357 error.UnableToReadElfFile,
358 => break :blk file,
359
360 else => |e| return e,
361 };
362 const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;
363 const line = buffer[0..newline];
364 if (!mem.startsWith(u8, line, "#!")) break :blk file;
365 var it = mem.tokenizeScalar(u8, line[2..], ' ');
366 file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);
367 file.close();
368 }
369 };
370 defer elf_file.close();
371
372 // If Zig is statically linked, such as via distributed binary static builds, the above
373 // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file.
374 // TODO: inline this function and combine the buffer we already read above to find
375 // the possible shebang line with the buffer we use for the ELF header.
376 return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {
377 error.FileSystem,
378 error.SystemResources,
379 error.SymLinkLoop,
380 error.ProcessFdQuotaExceeded,
381 error.SystemFdQuotaExceeded,
382 => |e| return e,
383
384 error.UnableToReadElfFile,
385 error.InvalidElfClass,
386 error.InvalidElfVersion,
387 error.InvalidElfEndian,
388 error.InvalidElfFile,
389 error.InvalidElfMagic,
390 error.Unexpected,
391 error.UnexpectedEndOfFile,
392 error.NameTooLong,
393 // Finally, we fall back on the standard path.
394 => |e| {
395 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
396 return defaultAbiAndDynamicLinker(cpu, os, query);
397 },
398 };
399}
400
401fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
402 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
403 error.NameTooLong => unreachable,
404 error.InvalidUtf8 => unreachable,
405 error.BadPathName => unreachable,
406 error.DeviceBusy => unreachable,
407 error.NetworkNotFound => unreachable, // Windows-only
408
409 error.FileNotFound,
410 error.NotDir,
411 error.InvalidHandle,
412 error.AccessDenied,
413 error.NoDevice,
414 => return error.GLibCNotFound,
415
416 error.ProcessFdQuotaExceeded,
417 error.SystemFdQuotaExceeded,
418 error.SystemResources,
419 error.SymLinkLoop,
420 error.Unexpected,
421 => |e| return e,
422 };
423 defer dir.close();
424
425 // Now we have a candidate for the path to libc shared object. In
426 // the past, we used readlink() here because the link name would
427 // reveal the glibc version. However, in more recent GNU/Linux
428 // installations, there is no symlink. Thus we instead use a more
429 // robust check of opening the libc shared object and looking at the
430 // .dynstr section, and finding the max version number of symbols
431 // that start with "GLIBC_2.".
432 const glibc_so_basename = "libc.so.6";
433 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
434 error.NameTooLong => unreachable,
435 error.InvalidUtf8 => unreachable, // Windows only
436 error.BadPathName => unreachable, // Windows only
437 error.PipeBusy => unreachable, // Windows-only
438 error.SharingViolation => unreachable, // Windows-only
439 error.NetworkNotFound => unreachable, // Windows-only
440 error.FileLocksNotSupported => unreachable, // No lock requested.
441 error.NoSpaceLeft => unreachable, // read-only
442 error.PathAlreadyExists => unreachable, // read-only
443 error.DeviceBusy => unreachable, // read-only
444 error.FileBusy => unreachable, // read-only
445 error.InvalidHandle => unreachable, // should not be in the error set
446 error.WouldBlock => unreachable, // not using O_NONBLOCK
447 error.NoDevice => unreachable, // not asking for a special device
448
449 error.AccessDenied,
450 error.FileNotFound,
451 error.NotDir,
452 error.IsDir,
453 => return error.GLibCNotFound,
454
455 error.FileTooBig => return error.Unexpected,
456
457 error.ProcessFdQuotaExceeded,
458 error.SystemFdQuotaExceeded,
459 error.SystemResources,
460 error.SymLinkLoop,
461 error.Unexpected,
462 => |e| return e,
463 };
464 defer f.close();
465
466 return glibcVerFromSoFile(f) catch |err| switch (err) {
467 error.InvalidElfMagic,
468 error.InvalidElfEndian,
469 error.InvalidElfClass,
470 error.InvalidElfFile,
471 error.InvalidElfVersion,
472 error.InvalidGnuLibCVersion,
473 error.UnexpectedEndOfFile,
474 => return error.GLibCNotFound,
475
476 error.SystemResources,
477 error.UnableToReadElfFile,
478 error.Unexpected,
479 error.FileSystem,
480 => |e| return e,
481 };
482}
483
484fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
485 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
486 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
487 const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
488 const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf));
489 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
490 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
491 elf.ELFDATA2LSB => .little,
492 elf.ELFDATA2MSB => .big,
493 else => return error.InvalidElfEndian,
494 };
495 const need_bswap = elf_endian != native_endian;
496 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
497
498 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
499 elf.ELFCLASS32 => false,
500 elf.ELFCLASS64 => true,
501 else => return error.InvalidElfClass,
502 };
503 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
504 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
505 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
506 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
507 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
508 if (sh_buf.len < shentsize) return error.InvalidElfFile;
509
510 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
511 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
512 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
513 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
514 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
515 var strtab_buf: [4096:0]u8 = undefined;
516 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
517 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
518 const shstrtab = strtab_buf[0..shstrtab_read_len];
519 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
520 var sh_i: u16 = 0;
521 const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
522 // Reserve some bytes so that we can deref the 64-bit struct fields
523 // even when the ELF file is 32-bits.
524 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
525 const sh_read_byte_len = try preadMin(
526 file,
527 sh_buf[0 .. sh_buf.len - sh_reserve],
528 shoff,
529 shentsize,
530 );
531 var sh_buf_i: usize = 0;
532 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
533 sh_i += 1;
534 shoff += shentsize;
535 sh_buf_i += shentsize;
536 }) {
537 const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
538 const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
539 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
540 const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
541 if (mem.eql(u8, sh_name, ".dynstr")) {
542 break :find_dyn_str .{
543 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
544 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
545 };
546 }
547 }
548 } else return error.InvalidGnuLibCVersion;
549
550 // Here we loop over all the strings in the dynstr string table, assuming that any
551 // strings that start with "GLIBC_2." indicate the existence of such a glibc version,
552 // and furthermore, that the system-installed glibc is at minimum that version.
553
554 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
555 // Here I use double this value plus some headroom. This makes it only need
556 // a single read syscall here.
557 var buf: [80000]u8 = undefined;
558 if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
559
560 const dynstr_size: usize = @intCast(dynstr.size);
561 const dynstr_bytes = buf[0..dynstr_size];
562 _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
563 var it = mem.splitScalar(u8, dynstr_bytes, 0);
564 var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
565 while (it.next()) |s| {
566 if (mem.startsWith(u8, s, "GLIBC_2.")) {
567 const chopped = s["GLIBC_".len..];
568 const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
569 error.Overflow => return error.InvalidGnuLibCVersion,
570 error.InvalidVersion => return error.InvalidGnuLibCVersion,
571 };
572 switch (ver.order(max_ver)) {
573 .gt => max_ver = ver,
574 .lt, .eq => continue,
575 }
576 }
577 }
578 return max_ver;
579}
580
581fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion {
582 // example: "libc-2.3.4.so"
583 // example: "libc-2.27.so"
584 // example: "ld-2.33.so"
585 const suffix = ".so";
586 if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
587 return error.UnrecognizedGnuLibCFileName;
588 }
589 // chop off "libc-" and ".so"
590 const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
591 return Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) {
592 error.Overflow => return error.InvalidGnuLibCVersion,
593 error.InvalidVersion => return error.InvalidGnuLibCVersion,
594 };
595}
596
597test glibcVerFromLinkName {
598 try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist"));
599 try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-"));
600
601 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-"));
602 try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-"));
603 try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-"));
604 try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-"));
605 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
606}
607
608pub const AbiAndDynamicLinkerFromFileError = error{
609 FileSystem,
610 SystemResources,
611 SymLinkLoop,
612 ProcessFdQuotaExceeded,
613 SystemFdQuotaExceeded,
614 UnableToReadElfFile,
615 InvalidElfClass,
616 InvalidElfVersion,
617 InvalidElfEndian,
618 InvalidElfFile,
619 InvalidElfMagic,
620 Unexpected,
621 UnexpectedEndOfFile,
622 NameTooLong,
623};
624
625pub fn abiAndDynamicLinkerFromFile(
626 file: fs.File,
627 cpu: Target.Cpu,
628 os: Target.Os,
629 ld_info_list: []const LdInfo,
630 query: Target.Query,
631) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
632 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
633 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
634 const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
635 const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf));
636 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
637 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
638 elf.ELFDATA2LSB => .little,
639 elf.ELFDATA2MSB => .big,
640 else => return error.InvalidElfEndian,
641 };
642 const need_bswap = elf_endian != native_endian;
643 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
644
645 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
646 elf.ELFCLASS32 => false,
647 elf.ELFCLASS64 => true,
648 else => return error.InvalidElfClass,
649 };
650 var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
651 const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
652 const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
653
654 var result: NativeTargetInfo = .{
655 .target = .{
656 .cpu = cpu,
657 .os = os,
658 .abi = query.abi orelse Target.Abi.default(cpu.arch, os),
659 .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
660 },
661 .dynamic_linker = query.dynamic_linker,
662 };
663 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
664 const look_for_ld = query.dynamic_linker.get() == null;
665
666 var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
667 if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
668
669 var ph_i: u16 = 0;
670 while (ph_i < phnum) {
671 // Reserve some bytes so that we can deref the 64-bit struct fields
672 // even when the ELF file is 32-bits.
673 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
674 const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
675 var ph_buf_i: usize = 0;
676 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
677 ph_i += 1;
678 phoff += phentsize;
679 ph_buf_i += phentsize;
680 }) {
681 const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
682 const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
683 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
684 switch (p_type) {
685 elf.PT_INTERP => if (look_for_ld) {
686 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
687 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
688 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
689 const filesz = @as(usize, @intCast(p_filesz));
690 _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
691 // PT_INTERP includes a null byte in filesz.
692 const len = filesz - 1;
693 // dynamic_linker.max_byte is "max", not "len".
694 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
695 result.dynamic_linker.max_byte = @as(u8, @intCast(len - 1));
696
697 // Use it to determine ABI.
698 const full_ld_path = result.dynamic_linker.buffer[0..len];
699 for (ld_info_list) |ld_info| {
700 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
701 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
702 result.target.abi = ld_info.abi;
703 break;
704 }
705 }
706 },
707 // We only need this for detecting glibc version.
708 elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
709 query.glibc_version == null)
710 {
711 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
712 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
713 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
714 const dyn_num = p_filesz / dyn_size;
715 var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
716 var dyn_i: usize = 0;
717 dyn: while (dyn_i < dyn_num) {
718 // Reserve some bytes so that we can deref the 64-bit struct fields
719 // even when the ELF file is 32-bits.
720 const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
721 const dyn_read_byte_len = try preadMin(
722 file,
723 dyn_buf[0 .. dyn_buf.len - dyn_reserve],
724 dyn_off,
725 dyn_size,
726 );
727 var dyn_buf_i: usize = 0;
728 while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
729 dyn_i += 1;
730 dyn_off += dyn_size;
731 dyn_buf_i += dyn_size;
732 }) {
733 const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
734 const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
735 const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
736 const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
737 if (tag == elf.DT_RUNPATH) {
738 rpath_offset = val;
739 break :dyn;
740 }
741 }
742 }
743 },
744 else => continue,
745 }
746 }
747 }
748
749 if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
750 query.glibc_version == null)
751 {
752 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
753
754 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
755 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
756 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
757
758 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
759 if (sh_buf.len < shentsize) return error.InvalidElfFile;
760
761 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
762 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
763 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
764 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
765 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
766 var strtab_buf: [4096:0]u8 = undefined;
767 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
768 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
769 const shstrtab = strtab_buf[0..shstrtab_read_len];
770
771 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
772 var sh_i: u16 = 0;
773 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
774 // Reserve some bytes so that we can deref the 64-bit struct fields
775 // even when the ELF file is 32-bits.
776 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
777 const sh_read_byte_len = try preadMin(
778 file,
779 sh_buf[0 .. sh_buf.len - sh_reserve],
780 shoff,
781 shentsize,
782 );
783 var sh_buf_i: usize = 0;
784 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
785 sh_i += 1;
786 shoff += shentsize;
787 sh_buf_i += shentsize;
788 }) {
789 const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
790 const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
791 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
792 const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
793 if (mem.eql(u8, sh_name, ".dynstr")) {
794 break :find_dyn_str .{
795 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
796 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
797 };
798 }
799 }
800 } else null;
801
802 if (dynstr) |ds| {
803 if (rpath_offset) |rpoff| {
804 if (rpoff > ds.size) return error.InvalidElfFile;
805 const rpoff_file = ds.offset + rpoff;
806 const rp_max_size = ds.size - rpoff;
807
808 const strtab_len = @min(rp_max_size, strtab_buf.len);
809 const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len);
810 const strtab = strtab_buf[0..strtab_read_len];
811
812 const rpath_list = mem.sliceTo(strtab, 0);
813 var it = mem.tokenizeScalar(u8, rpath_list, ':');
814 while (it.next()) |rpath| {
815 if (glibcVerFromRPath(rpath)) |ver| {
816 result.target.os.version_range.linux.glibc = ver;
817 return result;
818 } else |err| switch (err) {
819 error.GLibCNotFound => continue,
820 else => |e| return e,
821 }
822 }
823 }
824 }
825
826 if (result.dynamic_linker.get()) |dl_path| glibc_ver: {
827 // There is no DT_RUNPATH so we try to find libc.so.6 inside the same
828 // directory as the dynamic linker.
829 if (fs.path.dirname(dl_path)) |rpath| {
830 if (glibcVerFromRPath(rpath)) |ver| {
831 result.target.os.version_range.linux.glibc = ver;
832 return result;
833 } else |err| switch (err) {
834 error.GLibCNotFound => {},
835 else => |e| return e,
836 }
837 }
838
839 // So far, no luck. Next we try to see if the information is
840 // present in the symlink data for the dynamic linker path.
841 var link_buf: [std.os.PATH_MAX]u8 = undefined;
842 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
843 error.NameTooLong => unreachable,
844 error.InvalidUtf8 => unreachable, // Windows only
845 error.BadPathName => unreachable, // Windows only
846 error.UnsupportedReparsePointType => unreachable, // Windows only
847 error.NetworkNotFound => unreachable, // Windows only
848
849 error.AccessDenied,
850 error.FileNotFound,
851 error.NotLink,
852 error.NotDir,
853 => break :glibc_ver,
854
855 error.SystemResources,
856 error.FileSystem,
857 error.SymLinkLoop,
858 error.Unexpected,
859 => |e| return e,
860 };
861 result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
862 fs.path.basename(link_name),
863 "ld-",
864 ) catch |err| switch (err) {
865 error.UnrecognizedGnuLibCFileName,
866 error.InvalidGnuLibCVersion,
867 => break :glibc_ver,
868 };
869 return result;
870 }
871
872 // Nothing worked so far. Finally we fall back to hard-coded search paths.
873 // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`.
874 var path_buf: [std.os.PATH_MAX]u8 = undefined;
875 var index: usize = 0;
876 const prefix = "/lib/";
877 const cpu_arch = @tagName(result.target.cpu.arch);
878 const os_tag = @tagName(result.target.os.tag);
879 const abi = @tagName(result.target.abi);
880 @memcpy(path_buf[index..][0..prefix.len], prefix);
881 index += prefix.len;
882 @memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch);
883 index += cpu_arch.len;
884 path_buf[index] = '-';
885 index += 1;
886 @memcpy(path_buf[index..][0..os_tag.len], os_tag);
887 index += os_tag.len;
888 path_buf[index] = '-';
889 index += 1;
890 @memcpy(path_buf[index..][0..abi.len], abi);
891 index += abi.len;
892 const rpath = path_buf[0..index];
893 if (glibcVerFromRPath(rpath)) |ver| {
894 result.target.os.version_range.linux.glibc = ver;
895 return result;
896 } else |err| switch (err) {
897 error.GLibCNotFound => {},
898 else => |e| return e,
899 }
900 }
901
902 return result;
903}
904
905fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
906 var i: usize = 0;
907 while (i < min_read_len) {
908 const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
909 error.OperationAborted => unreachable, // Windows-only
910 error.WouldBlock => unreachable, // Did not request blocking mode
911 error.NotOpenForReading => unreachable,
912 error.SystemResources => return error.SystemResources,
913 error.IsDir => return error.UnableToReadElfFile,
914 error.BrokenPipe => return error.UnableToReadElfFile,
915 error.Unseekable => return error.UnableToReadElfFile,
916 error.ConnectionResetByPeer => return error.UnableToReadElfFile,
917 error.ConnectionTimedOut => return error.UnableToReadElfFile,
918 error.SocketNotConnected => return error.UnableToReadElfFile,
919 error.NetNameDeleted => return error.UnableToReadElfFile,
920 error.Unexpected => return error.Unexpected,
921 error.InputOutput => return error.FileSystem,
922 error.AccessDenied => return error.Unexpected,
923 };
924 if (len == 0) return error.UnexpectedEndOfFile;
925 i += len;
926 }
927 return i;
928}
929
930fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !NativeTargetInfo {
931 const target: Target = .{
932 .cpu = cpu,
933 .os = os,
934 .abi = query.abi orelse Target.Abi.default(cpu.arch, os),
935 .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
936 };
937 return NativeTargetInfo{
938 .target = target,
939 .dynamic_linker = if (query.dynamic_linker.get() == null)
940 target.standardDynamicLinkerPath()
941 else
942 query.dynamic_linker,
943 };
944}
945
946pub const LdInfo = struct {
947 ld: DynamicLinker,
948 abi: Target.Abi,
949};
950
951pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
952 if (is_64) {
953 if (need_bswap) {
954 return @byteSwap(int_64);
955 } else {
956 return int_64;
957 }
958 } else {
959 if (need_bswap) {
960 return @byteSwap(int_32);
961 } else {
962 return int_32;
963 }
964 }
965}
966
967fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu {
968 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
969 // although it is a runtime value, is guaranteed to be one of the architectures in the set
970 // of the respective switch prong.
971 switch (builtin.cpu.arch) {
972 .x86_64, .x86 => {
973 return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query);
974 },
975 else => {},
976 }
977
978 switch (builtin.os.tag) {
979 .linux => return linux.detectNativeCpuAndFeatures(),
980 .macos => return darwin.macos.detectNativeCpuAndFeatures(),
981 .windows => return windows.detectNativeCpuAndFeatures(),
982 else => {},
983 }
984
985 // This architecture does not have CPU model & feature detection yet.
986 // See https://github.com/ziglang/zig/issues/4591
987 return null;
988}
989
990pub const Executor = union(enum) {
991 native,
992 rosetta,
993 qemu: []const u8,
994 wine: []const u8,
995 wasmtime: []const u8,
996 darling: []const u8,
997 bad_dl: []const u8,
998 bad_os_or_cpu,
999};
1000
1001pub const GetExternalExecutorOptions = struct {
1002 allow_darling: bool = true,
1003 allow_qemu: bool = true,
1004 allow_rosetta: bool = true,
1005 allow_wasmtime: bool = true,
1006 allow_wine: bool = true,
1007 qemu_fixes_dl: bool = false,
1008 link_libc: bool = false,
1009};
1010
1011/// Return whether or not the given host is capable of running executables of
1012/// the other target.
1013pub fn getExternalExecutor(
1014 host: NativeTargetInfo,
1015 candidate: *const NativeTargetInfo,
1016 options: GetExternalExecutorOptions,
1017) Executor {
1018 const os_match = host.target.os.tag == candidate.target.os.tag;
1019 const cpu_ok = cpu_ok: {
1020 if (host.target.cpu.arch == candidate.target.cpu.arch)
1021 break :cpu_ok true;
1022
1023 if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .x86)
1024 break :cpu_ok true;
1025
1026 if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm)
1027 break :cpu_ok true;
1028
1029 if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb)
1030 break :cpu_ok true;
1031
1032 // TODO additionally detect incompatible CPU features.
1033 // Note that in some cases the OS kernel will emulate missing CPU features
1034 // when an illegal instruction is encountered.
1035
1036 break :cpu_ok false;
1037 };
1038
1039 var bad_result: Executor = .bad_os_or_cpu;
1040
1041 if (os_match and cpu_ok) native: {
1042 if (options.link_libc) {
1043 if (candidate.dynamic_linker.get()) |candidate_dl| {
1044 fs.cwd().access(candidate_dl, .{}) catch {
1045 bad_result = .{ .bad_dl = candidate_dl };
1046 break :native;
1047 };
1048 }
1049 }
1050 return .native;
1051 }
1052
1053 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
1054 // to emulate the foreign architecture.
1055 if (options.allow_rosetta and os_match and
1056 host.target.os.tag == .macos and host.target.cpu.arch == .aarch64)
1057 {
1058 switch (candidate.target.cpu.arch) {
1059 .x86_64 => return .rosetta,
1060 else => return bad_result,
1061 }
1062 }
1063
1064 // If the OS matches, we can use QEMU to emulate a foreign architecture.
1065 if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
1066 return switch (candidate.target.cpu.arch) {
1067 .aarch64 => Executor{ .qemu = "qemu-aarch64" },
1068 .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
1069 .arm => Executor{ .qemu = "qemu-arm" },
1070 .armeb => Executor{ .qemu = "qemu-armeb" },
1071 .hexagon => Executor{ .qemu = "qemu-hexagon" },
1072 .x86 => Executor{ .qemu = "qemu-i386" },
1073 .m68k => Executor{ .qemu = "qemu-m68k" },
1074 .mips => Executor{ .qemu = "qemu-mips" },
1075 .mipsel => Executor{ .qemu = "qemu-mipsel" },
1076 .mips64 => Executor{ .qemu = "qemu-mips64" },
1077 .mips64el => Executor{ .qemu = "qemu-mips64el" },
1078 .powerpc => Executor{ .qemu = "qemu-ppc" },
1079 .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
1080 .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
1081 .riscv32 => Executor{ .qemu = "qemu-riscv32" },
1082 .riscv64 => Executor{ .qemu = "qemu-riscv64" },
1083 .s390x => Executor{ .qemu = "qemu-s390x" },
1084 .sparc => Executor{ .qemu = "qemu-sparc" },
1085 .sparc64 => Executor{ .qemu = "qemu-sparc64" },
1086 .x86_64 => Executor{ .qemu = "qemu-x86_64" },
1087 else => return bad_result,
1088 };
1089 }
1090
1091 switch (candidate.target.os.tag) {
1092 .windows => {
1093 if (options.allow_wine) {
1094 // x86_64 wine does not support emulating aarch64-windows and
1095 // vice versa.
1096 if (candidate.target.cpu.arch != builtin.cpu.arch) {
1097 return bad_result;
1098 }
1099 switch (candidate.target.ptrBitWidth()) {
1100 32 => return Executor{ .wine = "wine" },
1101 64 => return Executor{ .wine = "wine64" },
1102 else => return bad_result,
1103 }
1104 }
1105 return bad_result;
1106 },
1107 .wasi => {
1108 if (options.allow_wasmtime) {
1109 switch (candidate.target.ptrBitWidth()) {
1110 32 => return Executor{ .wasmtime = "wasmtime" },
1111 else => return bad_result,
1112 }
1113 }
1114 return bad_result;
1115 },
1116 .macos => {
1117 if (options.allow_darling) {
1118 // This check can be loosened once darling adds a QEMU-based emulation
1119 // layer for non-host architectures:
1120 // https://github.com/darlinghq/darling/issues/863
1121 if (candidate.target.cpu.arch != builtin.cpu.arch) {
1122 return bad_result;
1123 }
1124 return Executor{ .darling = "darling" };
1125 }
1126 return bad_result;
1127 },
1128 else => return bad_result,
1129 }
1130}
src/Compilation.zig+21-6
......@@ -6527,7 +6527,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
65276527 try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)});
65286528 }
65296529 }
6530
65316530 try buffer.writer().print(
65326531 \\ }}),
65336532 \\}};
......@@ -6607,15 +6606,31 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
66076606 .{ windows.min, windows.max },
66086607 ),
66096608 }
6610 try buffer.appendSlice("};\n");
6611
6612 try buffer.writer().print(
6613 \\pub const target = std.Target{{
6609 try buffer.appendSlice(
6610 \\};
6611 \\pub const target: std.Target = .{
66146612 \\ .cpu = cpu,
66156613 \\ .os = os,
66166614 \\ .abi = abi,
66176615 \\ .ofmt = object_format,
6618 \\}};
6616 \\
6617 );
6618
6619 if (target.dynamic_linker.get()) |dl| {
6620 try buffer.writer().print(
6621 \\ .dynamic_linker = std.Target.DynamicLinker.init("{s}"),
6622 \\}};
6623 \\
6624 , .{dl});
6625 } else {
6626 try buffer.appendSlice(
6627 \\ .dynamic_linker = std.Target.DynamicLinker.none,
6628 \\};
6629 \\
6630 );
6631 }
6632
6633 try buffer.writer().print(
66196634 \\pub const object_format = std.Target.ObjectFormat.{};
66206635 \\pub const mode = std.builtin.OptimizeMode.{};
66216636 \\pub const link_libc = {};
src/main.zig+62-70
......@@ -321,13 +321,14 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
321321 } else if (mem.eql(u8, cmd, "init")) {
322322 return cmdInit(gpa, arena, cmd_args);
323323 } else if (mem.eql(u8, cmd, "targets")) {
324 const info = try detectNativeTargetInfo(.{});
324 const host = try std.zig.system.resolveTargetQuery(.{});
325325 const stdout = io.getStdOut().writer();
326 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
326 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, host);
327327 } else if (mem.eql(u8, cmd, "version")) {
328328 try std.io.getStdOut().writeAll(build_options.version ++ "\n");
329 // Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version"
330 // to avoid affecting the startup time for build-critical commands (check takes about ~10 μs)
329 // Check libc++ linkage to make sure Zig was built correctly, but only
330 // for "env" and "version" to avoid affecting the startup time for
331 // build-critical commands (check takes about ~10 μs)
331332 return verifyLibcxxCorrectlyLinked();
332333 } else if (mem.eql(u8, cmd, "env")) {
333334 verifyLibcxxCorrectlyLinked();
......@@ -2608,9 +2609,9 @@ fn buildOutputType(
26082609 }
26092610
26102611 const target_query = try parseTargetQueryOrReportFatalError(arena, target_parse_options);
2611 const target_info = try detectNativeTargetInfo(target_query);
2612 const target = try std.zig.system.resolveTargetQuery(target_query);
26122613
2613 if (target_info.target.os.tag != .freestanding) {
2614 if (target.os.tag != .freestanding) {
26142615 if (ensure_libc_on_non_freestanding)
26152616 link_libc = true;
26162617 if (ensure_libcpp_on_non_freestanding)
......@@ -2621,7 +2622,7 @@ fn buildOutputType(
26212622 if (!force) {
26222623 entry = null;
26232624 } else if (entry == null and output_mode == .Exe) {
2624 entry = switch (target_info.target.ofmt) {
2625 entry = switch (target.ofmt) {
26252626 .coff => "wWinMainCRTStartup",
26262627 .macho => "_main",
26272628 .elf, .plan9 => "_start",
......@@ -2629,12 +2630,12 @@ fn buildOutputType(
26292630 else => |tag| fatal("No default entry point available for output format {s}", .{@tagName(tag)}),
26302631 };
26312632 }
2632 } else if (entry == null and target_info.target.isWasm() and output_mode == .Exe) {
2633 } else if (entry == null and target.isWasm() and output_mode == .Exe) {
26332634 // For WebAssembly the compiler defaults to setting the entry name when no flags are set.
26342635 entry = defaultWasmEntryName(wasi_exec_model);
26352636 }
26362637
2637 if (target_info.target.ofmt == .coff) {
2638 if (target.ofmt == .coff) {
26382639 // Now that we know the target supports resources,
26392640 // we can add the res files as link objects.
26402641 for (res_files.items) |res_file| {
......@@ -2652,7 +2653,7 @@ fn buildOutputType(
26522653 }
26532654 }
26542655
2655 if (target_info.target.cpu.arch.isWasm()) blk: {
2656 if (target.cpu.arch.isWasm()) blk: {
26562657 if (single_threaded == null) {
26572658 single_threaded = true;
26582659 }
......@@ -2678,8 +2679,8 @@ fn buildOutputType(
26782679 fatal("shared memory is not allowed in object files", .{});
26792680 }
26802681
2681 if (!target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or
2682 !target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory)))
2682 if (!target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or
2683 !target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory)))
26832684 {
26842685 fatal("'atomics' and 'bulk-memory' features must be enabled to use shared memory", .{});
26852686 }
......@@ -2777,15 +2778,15 @@ fn buildOutputType(
27772778 }
27782779
27792780 for (system_libs.keys(), system_libs.values()) |lib_name, info| {
2780 if (target_info.target.is_libc_lib_name(lib_name)) {
2781 if (target.is_libc_lib_name(lib_name)) {
27812782 link_libc = true;
27822783 continue;
27832784 }
2784 if (target_info.target.is_libcpp_lib_name(lib_name)) {
2785 if (target.is_libcpp_lib_name(lib_name)) {
27852786 link_libcpp = true;
27862787 continue;
27872788 }
2788 switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) {
2789 switch (target_util.classifyCompilerRtLibName(target, lib_name)) {
27892790 .none => {},
27902791 .only_libunwind, .both => {
27912792 link_libunwind = true;
......@@ -2797,8 +2798,8 @@ fn buildOutputType(
27972798 },
27982799 }
27992800
2800 if (target_info.target.isMinGW()) {
2801 const exists = mingw.libExists(arena, target_info.target, zig_lib_directory, lib_name) catch |err| {
2801 if (target.isMinGW()) {
2802 const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| {
28022803 fatal("failed to check zig installation for DLL import libs: {s}", .{
28032804 @errorName(err),
28042805 });
......@@ -2820,7 +2821,7 @@ fn buildOutputType(
28202821 fatal("cannot use absolute path as a system library: {s}", .{lib_name});
28212822 }
28222823
2823 if (target_info.target.os.tag == .wasi) {
2824 if (target.os.tag == .wasi) {
28242825 if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| {
28252826 try wasi_emulated_libs.append(crt_file);
28262827 continue;
......@@ -2838,7 +2839,7 @@ fn buildOutputType(
28382839 if (sysroot == null and target_query.isNativeOs() and target_query.isNativeAbi() and
28392840 (external_system_libs.len != 0 or want_native_include_dirs))
28402841 {
2841 const paths = std.zig.system.NativePaths.detect(arena, target_info) catch |err| {
2842 const paths = std.zig.system.NativePaths.detect(arena, target) catch |err| {
28422843 fatal("unable to detect native system paths: {s}", .{@errorName(err)});
28432844 };
28442845 for (paths.warnings.items) |warning| {
......@@ -2857,7 +2858,7 @@ fn buildOutputType(
28572858 }
28582859
28592860 if (builtin.target.os.tag == .windows and
2860 target_info.target.abi == .msvc and
2861 target.abi == .msvc and
28612862 external_system_libs.len != 0)
28622863 {
28632864 if (libc_installation == null) {
......@@ -2902,7 +2903,7 @@ fn buildOutputType(
29022903 &checked_paths,
29032904 lib_dir_path,
29042905 lib_name,
2905 target_info.target,
2906 target,
29062907 info.preferred_mode,
29072908 )) {
29082909 const path = try arena.dupe(u8, test_path.items);
......@@ -2936,7 +2937,7 @@ fn buildOutputType(
29362937 &checked_paths,
29372938 lib_dir_path,
29382939 lib_name,
2939 target_info.target,
2940 target,
29402941 info.fallbackMode(),
29412942 )) {
29422943 const path = try arena.dupe(u8, test_path.items);
......@@ -2970,7 +2971,7 @@ fn buildOutputType(
29702971 &checked_paths,
29712972 lib_dir_path,
29722973 lib_name,
2973 target_info.target,
2974 target,
29742975 info.preferred_mode,
29752976 )) {
29762977 const path = try arena.dupe(u8, test_path.items);
......@@ -2994,7 +2995,7 @@ fn buildOutputType(
29942995 &checked_paths,
29952996 lib_dir_path,
29962997 lib_name,
2997 target_info.target,
2998 target,
29982999 info.fallbackMode(),
29993000 )) {
30003001 const path = try arena.dupe(u8, test_path.items);
......@@ -3089,15 +3090,13 @@ fn buildOutputType(
30893090 }
30903091 // After this point, resolved_frameworks is used instead of frameworks.
30913092
3092 const object_format = target_info.target.ofmt;
3093
3094 if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) {
3093 if (output_mode == .Obj and (target.ofmt == .coff or target.ofmt == .macho)) {
30953094 const total_obj_count = c_source_files.items.len +
30963095 @intFromBool(root_src_file != null) +
30973096 rc_source_files.items.len +
30983097 link_objects.items.len;
30993098 if (total_obj_count > 1) {
3100 fatal("{s} does not support linking multiple objects into one", .{@tagName(object_format)});
3099 fatal("{s} does not support linking multiple objects into one", .{@tagName(target.ofmt)});
31013100 }
31023101 }
31033102
......@@ -3110,7 +3109,7 @@ fn buildOutputType(
31103109 const resolved_soname: ?[]const u8 = switch (soname) {
31113110 .yes => |explicit| explicit,
31123111 .no => null,
3113 .yes_default_value => switch (object_format) {
3112 .yes_default_value => switch (target.ofmt) {
31143113 .elf => if (have_version)
31153114 try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ root_name, version.major })
31163115 else
......@@ -3119,7 +3118,7 @@ fn buildOutputType(
31193118 },
31203119 };
31213120
3122 const a_out_basename = switch (object_format) {
3121 const a_out_basename = switch (target.ofmt) {
31233122 .coff => "a.exe",
31243123 else => "a.out",
31253124 };
......@@ -3141,7 +3140,7 @@ fn buildOutputType(
31413140 },
31423141 .basename = try std.zig.binNameAlloc(arena, .{
31433142 .root_name = root_name,
3144 .target = target_info.target,
3143 .target = target,
31453144 .output_mode = output_mode,
31463145 .link_mode = link_mode,
31473146 .version = optional_version,
......@@ -3269,7 +3268,7 @@ fn buildOutputType(
32693268 // Note that cmake when targeting Windows will try to execute
32703269 // zig cc to make an executable and output an implib too.
32713270 const implib_eligible = is_exe_or_dyn_lib and
3272 emit_bin_loc != null and target_info.target.os.tag == .windows;
3271 emit_bin_loc != null and target.os.tag == .windows;
32733272 if (!implib_eligible) {
32743273 if (!emit_implib_arg_provided) {
32753274 emit_implib = .no;
......@@ -3419,7 +3418,7 @@ fn buildOutputType(
34193418 // "-" is stdin. Dump it to a real file.
34203419 const sep = fs.path.sep_str;
34213420 const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{
3422 std.crypto.random.int(u64), ext.canonicalName(target_info.target),
3421 std.crypto.random.int(u64), ext.canonicalName(target),
34233422 });
34243423 try local_cache_directory.handle.makePath("tmp");
34253424 // Note that in one of the happy paths, execve() is used to switch
......@@ -3454,10 +3453,10 @@ fn buildOutputType(
34543453 .local_cache_directory = local_cache_directory,
34553454 .global_cache_directory = global_cache_directory,
34563455 .root_name = root_name,
3457 .target = target_info.target,
3456 .target = target,
34583457 .is_native_os = target_query.isNativeOs(),
34593458 .is_native_abi = target_query.isNativeAbi(),
3460 .dynamic_linker = target_info.dynamic_linker.get(),
3459 .dynamic_linker = target.dynamic_linker.get(),
34613460 .sysroot = sysroot,
34623461 .output_mode = output_mode,
34633462 .main_mod = main_mod,
......@@ -3603,7 +3602,6 @@ fn buildOutputType(
36033602 .want_structured_cfg = want_structured_cfg,
36043603 }) catch |err| switch (err) {
36053604 error.LibCUnavailable => {
3606 const target = target_info.target;
36073605 const triple_name = try target.zigTriple(arena);
36083606 std.log.err("unable to find or provide libc for target '{s}'", .{triple_name});
36093607
......@@ -3692,7 +3690,7 @@ fn buildOutputType(
36923690 try comp.makeBinFileExecutable();
36933691 saveState(comp, debug_incremental);
36943692
3695 if (test_exec_args.items.len == 0 and object_format == .c) default_exec_args: {
3693 if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: {
36963694 // Default to using `zig run` to execute the produced .c code from `zig test`.
36973695 const c_code_loc = emit_bin_loc orelse break :default_exec_args;
36983696 const c_code_directory = c_code_loc.directory orelse comp.bin_file.options.emit.?.directory;
......@@ -3707,7 +3705,7 @@ fn buildOutputType(
37073705
37083706 if (link_libc) {
37093707 try test_exec_args.append("-lc");
3710 } else if (target_info.target.os.tag == .windows) {
3708 } else if (target.os.tag == .windows) {
37113709 try test_exec_args.appendSlice(&.{
37123710 "--subsystem", "console",
37133711 "-lkernel32", "-lntdll",
......@@ -3741,7 +3739,7 @@ fn buildOutputType(
37413739 test_exec_args.items,
37423740 self_exe_path.?,
37433741 arg_mode,
3744 &target_info,
3742 &target,
37453743 &comp_destroyed,
37463744 all_args,
37473745 runtime_args_start,
......@@ -3861,7 +3859,7 @@ fn serve(
38613859 // test_exec_args,
38623860 // self_exe_path.?,
38633861 // arg_mode,
3864 // target_info,
3862 // target,
38653863 // true,
38663864 // &comp_destroyed,
38673865 // all_args,
......@@ -4071,7 +4069,7 @@ fn runOrTest(
40714069 test_exec_args: []const ?[]const u8,
40724070 self_exe_path: []const u8,
40734071 arg_mode: ArgMode,
4074 target_info: *const std.zig.system.NativeTargetInfo,
4072 target: *const std.Target,
40754073 comp_destroyed: *bool,
40764074 all_args: []const []const u8,
40774075 runtime_args_start: ?usize,
......@@ -4105,7 +4103,7 @@ fn runOrTest(
41054103 if (process.can_execv and arg_mode == .run) {
41064104 // execv releases the locks; no need to destroy the Compilation here.
41074105 const err = process.execve(gpa, argv.items, &env_map);
4108 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
4106 try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
41094107 const cmd = try std.mem.join(arena, " ", argv.items);
41104108 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
41114109 } else if (process.can_spawn) {
......@@ -4121,7 +4119,7 @@ fn runOrTest(
41214119 comp_destroyed.* = true;
41224120
41234121 const term = child.spawnAndWait() catch |err| {
4124 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
4122 try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
41254123 const cmd = try std.mem.join(arena, " ", argv.items);
41264124 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
41274125 };
......@@ -4820,12 +4818,10 @@ pub fn cmdLibC(gpa: Allocator, args: []const []const u8) !void {
48204818 if (!target_query.isNative()) {
48214819 fatal("unable to detect libc for non-native target", .{});
48224820 }
4823 const target_info = try detectNativeTargetInfo(target_query);
4824
48254821 var libc = LibCInstallation.findNative(.{
48264822 .allocator = gpa,
48274823 .verbose = true,
4828 .target = target_info.target,
4824 .target = try std.zig.system.resolveTargetQuery(target_query),
48294825 }) catch |err| {
48304826 fatal("unable to detect native libc: {s}", .{@errorName(err)});
48314827 };
......@@ -5114,11 +5110,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
51145110 gimmeMoreOfThoseSweetSweetFileDescriptors();
51155111
51165112 const target_query: std.Target.Query = .{};
5117 const target_info = try detectNativeTargetInfo(target_query);
5113 const target = try std.zig.system.resolveTargetQuery(target_query);
51185114
51195115 const exe_basename = try std.zig.binNameAlloc(arena, .{
51205116 .root_name = "build",
5121 .target = target_info.target,
5117 .target = target,
51225118 .output_mode = .Exe,
51235119 });
51245120 const emit_bin: Compilation.EmitLoc = .{
......@@ -5282,10 +5278,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
52825278 .local_cache_directory = local_cache_directory,
52835279 .global_cache_directory = global_cache_directory,
52845280 .root_name = "build",
5285 .target = target_info.target,
5281 .target = target,
52865282 .is_native_os = target_query.isNativeOs(),
52875283 .is_native_abi = target_query.isNativeAbi(),
5288 .dynamic_linker = target_info.dynamic_linker.get(),
5284 .dynamic_linker = target.dynamic_linker.get(),
52895285 .output_mode = .Exe,
52905286 .main_mod = &main_mod,
52915287 .emit_bin = emit_bin,
......@@ -6269,10 +6265,6 @@ test "fds" {
62696265 gimmeMoreOfThoseSweetSweetFileDescriptors();
62706266}
62716267
6272fn detectNativeTargetInfo(target_query: std.Target.Query) !std.zig.system.NativeTargetInfo {
6273 return std.zig.system.NativeTargetInfo.detect(target_query);
6274}
6275
62766268const usage_ast_check =
62776269 \\Usage: zig ast-check [file]
62786270 \\
......@@ -6669,24 +6661,24 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 {
66696661fn warnAboutForeignBinaries(
66706662 arena: Allocator,
66716663 arg_mode: ArgMode,
6672 target_info: *const std.zig.system.NativeTargetInfo,
6664 target: *const std.Target,
66736665 link_libc: bool,
66746666) !void {
66756667 const host_query: std.Target.Query = .{};
6676 const host_target_info = try detectNativeTargetInfo(host_query);
6668 const host_target = try std.zig.system.resolveTargetQuery(host_query);
66776669
6678 switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
6670 switch (std.zig.system.getExternalExecutor(host_target, target, .{ .link_libc = link_libc })) {
66796671 .native => return,
66806672 .rosetta => {
6681 const host_name = try host_target_info.target.zigTriple(arena);
6682 const foreign_name = try target_info.target.zigTriple(arena);
6673 const host_name = try host_target.zigTriple(arena);
6674 const foreign_name = try target.zigTriple(arena);
66836675 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
66846676 host_name, foreign_name,
66856677 });
66866678 },
66876679 .qemu => |qemu| {
6688 const host_name = try host_target_info.target.zigTriple(arena);
6689 const foreign_name = try target_info.target.zigTriple(arena);
6680 const host_name = try host_target.zigTriple(arena);
6681 const foreign_name = try target.zigTriple(arena);
66906682 switch (arg_mode) {
66916683 .zig_test => warn(
66926684 "the host system ({s}) does not appear to be capable of executing binaries " ++
......@@ -6702,8 +6694,8 @@ fn warnAboutForeignBinaries(
67026694 }
67036695 },
67046696 .wine => |wine| {
6705 const host_name = try host_target_info.target.zigTriple(arena);
6706 const foreign_name = try target_info.target.zigTriple(arena);
6697 const host_name = try host_target.zigTriple(arena);
6698 const foreign_name = try target.zigTriple(arena);
67076699 switch (arg_mode) {
67086700 .zig_test => warn(
67096701 "the host system ({s}) does not appear to be capable of executing binaries " ++
......@@ -6719,8 +6711,8 @@ fn warnAboutForeignBinaries(
67196711 }
67206712 },
67216713 .wasmtime => |wasmtime| {
6722 const host_name = try host_target_info.target.zigTriple(arena);
6723 const foreign_name = try target_info.target.zigTriple(arena);
6714 const host_name = try host_target.zigTriple(arena);
6715 const foreign_name = try target.zigTriple(arena);
67246716 switch (arg_mode) {
67256717 .zig_test => warn(
67266718 "the host system ({s}) does not appear to be capable of executing binaries " ++
......@@ -6736,8 +6728,8 @@ fn warnAboutForeignBinaries(
67366728 }
67376729 },
67386730 .darling => |darling| {
6739 const host_name = try host_target_info.target.zigTriple(arena);
6740 const foreign_name = try target_info.target.zigTriple(arena);
6731 const host_name = try host_target.zigTriple(arena);
6732 const foreign_name = try target.zigTriple(arena);
67416733 switch (arg_mode) {
67426734 .zig_test => warn(
67436735 "the host system ({s}) does not appear to be capable of executing binaries " ++
......@@ -6753,7 +6745,7 @@ fn warnAboutForeignBinaries(
67536745 }
67546746 },
67556747 .bad_dl => |foreign_dl| {
6756 const host_dl = host_target_info.dynamic_linker.get() orelse "(none)";
6748 const host_dl = host_target.dynamic_linker.get() orelse "(none)";
67576749 const tip_suffix = switch (arg_mode) {
67586750 .zig_test => ", '--test-no-exec', or '--test-cmd'",
67596751 else => "",
......@@ -6763,8 +6755,8 @@ fn warnAboutForeignBinaries(
67636755 });
67646756 },
67656757 .bad_os_or_cpu => {
6766 const host_name = try host_target_info.target.zigTriple(arena);
6767 const foreign_name = try target_info.target.zigTriple(arena);
6758 const host_name = try host_target.zigTriple(arena);
6759 const foreign_name = try target.zigTriple(arena);
67686760 const tip_suffix = switch (arg_mode) {
67696761 .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
67706762 else => "",
src/print_env.zig+2-2
......@@ -17,8 +17,8 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8, stdout: std.fs.File.Wr
1717
1818 const global_cache_dir = try introspect.resolveGlobalCacheDir(arena);
1919
20 const info = try std.zig.system.NativeTargetInfo.detect(.{});
21 const triple = try info.target.zigTriple(arena);
20 const host = try std.zig.system.resolveTargetQuery(.{});
21 const triple = try host.zigTriple(arena);
2222
2323 var bw = std.io.bufferedWriter(stdout);
2424 const w = bw.writer();
test/src/Cases.zig+13-17
......@@ -541,7 +541,7 @@ pub fn lowerToBuildSteps(
541541 cases_dir_path: []const u8,
542542 incremental_exe: *std.Build.Step.Compile,
543543) void {
544 const host = std.zig.system.NativeTargetInfo.detect(.{}) catch |err|
544 const host = std.zig.system.resolveTargetQuery(.{}) catch |err|
545545 std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
546546
547547 for (self.incremental_cases.items) |incr_case| {
......@@ -648,8 +648,7 @@ pub fn lowerToBuildSteps(
648648 },
649649 .Execution => |expected_stdout| no_exec: {
650650 const run = if (case.target.target.ofmt == .c) run_step: {
651 const target_info = case.target.toNativeTargetInfo();
652 if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) {
651 if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) {
653652 // We wouldn't be able to run the compiled C code.
654653 break :no_exec;
655654 }
......@@ -694,8 +693,7 @@ pub fn lowerToBuildSteps(
694693 continue; // Pass test.
695694 }
696695
697 const target_info = case.target.toNativeTargetInfo();
698 if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) {
696 if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) {
699697 // We wouldn't be able to run the compiled C code.
700698 continue; // Pass test.
701699 }
......@@ -1199,6 +1197,8 @@ const builtin = @import("builtin");
11991197const std = @import("std");
12001198const assert = std.debug.assert;
12011199const Allocator = std.mem.Allocator;
1200const getExternalExecutor = std.zig.system.getExternalExecutor;
1201
12021202const Compilation = @import("../../src/Compilation.zig");
12031203const zig_h = @import("../../src/link.zig").File.C.zig_h;
12041204const introspect = @import("../../src/introspect.zig");
......@@ -1386,18 +1386,15 @@ pub fn main() !void {
13861386}
13871387
13881388fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget {
1389 const result = std.zig.system.NativeTargetInfo.detect(query) catch
1390 @panic("unable to resolve target query");
1391
13921389 return .{
13931390 .query = query,
1394 .target = result.target,
1395 .dynamic_linker = result.dynamic_linker,
1391 .target = std.zig.system.resolveTargetQuery(query) catch
1392 @panic("unable to resolve target query"),
13961393 };
13971394}
13981395
13991396fn runCases(self: *Cases, zig_exe_path: []const u8) !void {
1400 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1397 const host = try std.zig.system.resolveTargetQuery(.{});
14011398
14021399 var progress = std.Progress{};
14031400 const root_node = progress.start("compiler", self.cases.items.len);
......@@ -1478,7 +1475,7 @@ fn runOneCase(
14781475 zig_exe_path: []const u8,
14791476 thread_pool: *ThreadPool,
14801477 global_cache_directory: Compilation.Directory,
1481 host: std.zig.system.NativeTargetInfo,
1478 host: std.Target,
14821479) !void {
14831480 const tmp_src_path = "tmp.zig";
14841481 const enable_rosetta = build_options.enable_rosetta;
......@@ -1488,8 +1485,7 @@ fn runOneCase(
14881485 const enable_darling = build_options.enable_darling;
14891486 const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir;
14901487
1491 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
1492 const target = target_info.target;
1488 const target = try std.zig.system.resolveTargetQuery(case.target);
14931489
14941490 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
14951491 defer arena_allocator.deinit();
......@@ -1579,7 +1575,7 @@ fn runOneCase(
15791575 .keep_source_files_loaded = true,
15801576 .is_native_os = case.target.isNativeOs(),
15811577 .is_native_abi = case.target.isNativeAbi(),
1582 .dynamic_linker = target_info.dynamic_linker.get(),
1578 .dynamic_linker = target.dynamic_linker.get(),
15831579 .link_libc = case.link_libc,
15841580 .use_llvm = use_llvm,
15851581 .self_exe_path = zig_exe_path,
......@@ -1715,7 +1711,7 @@ fn runOneCase(
17151711 .{ &tmp.sub_path, bin_name },
17161712 );
17171713 if (case.target.ofmt != null and case.target.ofmt.? == .c) {
1718 if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
1714 if (getExternalExecutor(host, &target, .{ .link_libc = true }) != .native) {
17191715 // We wouldn't be able to run the compiled C code.
17201716 continue :update; // Pass test.
17211717 }
......@@ -1734,7 +1730,7 @@ fn runOneCase(
17341730 if (zig_lib_directory.path) |p| {
17351731 try argv.appendSlice(&.{ "-I", p });
17361732 }
1737 } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) {
1733 } else switch (getExternalExecutor(host, &target, .{ .link_libc = case.link_libc })) {
17381734 .native => {
17391735 if (case.backend == .stage2 and case.target.getCpuArch().isArmOrThumb()) {
17401736 // https://github.com/ziglang/zig/issues/13623