authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-02 21:51:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-02 21:51:14-07:00
logf3edff439e2dd8e4055d21507b352141cd5b1718
tree160b6397c84011c008c0a5671bbdbbcf41c4b5fd
parent0cd87102221233c2885faccfcaaac297b8d3b656

improve detection of how to execute binaries on the host

`getExternalExecutor` is moved from `std.zig.CrossTarget` to `std.zig.system.NativeTargetInfo.getExternalExecutor`. The function also now communicates a bit more information about *why* the host is unable to execute a binary. The CLI is updated to report this information in a useful manner. `getExternalExecutor` is also improved to detect such patterns as: * x86_64 is able to execute x86 binaries * aarch64 is able to execute arm binaries * etc. Added qemu-hexagon support to `getExternalExecutor`. `std.Target.canExecBinaries` of is removed; callers should use the more powerful `getExternalExecutor` instead. Now that `zig test` tries to run the resulting binary no matter what, this commit has a follow-up change to the build system and docgen to utilize the `getExternalExecutor` function and pass `--test-no-exec` in some cases to avoid getting the error. Additionally: * refactor: extract NativePaths and NativeTargetInfo into their own files named after the structs. * small improvement to langref to reduce the complexity of the `callconv` expression in a couple examples.

11 files changed, 1388 insertions(+), 1216 deletions(-)

CMakeLists.txt+2
......@@ -540,6 +540,8 @@ set(ZIG_STAGE2_SOURCES
540540 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
541541 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
542542 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
543 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
544 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativeTargetInfo.zig"
543545 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig"
544546 "${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig"
545547 "${CMAKE_SOURCE_DIR}/src/Air.zig"
doc/docgen.zig+27-6
......@@ -1202,6 +1202,7 @@ fn genHtml(
12021202 var env_map = try process.getEnvMap(allocator);
12031203 try env_map.put("ZIG_DEBUG_COLOR", "1");
12041204
1205 const host = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
12051206 const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);
12061207
12071208 for (toc.nodes) |node| {
......@@ -1424,13 +1425,17 @@ fn genHtml(
14241425 var test_args = std.ArrayList([]const u8).init(allocator);
14251426 defer test_args.deinit();
14261427
1427 try test_args.appendSlice(&[_][]const u8{ zig_exe, "test", tmp_source_file_name });
1428 try test_args.appendSlice(&[_][]const u8{
1429 zig_exe, "test", tmp_source_file_name,
1430 });
14281431 try shell_out.print("$ zig test {s}.zig ", .{code.name});
14291432
14301433 switch (code.mode) {
14311434 .Debug => {},
14321435 else => {
1433 try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1436 try test_args.appendSlice(&[_][]const u8{
1437 "-O", @tagName(code.mode),
1438 });
14341439 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
14351440 },
14361441 }
......@@ -1441,8 +1446,26 @@ fn genHtml(
14411446 if (code.target_str) |triple| {
14421447 try test_args.appendSlice(&[_][]const u8{ "-target", triple });
14431448 try shell_out.print("-target {s} ", .{triple});
1449
1450 const cross_target = try std.zig.CrossTarget.parse(.{
1451 .arch_os_abi = triple,
1452 });
1453 const target_info = try std.zig.system.NativeTargetInfo.detect(
1454 allocator,
1455 cross_target,
1456 );
1457 switch (host.getExternalExecutor(target_info, .{
1458 .link_libc = code.link_libc,
1459 })) {
1460 .native => {},
1461 else => {
1462 try test_args.appendSlice(&[_][]const u8{"--test-no-exec"});
1463 try shell_out.writeAll("--test-no-exec");
1464 },
1465 }
14441466 }
1445 const result = exec(allocator, &env_map, test_args.items) catch return parseError(tokenizer, code.source_token, "test failed", .{});
1467 const result = exec(allocator, &env_map, test_args.items) catch
1468 return parseError(tokenizer, code.source_token, "test failed", .{});
14461469 const escaped_stderr = try escapeHtml(allocator, result.stderr);
14471470 const escaped_stdout = try escapeHtml(allocator, result.stdout);
14481471 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
......@@ -1504,9 +1527,7 @@ fn genHtml(
15041527 defer test_args.deinit();
15051528
15061529 try test_args.appendSlice(&[_][]const u8{
1507 zig_exe,
1508 "test",
1509 tmp_source_file_name,
1530 zig_exe, "test", tmp_source_file_name,
15101531 });
15111532 var mode_arg: []const u8 = "";
15121533 switch (code.mode) {
doc/langref.html.in+14-4
......@@ -4763,7 +4763,13 @@ test "noreturn" {
47634763 <p>Another use case for {#syntax#}noreturn{#endsyntax#} is the {#syntax#}exit{#endsyntax#} function:</p>
47644764 {#code_begin|test|noreturn_from_exit#}
47654765 {#target_windows#}
4766pub extern "kernel32" fn ExitProcess(exit_code: c_uint) callconv(if (@import("builtin").target.cpu.arch == .i386) .Stdcall else .C) noreturn;
4766const std = @import("std");
4767const builtin = @import("builtin");
4768const native_arch = builtin.cpu.arch;
4769const expect = std.testing.expect;
4770
4771const WINAPI: std.builtin.CallingConvention = if (native_arch == .i386) .Stdcall else .C;
4772extern "kernel32" fn ExitProcess(exit_code: c_uint) callconv(WINAPI) noreturn;
47674773
47684774test "foo" {
47694775 const value = bar() catch ExitProcess(1);
......@@ -4774,12 +4780,15 @@ fn bar() anyerror!u32 {
47744780 return 1234;
47754781}
47764782
4777const expect = @import("std").testing.expect;
47784783 {#code_end#}
47794784 {#header_close#}
4785
47804786 {#header_open|Functions#}
47814787 {#code_begin|test|functions#}
4782const expect = @import("std").testing.expect;
4788const std = @import("std");
4789const builtin = @import("builtin");
4790const native_arch = builtin.cpu.arch;
4791const expect = std.testing.expect;
47834792
47844793// Functions are declared like this
47854794fn add(a: i8, b: i8) i8 {
......@@ -4798,7 +4807,8 @@ export fn sub(a: i8, b: i8) i8 { return a - b; }
47984807// at link time, when linking statically, or at runtime, when linking
47994808// dynamically.
48004809// The callconv specifier changes the calling convention of the function.
4801extern "kernel32" fn ExitProcess(exit_code: u32) callconv(if (@import("builtin").target.cpu.arch == .i386) .Stdcall else .C) noreturn;
4810const WINAPI: std.builtin.CallingConvention = if (native_arch == .i386) .Stdcall else .C;
4811extern "kernel32" fn ExitProcess(exit_code: u32) callconv(WINAPI) noreturn;
48024812extern "c" fn atan2(a: f64, b: f64) f64;
48034813
48044814// The @setCold builtin tells the optimizer that a function is rarely called.
lib/std/build.zig+85-69
......@@ -16,6 +16,7 @@ const BufMap = std.BufMap;
1616const fmt_lib = std.fmt;
1717const File = std.fs.File;
1818const CrossTarget = std.zig.CrossTarget;
19const NativeTargetInfo = std.zig.system.NativeTargetInfo;
1920
2021pub const FmtStep = @import("build/FmtStep.zig");
2122pub const TranslateCStep = @import("build/TranslateCStep.zig");
......@@ -86,6 +87,9 @@ pub const Builder = struct {
8687 /// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`.
8788 glibc_runtimes_dir: ?[]const u8 = null,
8889
90 /// Information about the native target. Computed before build() is invoked.
91 host: NativeTargetInfo,
92
8993 const PkgConfigError = error{
9094 PkgConfigCrashed,
9195 PkgConfigFailed,
......@@ -159,6 +163,8 @@ pub const Builder = struct {
159163 const env_map = try allocator.create(BufMap);
160164 env_map.* = try process.getEnvMap(allocator);
161165
166 const host = try NativeTargetInfo.detect(allocator, .{});
167
162168 const self = try allocator.create(Builder);
163169 self.* = Builder{
164170 .zig_exe = zig_exe,
......@@ -204,6 +210,7 @@ pub const Builder = struct {
204210 .install_path = undefined,
205211 .vcpkg_root = VcpkgRoot{ .unattempted = {} },
206212 .args = null,
213 .host = host,
207214 };
208215 try self.top_level_steps.append(&self.install_tls);
209216 try self.top_level_steps.append(&self.uninstall_tls);
......@@ -1436,6 +1443,7 @@ pub const LibExeObjStep = struct {
14361443 builder: *Builder,
14371444 name: []const u8,
14381445 target: CrossTarget = CrossTarget{},
1446 target_info: NativeTargetInfo,
14391447 linker_script: ?FileSource = null,
14401448 version_script: ?[]const u8 = null,
14411449 out_filename: []const u8,
......@@ -1655,6 +1663,8 @@ pub const LibExeObjStep = struct {
16551663 .output_lib_path_source = GeneratedFile{ .step = &self.step },
16561664 .output_h_path_source = GeneratedFile{ .step = &self.step },
16571665 .output_pdb_path_source = GeneratedFile{ .step = &self.step },
1666
1667 .target_info = undefined, // populated in computeOutFileNames
16581668 };
16591669 self.computeOutFileNames();
16601670 if (root_src) |rs| rs.addStepDependencies(&self.step);
......@@ -1662,11 +1672,11 @@ pub const LibExeObjStep = struct {
16621672 }
16631673
16641674 fn computeOutFileNames(self: *LibExeObjStep) void {
1665 const target_info = std.zig.system.NativeTargetInfo.detect(
1666 self.builder.allocator,
1667 self.target,
1668 ) catch unreachable;
1669 const target = target_info.target;
1675 self.target_info = NativeTargetInfo.detect(self.builder.allocator, self.target) catch
1676 unreachable;
1677
1678 const target = self.target_info.target;
1679
16701680 self.out_filename = std.zig.binNameAlloc(self.builder.allocator, .{
16711681 .root_name = self.name,
16721682 .target = target,
......@@ -2526,74 +2536,80 @@ pub const LibExeObjStep = struct {
25262536 try zig_args.append("--test-cmd-bin");
25272537 }
25282538 }
2529 } else switch (self.target.getExternalExecutor()) {
2530 .native => {},
2531 .unavailable => {
2532 try zig_args.append("--test-no-exec");
2533 },
2534 .rosetta => if (builder.enable_rosetta) {
2535 try zig_args.append("--test-cmd-bin");
2536 } else {
2537 try zig_args.append("--test-no-exec");
2538 },
2539 .qemu => |bin_name| ok: {
2540 if (builder.enable_qemu) qemu: {
2541 const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc;
2542 const glibc_dir_arg = if (need_cross_glibc)
2543 builder.glibc_runtimes_dir orelse break :qemu
2544 else
2545 null;
2546 try zig_args.append("--test-cmd");
2547 try zig_args.append(bin_name);
2548 if (glibc_dir_arg) |dir| {
2549 // TODO look into making this a call to `linuxTriple`. This
2550 // needs the directory to be called "i686" rather than
2551 // "i386" which is why we do it manually here.
2552 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
2553 const cpu_arch = self.target.getCpuArch();
2554 const os_tag = self.target.getOsTag();
2555 const abi = self.target.getAbi();
2556 const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
2557 "i686"
2539 } else {
2540 const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc;
2541
2542 switch (self.builder.host.getExternalExecutor(self.target_info, .{
2543 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
2544 .link_libc = self.is_linking_libc,
2545 })) {
2546 .native => {},
2547 .bad_dl, .bad_os_or_cpu => {
2548 try zig_args.append("--test-no-exec");
2549 },
2550 .rosetta => if (builder.enable_rosetta) {
2551 try zig_args.append("--test-cmd-bin");
2552 } else {
2553 try zig_args.append("--test-no-exec");
2554 },
2555 .qemu => |bin_name| ok: {
2556 if (builder.enable_qemu) qemu: {
2557 const glibc_dir_arg = if (need_cross_glibc)
2558 builder.glibc_runtimes_dir orelse break :qemu
25582559 else
2559 @tagName(cpu_arch);
2560 const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{
2561 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
2562 });
2563
2560 null;
25642561 try zig_args.append("--test-cmd");
2565 try zig_args.append("-L");
2566 try zig_args.append("--test-cmd");
2567 try zig_args.append(full_dir);
2562 try zig_args.append(bin_name);
2563 if (glibc_dir_arg) |dir| {
2564 // TODO look into making this a call to `linuxTriple`. This
2565 // needs the directory to be called "i686" rather than
2566 // "i386" which is why we do it manually here.
2567 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
2568 const cpu_arch = self.target.getCpuArch();
2569 const os_tag = self.target.getOsTag();
2570 const abi = self.target.getAbi();
2571 const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
2572 "i686"
2573 else
2574 @tagName(cpu_arch);
2575 const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{
2576 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
2577 });
2578
2579 try zig_args.append("--test-cmd");
2580 try zig_args.append("-L");
2581 try zig_args.append("--test-cmd");
2582 try zig_args.append(full_dir);
2583 }
2584 try zig_args.append("--test-cmd-bin");
2585 break :ok;
25682586 }
2587 try zig_args.append("--test-no-exec");
2588 },
2589 .wine => |bin_name| if (builder.enable_wine) {
2590 try zig_args.append("--test-cmd");
2591 try zig_args.append(bin_name);
25692592 try zig_args.append("--test-cmd-bin");
2570 break :ok;
2571 }
2572 try zig_args.append("--test-no-exec");
2573 },
2574 .wine => |bin_name| if (builder.enable_wine) {
2575 try zig_args.append("--test-cmd");
2576 try zig_args.append(bin_name);
2577 try zig_args.append("--test-cmd-bin");
2578 } else {
2579 try zig_args.append("--test-no-exec");
2580 },
2581 .wasmtime => |bin_name| if (builder.enable_wasmtime) {
2582 try zig_args.append("--test-cmd");
2583 try zig_args.append(bin_name);
2584 try zig_args.append("--test-cmd");
2585 try zig_args.append("--dir=.");
2586 try zig_args.append("--test-cmd-bin");
2587 } else {
2588 try zig_args.append("--test-no-exec");
2589 },
2590 .darling => |bin_name| if (builder.enable_darling) {
2591 try zig_args.append("--test-cmd");
2592 try zig_args.append(bin_name);
2593 try zig_args.append("--test-cmd-bin");
2594 } else {
2595 try zig_args.append("--test-no-exec");
2596 },
2593 } else {
2594 try zig_args.append("--test-no-exec");
2595 },
2596 .wasmtime => |bin_name| if (builder.enable_wasmtime) {
2597 try zig_args.append("--test-cmd");
2598 try zig_args.append(bin_name);
2599 try zig_args.append("--test-cmd");
2600 try zig_args.append("--dir=.");
2601 try zig_args.append("--test-cmd-bin");
2602 } else {
2603 try zig_args.append("--test-no-exec");
2604 },
2605 .darling => |bin_name| if (builder.enable_darling) {
2606 try zig_args.append("--test-cmd");
2607 try zig_args.append(bin_name);
2608 try zig_args.append("--test-cmd-bin");
2609 } else {
2610 try zig_args.append("--test-no-exec");
2611 },
2612 }
25972613 }
25982614
25992615 for (self.packages.items) |pkg| {
lib/std/target.zig-21
......@@ -1689,27 +1689,6 @@ pub const Target = struct {
16891689 }
16901690 }
16911691
1692 /// Return whether or not the given host target is capable of executing natively executables
1693 /// of the other target.
1694 pub fn canExecBinariesOf(host_target: Target, binary_target: Target) bool {
1695 if (host_target.os.tag != binary_target.os.tag)
1696 return false;
1697
1698 if (host_target.cpu.arch == binary_target.cpu.arch)
1699 return true;
1700
1701 if (host_target.cpu.arch == .x86_64 and binary_target.cpu.arch == .i386)
1702 return true;
1703
1704 if (host_target.cpu.arch == .aarch64 and binary_target.cpu.arch == .arm)
1705 return true;
1706
1707 if (host_target.cpu.arch == .aarch64_be and binary_target.cpu.arch == .armeb)
1708 return true;
1709
1710 return false;
1711 }
1712
17131692 /// 0c spim little-endian MIPS 3000 family
17141693 /// 1c 68000 Motorola MC68000
17151694 /// 2c 68020 Motorola MC68020
lib/std/zig/CrossTarget.zig-89
......@@ -610,95 +610,6 @@ pub fn vcpkgTriplet(self: CrossTarget, allocator: mem.Allocator, linkage: VcpkgL
610610 return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix });
611611}
612612
613pub const Executor = union(enum) {
614 native,
615 rosetta,
616 qemu: []const u8,
617 wine: []const u8,
618 wasmtime: []const u8,
619 darling: []const u8,
620 unavailable,
621};
622
623/// Note that even a `CrossTarget` which returns `false` for `isNative` could still be natively executed.
624/// For example `-target arm-native` running on an aarch64 host.
625pub fn getExternalExecutor(self: CrossTarget) Executor {
626 const cpu_arch = self.getCpuArch();
627 const os_tag = self.getOsTag();
628 const os_match = os_tag == builtin.os.tag;
629
630 // If the OS and CPU arch match, the binary can be considered native.
631 // TODO additionally match the CPU features. This `getExternalExecutor` function should
632 // be moved to std.Target and match any chosen target against the native target.
633 if (os_match and cpu_arch == builtin.cpu.arch) {
634 // However, we also need to verify that the dynamic linker path is valid.
635 if (self.os_tag == null) {
636 return .native;
637 }
638 // TODO here we call toTarget, a deprecated function, because of the above TODO about moving
639 // this code to std.Target.
640 const opt_dl = self.dynamic_linker.get() orelse self.toTarget().standardDynamicLinkerPath().get();
641 if (opt_dl) |dl| blk: {
642 std.fs.cwd().access(dl, .{}) catch break :blk;
643 return .native;
644 }
645 }
646 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
647 // to emulate the foreign architecture.
648 if (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) {
649 return switch (cpu_arch) {
650 .x86_64 => .rosetta,
651 else => .unavailable,
652 };
653 }
654
655 // If the OS matches, we can use QEMU to emulate a foreign architecture.
656 if (os_match) {
657 return switch (cpu_arch) {
658 .aarch64 => Executor{ .qemu = "qemu-aarch64" },
659 .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
660 .arm => Executor{ .qemu = "qemu-arm" },
661 .armeb => Executor{ .qemu = "qemu-armeb" },
662 .i386 => Executor{ .qemu = "qemu-i386" },
663 .mips => Executor{ .qemu = "qemu-mips" },
664 .mipsel => Executor{ .qemu = "qemu-mipsel" },
665 .mips64 => Executor{ .qemu = "qemu-mips64" },
666 .mips64el => Executor{ .qemu = "qemu-mips64el" },
667 .powerpc => Executor{ .qemu = "qemu-ppc" },
668 .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
669 .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
670 .riscv32 => Executor{ .qemu = "qemu-riscv32" },
671 .riscv64 => Executor{ .qemu = "qemu-riscv64" },
672 .s390x => Executor{ .qemu = "qemu-s390x" },
673 .sparc => Executor{ .qemu = "qemu-sparc" },
674 .x86_64 => Executor{ .qemu = "qemu-x86_64" },
675 else => return .unavailable,
676 };
677 }
678
679 switch (os_tag) {
680 .windows => switch (cpu_arch.ptrBitWidth()) {
681 32 => return Executor{ .wine = "wine" },
682 64 => return Executor{ .wine = "wine64" },
683 else => return .unavailable,
684 },
685 .wasi => switch (cpu_arch.ptrBitWidth()) {
686 32 => return Executor{ .wasmtime = "wasmtime" },
687 else => return .unavailable,
688 },
689 .macos => {
690 // TODO loosen this check once upstream adds QEMU-based emulation
691 // layer for non-host architectures:
692 // https://github.com/darlinghq/darling/issues/863
693 if (cpu_arch != builtin.cpu.arch) {
694 return .unavailable;
695 }
696 return Executor{ .darling = "darling" };
697 },
698 else => return .unavailable,
699 }
700}
701
702613pub fn isGnuLibC(self: CrossTarget) bool {
703614 return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi());
704615}
lib/std/zig/system.zig+10-1002
......@@ -1,1007 +1,15 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const elf = std.elf;
4const mem = std.mem;
5const fs = std.fs;
6const Allocator = std.mem.Allocator;
7const ArrayList = std.ArrayList;
8const assert = std.debug.assert;
9const process = std.process;
10const Target = std.Target;
11const CrossTarget = std.zig.CrossTarget;
12const native_endian = builtin.cpu.arch.endian();
13const linux = @import("system/linux.zig");
1pub const NativePaths = @import("system/NativePaths.zig");
2pub const NativeTargetInfo = @import("system/NativeTargetInfo.zig");
3
144pub const windows = @import("system/windows.zig");
155pub const darwin = @import("system/darwin.zig");
16
17pub const NativePaths = struct {
18 include_dirs: ArrayList([:0]u8),
19 lib_dirs: ArrayList([:0]u8),
20 framework_dirs: ArrayList([:0]u8),
21 rpaths: ArrayList([:0]u8),
22 warnings: ArrayList([:0]u8),
23
24 pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths {
25 const native_target = native_info.target;
26
27 var self: NativePaths = .{
28 .include_dirs = ArrayList([:0]u8).init(allocator),
29 .lib_dirs = ArrayList([:0]u8).init(allocator),
30 .framework_dirs = ArrayList([:0]u8).init(allocator),
31 .rpaths = ArrayList([:0]u8).init(allocator),
32 .warnings = ArrayList([:0]u8).init(allocator),
33 };
34 errdefer self.deinit();
35
36 var is_nix = false;
37 if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
38 defer allocator.free(nix_cflags_compile);
39
40 is_nix = true;
41 var it = mem.tokenize(u8, nix_cflags_compile, " ");
42 while (true) {
43 const word = it.next() orelse break;
44 if (mem.eql(u8, word, "-isystem")) {
45 const include_path = it.next() orelse {
46 try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE");
47 break;
48 };
49 try self.addIncludeDir(include_path);
50 } else {
51 if (mem.startsWith(u8, word, "-frandom-seed=")) {
52 continue;
53 }
54 try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});
55 }
56 }
57 } else |err| switch (err) {
58 error.InvalidUtf8 => {},
59 error.EnvironmentVariableNotFound => {},
60 error.OutOfMemory => |e| return e,
61 }
62 if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
63 defer allocator.free(nix_ldflags);
64
65 is_nix = true;
66 var it = mem.tokenize(u8, nix_ldflags, " ");
67 while (true) {
68 const word = it.next() orelse break;
69 if (mem.eql(u8, word, "-rpath")) {
70 const rpath = it.next() orelse {
71 try self.addWarning("Expected argument after -rpath in NIX_LDFLAGS");
72 break;
73 };
74 try self.addRPath(rpath);
75 } else if (word.len > 2 and word[0] == '-' and word[1] == 'L') {
76 const lib_path = word[2..];
77 try self.addLibDir(lib_path);
78 } else {
79 try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word});
80 break;
81 }
82 }
83 } else |err| switch (err) {
84 error.InvalidUtf8 => {},
85 error.EnvironmentVariableNotFound => {},
86 error.OutOfMemory => |e| return e,
87 }
88 if (is_nix) {
89 return self;
90 }
91
92 if (comptime builtin.target.isDarwin()) {
93 try self.addIncludeDir("/usr/include");
94 try self.addIncludeDir("/usr/local/include");
95
96 try self.addLibDir("/usr/lib");
97 try self.addLibDir("/usr/local/lib");
98
99 try self.addFrameworkDir("/Library/Frameworks");
100 try self.addFrameworkDir("/System/Library/Frameworks");
101
102 return self;
103 }
104
105 if (comptime native_target.os.tag == .solaris) {
106 try self.addLibDir("/usr/lib/64");
107 try self.addLibDir("/usr/local/lib/64");
108 try self.addLibDir("/lib/64");
109
110 try self.addIncludeDir("/usr/include");
111 try self.addIncludeDir("/usr/local/include");
112
113 return self;
114 }
115
116 if (native_target.os.tag != .windows) {
117 const triple = try native_target.linuxTriple(allocator);
118 const qual = native_target.cpu.arch.ptrBitWidth();
119
120 // TODO: $ ld --verbose | grep SEARCH_DIR
121 // the output contains some paths that end with lib64, maybe include them too?
122 // TODO: what is the best possible order of things?
123 // TODO: some of these are suspect and should only be added on some systems. audit needed.
124
125 try self.addIncludeDir("/usr/local/include");
126 try self.addLibDirFmt("/usr/local/lib{d}", .{qual});
127 try self.addLibDir("/usr/local/lib");
128
129 try self.addIncludeDirFmt("/usr/include/{s}", .{triple});
130 try self.addLibDirFmt("/usr/lib/{s}", .{triple});
131
132 try self.addIncludeDir("/usr/include");
133 try self.addLibDirFmt("/lib{d}", .{qual});
134 try self.addLibDir("/lib");
135 try self.addLibDirFmt("/usr/lib{d}", .{qual});
136 try self.addLibDir("/usr/lib");
137
138 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
139 // zlib.h is in /usr/include (added above)
140 // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
141 try self.addLibDirFmt("/lib/{s}", .{triple});
142 }
143
144 return self;
145 }
146
147 pub fn deinit(self: *NativePaths) void {
148 deinitArray(&self.include_dirs);
149 deinitArray(&self.lib_dirs);
150 deinitArray(&self.framework_dirs);
151 deinitArray(&self.rpaths);
152 deinitArray(&self.warnings);
153 self.* = undefined;
154 }
155
156 fn deinitArray(array: *ArrayList([:0]u8)) void {
157 for (array.items) |item| {
158 array.allocator.free(item);
159 }
160 array.deinit();
161 }
162
163 pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void {
164 return self.appendArray(&self.include_dirs, s);
165 }
166
167 pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
168 const item = try std.fmt.allocPrintZ(self.include_dirs.allocator, fmt, args);
169 errdefer self.include_dirs.allocator.free(item);
170 try self.include_dirs.append(item);
171 }
172
173 pub fn addLibDir(self: *NativePaths, s: []const u8) !void {
174 return self.appendArray(&self.lib_dirs, s);
175 }
176
177 pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
178 const item = try std.fmt.allocPrintZ(self.lib_dirs.allocator, fmt, args);
179 errdefer self.lib_dirs.allocator.free(item);
180 try self.lib_dirs.append(item);
181 }
182
183 pub fn addWarning(self: *NativePaths, s: []const u8) !void {
184 return self.appendArray(&self.warnings, s);
185 }
186
187 pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void {
188 return self.appendArray(&self.framework_dirs, s);
189 }
190
191 pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
192 const item = try std.fmt.allocPrintZ(self.framework_dirs.allocator, fmt, args);
193 errdefer self.framework_dirs.allocator.free(item);
194 try self.framework_dirs.append(item);
195 }
196
197 pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
198 const item = try std.fmt.allocPrintZ(self.warnings.allocator, fmt, args);
199 errdefer self.warnings.allocator.free(item);
200 try self.warnings.append(item);
201 }
202
203 pub fn addRPath(self: *NativePaths, s: []const u8) !void {
204 return self.appendArray(&self.rpaths, s);
205 }
206
207 fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void {
208 _ = self;
209 const item = try array.allocator.dupeZ(u8, s);
210 errdefer array.allocator.free(item);
211 try array.append(item);
212 }
213};
214
215pub const NativeTargetInfo = struct {
216 target: Target,
217
218 dynamic_linker: DynamicLinker = DynamicLinker{},
219
220 pub const DynamicLinker = Target.DynamicLinker;
221
222 pub const DetectError = error{
223 OutOfMemory,
224 FileSystem,
225 SystemResources,
226 SymLinkLoop,
227 ProcessFdQuotaExceeded,
228 SystemFdQuotaExceeded,
229 DeviceBusy,
230 OSVersionDetectionFail,
231 };
232
233 /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
234 /// natively, which should be standard or default, and which are provided explicitly, this function
235 /// resolves the native components by detecting the native system, and then resolves standard/default parts
236 /// relative to that.
237 /// Any resources this function allocates are released before returning, and so there is no
238 /// deinitialization method.
239 /// TODO Remove the Allocator requirement from this function.
240 pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
241 var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
242 if (cross_target.os_tag == null) {
243 switch (builtin.target.os.tag) {
244 .linux => {
245 const uts = std.os.uname();
246 const release = mem.sliceTo(&uts.release, 0);
247 // The release field sometimes has a weird format,
248 // `Version.parse` will attempt to find some meaningful interpretation.
249 if (std.builtin.Version.parse(release)) |ver| {
250 os.version_range.linux.range.min = ver;
251 os.version_range.linux.range.max = ver;
252 } else |err| switch (err) {
253 error.Overflow => {},
254 error.InvalidCharacter => {},
255 error.InvalidVersion => {},
256 }
257 },
258 .solaris => {
259 const uts = std.os.uname();
260 const release = mem.sliceTo(&uts.release, 0);
261 if (std.builtin.Version.parse(release)) |ver| {
262 os.version_range.semver.min = ver;
263 os.version_range.semver.max = ver;
264 } else |err| switch (err) {
265 error.Overflow => {},
266 error.InvalidCharacter => {},
267 error.InvalidVersion => {},
268 }
269 },
270 .windows => {
271 const detected_version = windows.detectRuntimeVersion();
272 os.version_range.windows.min = detected_version;
273 os.version_range.windows.max = detected_version;
274 },
275 .macos => try darwin.macos.detect(&os),
276 .freebsd, .netbsd, .dragonfly => {
277 const key = switch (builtin.target.os.tag) {
278 .freebsd => "kern.osreldate",
279 .netbsd, .dragonfly => "kern.osrevision",
280 else => unreachable,
281 };
282 var value: u32 = undefined;
283 var len: usize = @sizeOf(@TypeOf(value));
284
285 std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
286 error.NameTooLong => unreachable, // constant, known good value
287 error.PermissionDenied => unreachable, // only when setting values,
288 error.SystemResources => unreachable, // memory already on the stack
289 error.UnknownName => unreachable, // constant, known good value
290 error.Unexpected => return error.OSVersionDetectionFail,
291 };
292
293 switch (builtin.target.os.tag) {
294 .freebsd => {
295 // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
296 // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
297 // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
298 // e.g. 492101 = 4.11-STABLE = 4.(9+2)
299 const major = value / 100_000;
300 const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
301 const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
302 const patch = value % 1_000;
303 os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
304 os.version_range.semver.max = os.version_range.semver.min;
305 },
306 .netbsd => {
307 // #define __NetBSD_Version__ MMmmrrpp00
308 //
309 // M = major version
310 // m = minor version; a minor number of 99 indicates current.
311 // r = 0 (*)
312 // p = patchlevel
313 const major = value / 100_000_000;
314 const minor = value % 100_000_000 / 1_000_000;
315 const patch = value % 10_000 / 100;
316 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
317 os.version_range.semver.max = os.version_range.semver.min;
318 },
319 .dragonfly => {
320 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
321 // flat base10 format: Mmmmpp
322 // M = major
323 // m = minor; odd-numbers indicate current dev branch
324 // p = patch
325 const major = value / 100_000;
326 const minor = value % 100_000 / 100;
327 const patch = value % 100;
328 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
329 os.version_range.semver.max = os.version_range.semver.min;
330 },
331 else => unreachable,
332 }
333 },
334 .openbsd => {
335 const mib: [2]c_int = [_]c_int{
336 std.os.CTL.KERN,
337 std.os.KERN.OSRELEASE,
338 };
339 var buf: [64]u8 = undefined;
340 var len: usize = buf.len;
341
342 std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
343 error.NameTooLong => unreachable, // constant, known good value
344 error.PermissionDenied => unreachable, // only when setting values,
345 error.SystemResources => unreachable, // memory already on the stack
346 error.UnknownName => unreachable, // constant, known good value
347 error.Unexpected => return error.OSVersionDetectionFail,
348 };
349
350 if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| {
351 os.version_range.semver.min = ver;
352 os.version_range.semver.max = ver;
353 } else |_| {
354 return error.OSVersionDetectionFail;
355 }
356 },
357 else => {
358 // Unimplemented, fall back to default version range.
359 },
360 }
361 }
362
363 if (cross_target.os_version_min) |min| switch (min) {
364 .none => {},
365 .semver => |semver| switch (cross_target.getOsTag()) {
366 .linux => os.version_range.linux.range.min = semver,
367 else => os.version_range.semver.min = semver,
368 },
369 .windows => |win_ver| os.version_range.windows.min = win_ver,
370 };
371
372 if (cross_target.os_version_max) |max| switch (max) {
373 .none => {},
374 .semver => |semver| switch (cross_target.getOsTag()) {
375 .linux => os.version_range.linux.range.max = semver,
376 else => os.version_range.semver.max = semver,
377 },
378 .windows => |win_ver| os.version_range.windows.max = win_ver,
379 };
380
381 if (cross_target.glibc_version) |glibc| {
382 assert(cross_target.isGnuLibC());
383 os.version_range.linux.glibc = glibc;
384 }
385
386 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
387 // native CPU architecture as being different than the current target), we use this:
388 const cpu_arch = cross_target.getCpuArch();
389
390 var cpu = switch (cross_target.cpu_model) {
391 .native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target),
392 .baseline => Target.Cpu.baseline(cpu_arch),
393 .determined_by_cpu_arch => if (cross_target.cpu_arch == null)
394 detectNativeCpuAndFeatures(cpu_arch, os, cross_target)
395 else
396 Target.Cpu.baseline(cpu_arch),
397 .explicit => |model| model.toCpu(cpu_arch),
398 } orelse backup_cpu_detection: {
399 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
400 };
401 var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
402 // For x86, we need to populate some CPU feature flags depending on architecture
403 // and mode:
404 // * 16bit_mode => if the abi is code16
405 // * 32bit_mode => if the arch is i386
406 // However, the "mode" flags can be used as overrides, so if the user explicitly
407 // sets one of them, that takes precedence.
408 switch (cpu_arch) {
409 .i386 => {
410 if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{
411 .@"16bit_mode", .@"32bit_mode",
412 })) {
413 switch (result.target.abi) {
414 .code16 => result.target.cpu.features.addFeature(
415 @enumToInt(std.Target.x86.Feature.@"16bit_mode"),
416 ),
417 else => result.target.cpu.features.addFeature(
418 @enumToInt(std.Target.x86.Feature.@"32bit_mode"),
419 ),
420 }
421 }
422 },
423 .arm, .armeb => {
424 // XXX What do we do if the target has the noarm feature?
425 // What do we do if the user specifies +thumb_mode?
426 },
427 .thumb, .thumbeb => {
428 result.target.cpu.features.addFeature(
429 @enumToInt(std.Target.arm.Feature.thumb_mode),
430 );
431 },
432 else => {},
433 }
434 cross_target.updateCpuFeatures(&result.target.cpu.features);
435 return result;
436 }
437
438 /// First we attempt to use the executable's own binary. If it is dynamically
439 /// linked, then it should answer both the C ABI question and the dynamic linker question.
440 /// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
441 /// we fall back to the defaults.
442 /// TODO Remove the Allocator requirement from this function.
443 fn detectAbiAndDynamicLinker(
444 allocator: Allocator,
445 cpu: Target.Cpu,
446 os: Target.Os,
447 cross_target: CrossTarget,
448 ) DetectError!NativeTargetInfo {
449 const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
450 const is_linux = builtin.target.os.tag == .linux;
451 const have_all_info = cross_target.dynamic_linker.get() != null and
452 cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
453 const os_is_non_native = cross_target.os_tag != null;
454 if (!native_target_has_ld or have_all_info or os_is_non_native) {
455 return defaultAbiAndDynamicLinker(cpu, os, cross_target);
456 }
457 if (cross_target.abi) |abi| {
458 if (abi.isMusl()) {
459 // musl implies static linking.
460 return defaultAbiAndDynamicLinker(cpu, os, cross_target);
461 }
462 }
463 // The current target's ABI cannot be relied on for this. For example, we may build the zig
464 // compiler for target riscv64-linux-musl and provide a tarball for users to download.
465 // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
466 // and supported by Zig. But that means that we must detect the system ABI here rather than
467 // relying on `builtin.target`.
468 const all_abis = comptime blk: {
469 assert(@enumToInt(Target.Abi.none) == 0);
470 const fields = std.meta.fields(Target.Abi)[1..];
471 var array: [fields.len]Target.Abi = undefined;
472 inline for (fields) |field, i| {
473 array[i] = @field(Target.Abi, field.name);
474 }
475 break :blk array;
476 };
477 var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
478 var ld_info_list_len: usize = 0;
479
480 for (all_abis) |abi| {
481 // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
482 // skip adding it to `ld_info_list`.
483 const target: Target = .{
484 .cpu = cpu,
485 .os = os,
486 .abi = abi,
487 };
488 const ld = target.standardDynamicLinkerPath();
489 if (ld.get() == null) continue;
490
491 ld_info_list_buffer[ld_info_list_len] = .{
492 .ld = ld,
493 .abi = abi,
494 };
495 ld_info_list_len += 1;
496 }
497 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
498
499 // Best case scenario: the executable is dynamically linked, and we can iterate
500 // over our own shared objects and find a dynamic linker.
501 self_exe: {
502 const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
503 defer {
504 for (lib_paths) |lib_path| {
505 allocator.free(lib_path);
506 }
507 allocator.free(lib_paths);
508 }
509
510 var found_ld_info: LdInfo = undefined;
511 var found_ld_path: [:0]const u8 = undefined;
512
513 // Look for dynamic linker.
514 // This is O(N^M) but typical case here is N=2 and M=10.
515 find_ld: for (lib_paths) |lib_path| {
516 for (ld_info_list) |ld_info| {
517 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
518 if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
519 found_ld_info = ld_info;
520 found_ld_path = lib_path;
521 break :find_ld;
522 }
523 }
524 } else break :self_exe;
525
526 // Look for glibc version.
527 var os_adjusted = os;
528 if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and
529 cross_target.glibc_version == null)
530 {
531 for (lib_paths) |lib_path| {
532 if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
533 os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
534 error.UnrecognizedGnuLibCFileName => continue,
535 error.InvalidGnuLibCVersion => continue,
536 error.GnuLibCVersionUnavailable => continue,
537 else => |e| return e,
538 };
539 break;
540 }
541 }
542 }
543
544 var result: NativeTargetInfo = .{
545 .target = .{
546 .cpu = cpu,
547 .os = os_adjusted,
548 .abi = cross_target.abi orelse found_ld_info.abi,
549 },
550 .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
551 DynamicLinker.init(found_ld_path)
552 else
553 cross_target.dynamic_linker,
554 };
555 return result;
556 }
557
558 const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) {
559 error.NoSpaceLeft => unreachable,
560 error.NameTooLong => unreachable,
561 error.PathAlreadyExists => unreachable,
562 error.SharingViolation => unreachable,
563 error.InvalidUtf8 => unreachable,
564 error.BadPathName => unreachable,
565 error.PipeBusy => unreachable,
566 error.FileLocksNotSupported => unreachable,
567 error.WouldBlock => unreachable,
568
569 error.IsDir,
570 error.NotDir,
571 error.AccessDenied,
572 error.NoDevice,
573 error.FileNotFound,
574 error.FileTooBig,
575 error.Unexpected,
576 => return defaultAbiAndDynamicLinker(cpu, os, cross_target),
577
578 else => |e| return e,
579 };
580 defer env_file.close();
581
582 // If Zig is statically linked, such as via distributed binary static builds, the above
583 // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
584 // Since that path is hard-coded into the shebang line of many portable scripts, it's a
585 // reasonably reliable path to check for.
586 return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
587 error.FileSystem,
588 error.SystemResources,
589 error.SymLinkLoop,
590 error.ProcessFdQuotaExceeded,
591 error.SystemFdQuotaExceeded,
592 => |e| return e,
593
594 error.UnableToReadElfFile,
595 error.InvalidElfClass,
596 error.InvalidElfVersion,
597 error.InvalidElfEndian,
598 error.InvalidElfFile,
599 error.InvalidElfMagic,
600 error.Unexpected,
601 error.UnexpectedEndOfFile,
602 error.NameTooLong,
603 // Finally, we fall back on the standard path.
604 => defaultAbiAndDynamicLinker(cpu, os, cross_target),
605 };
606 }
607
608 const glibc_so_basename = "libc.so.6";
609
610 fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
611 var link_buf: [std.os.PATH_MAX]u8 = undefined;
612 const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {
613 error.AccessDenied => return error.GnuLibCVersionUnavailable,
614 error.FileSystem => return error.FileSystem,
615 error.SymLinkLoop => return error.SymLinkLoop,
616 error.NameTooLong => unreachable,
617 error.NotLink => return error.GnuLibCVersionUnavailable,
618 error.FileNotFound => return error.GnuLibCVersionUnavailable,
619 error.SystemResources => return error.SystemResources,
620 error.NotDir => return error.GnuLibCVersionUnavailable,
621 error.Unexpected => return error.GnuLibCVersionUnavailable,
622 error.InvalidUtf8 => unreachable, // Windows only
623 error.BadPathName => unreachable, // Windows only
624 error.UnsupportedReparsePointType => unreachable, // Windows only
625 };
626 return glibcVerFromLinkName(link_name);
627 }
628
629 fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version {
630 // example: "libc-2.3.4.so"
631 // example: "libc-2.27.so"
632 const prefix = "libc-";
633 const suffix = ".so";
634 if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
635 return error.UnrecognizedGnuLibCFileName;
636 }
637 // chop off "libc-" and ".so"
638 const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
639 return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
640 error.Overflow => return error.InvalidGnuLibCVersion,
641 error.InvalidCharacter => return error.InvalidGnuLibCVersion,
642 error.InvalidVersion => return error.InvalidGnuLibCVersion,
643 };
644 }
645
646 pub const AbiAndDynamicLinkerFromFileError = error{
647 FileSystem,
648 SystemResources,
649 SymLinkLoop,
650 ProcessFdQuotaExceeded,
651 SystemFdQuotaExceeded,
652 UnableToReadElfFile,
653 InvalidElfClass,
654 InvalidElfVersion,
655 InvalidElfEndian,
656 InvalidElfFile,
657 InvalidElfMagic,
658 Unexpected,
659 UnexpectedEndOfFile,
660 NameTooLong,
661 };
662
663 pub fn abiAndDynamicLinkerFromFile(
664 file: fs.File,
665 cpu: Target.Cpu,
666 os: Target.Os,
667 ld_info_list: []const LdInfo,
668 cross_target: CrossTarget,
669 ) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
670 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
671 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
672 const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
673 const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
674 if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
675 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
676 elf.ELFDATA2LSB => .Little,
677 elf.ELFDATA2MSB => .Big,
678 else => return error.InvalidElfEndian,
679 };
680 const need_bswap = elf_endian != native_endian;
681 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
682
683 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
684 elf.ELFCLASS32 => false,
685 elf.ELFCLASS64 => true,
686 else => return error.InvalidElfClass,
687 };
688 var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
689 const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
690 const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
691
692 var result: NativeTargetInfo = .{
693 .target = .{
694 .cpu = cpu,
695 .os = os,
696 .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
697 },
698 .dynamic_linker = cross_target.dynamic_linker,
699 };
700 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
701 const look_for_ld = cross_target.dynamic_linker.get() == null;
702
703 var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
704 if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
705
706 var ph_i: u16 = 0;
707 while (ph_i < phnum) {
708 // Reserve some bytes so that we can deref the 64-bit struct fields
709 // even when the ELF file is 32-bits.
710 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
711 const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
712 var ph_buf_i: usize = 0;
713 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
714 ph_i += 1;
715 phoff += phentsize;
716 ph_buf_i += phentsize;
717 }) {
718 const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
719 const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
720 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
721 switch (p_type) {
722 elf.PT_INTERP => if (look_for_ld) {
723 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
724 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
725 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
726 const filesz = @intCast(usize, p_filesz);
727 _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
728 // PT_INTERP includes a null byte in filesz.
729 const len = filesz - 1;
730 // dynamic_linker.max_byte is "max", not "len".
731 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
732 result.dynamic_linker.max_byte = @intCast(u8, len - 1);
733
734 // Use it to determine ABI.
735 const full_ld_path = result.dynamic_linker.buffer[0..len];
736 for (ld_info_list) |ld_info| {
737 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
738 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
739 result.target.abi = ld_info.abi;
740 break;
741 }
742 }
743 },
744 // We only need this for detecting glibc version.
745 elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
746 cross_target.glibc_version == null)
747 {
748 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
749 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
750 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
751 const dyn_num = p_filesz / dyn_size;
752 var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
753 var dyn_i: usize = 0;
754 dyn: while (dyn_i < dyn_num) {
755 // Reserve some bytes so that we can deref the 64-bit struct fields
756 // even when the ELF file is 32-bits.
757 const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
758 const dyn_read_byte_len = try preadMin(
759 file,
760 dyn_buf[0 .. dyn_buf.len - dyn_reserve],
761 dyn_off,
762 dyn_size,
763 );
764 var dyn_buf_i: usize = 0;
765 while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
766 dyn_i += 1;
767 dyn_off += dyn_size;
768 dyn_buf_i += dyn_size;
769 }) {
770 const dyn32 = @ptrCast(
771 *elf.Elf32_Dyn,
772 @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
773 );
774 const dyn64 = @ptrCast(
775 *elf.Elf64_Dyn,
776 @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
777 );
778 const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
779 const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
780 if (tag == elf.DT_RUNPATH) {
781 rpath_offset = val;
782 break :dyn;
783 }
784 }
785 }
786 },
787 else => continue,
788 }
789 }
790 }
791
792 if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) {
793 if (rpath_offset) |rpoff| {
794 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
795
796 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
797 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
798 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
799
800 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
801 if (sh_buf.len < shentsize) return error.InvalidElfFile;
802
803 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
804 const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
805 const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
806 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
807 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
808 var strtab_buf: [4096:0]u8 = undefined;
809 const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
810 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
811 const shstrtab = strtab_buf[0..shstrtab_read_len];
812
813 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
814 var sh_i: u16 = 0;
815 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
816 // Reserve some bytes so that we can deref the 64-bit struct fields
817 // even when the ELF file is 32-bits.
818 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
819 const sh_read_byte_len = try preadMin(
820 file,
821 sh_buf[0 .. sh_buf.len - sh_reserve],
822 shoff,
823 shentsize,
824 );
825 var sh_buf_i: usize = 0;
826 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
827 sh_i += 1;
828 shoff += shentsize;
829 sh_buf_i += shentsize;
830 }) {
831 const sh32 = @ptrCast(
832 *elf.Elf32_Shdr,
833 @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
834 );
835 const sh64 = @ptrCast(
836 *elf.Elf64_Shdr,
837 @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
838 );
839 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
840 // TODO this pointer cast should not be necessary
841 const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
842 if (mem.eql(u8, sh_name, ".dynstr")) {
843 break :find_dyn_str .{
844 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
845 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
846 };
847 }
848 }
849 } else null;
850
851 if (dynstr) |ds| {
852 const strtab_len = std.math.min(ds.size, strtab_buf.len);
853 const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len);
854 const strtab = strtab_buf[0..strtab_read_len];
855 // TODO this pointer cast should not be necessary
856 const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) {
857 error.Overflow => return error.InvalidElfFile,
858 };
859 const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0);
860 var it = mem.tokenize(u8, rpath_list, ":");
861 while (it.next()) |rpath| {
862 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
863 error.NameTooLong => unreachable,
864 error.InvalidUtf8 => unreachable,
865 error.BadPathName => unreachable,
866 error.DeviceBusy => unreachable,
867
868 error.FileNotFound,
869 error.NotDir,
870 error.AccessDenied,
871 error.NoDevice,
872 => continue,
873
874 error.ProcessFdQuotaExceeded,
875 error.SystemFdQuotaExceeded,
876 error.SystemResources,
877 error.SymLinkLoop,
878 error.Unexpected,
879 => |e| return e,
880 };
881 defer dir.close();
882
883 var link_buf: [std.os.PATH_MAX]u8 = undefined;
884 const link_name = std.os.readlinkatZ(
885 dir.fd,
886 glibc_so_basename,
887 &link_buf,
888 ) catch |err| switch (err) {
889 error.NameTooLong => unreachable,
890 error.InvalidUtf8 => unreachable, // Windows only
891 error.BadPathName => unreachable, // Windows only
892 error.UnsupportedReparsePointType => unreachable, // Windows only
893
894 error.AccessDenied,
895 error.FileNotFound,
896 error.NotLink,
897 error.NotDir,
898 => continue,
899
900 error.SystemResources,
901 error.FileSystem,
902 error.SymLinkLoop,
903 error.Unexpected,
904 => |e| return e,
905 };
906 result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
907 link_name,
908 ) catch |err| switch (err) {
909 error.UnrecognizedGnuLibCFileName,
910 error.InvalidGnuLibCVersion,
911 => continue,
912 };
913 break;
914 }
915 }
916 }
917 }
918
919 return result;
920 }
921
922 fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
923 var i: usize = 0;
924 while (i < min_read_len) {
925 const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
926 error.OperationAborted => unreachable, // Windows-only
927 error.WouldBlock => unreachable, // Did not request blocking mode
928 error.NotOpenForReading => unreachable,
929 error.SystemResources => return error.SystemResources,
930 error.IsDir => return error.UnableToReadElfFile,
931 error.BrokenPipe => return error.UnableToReadElfFile,
932 error.Unseekable => return error.UnableToReadElfFile,
933 error.ConnectionResetByPeer => return error.UnableToReadElfFile,
934 error.ConnectionTimedOut => return error.UnableToReadElfFile,
935 error.Unexpected => return error.Unexpected,
936 error.InputOutput => return error.FileSystem,
937 error.AccessDenied => return error.Unexpected,
938 };
939 if (len == 0) return error.UnexpectedEndOfFile;
940 i += len;
941 }
942 return i;
943 }
944
945 fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
946 const target: Target = .{
947 .cpu = cpu,
948 .os = os,
949 .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
950 };
951 return NativeTargetInfo{
952 .target = target,
953 .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
954 target.standardDynamicLinkerPath()
955 else
956 cross_target.dynamic_linker,
957 };
958 }
959
960 pub const LdInfo = struct {
961 ld: DynamicLinker,
962 abi: Target.Abi,
963 };
964
965 pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
966 if (is_64) {
967 if (need_bswap) {
968 return @byteSwap(@TypeOf(int_64), int_64);
969 } else {
970 return int_64;
971 }
972 } else {
973 if (need_bswap) {
974 return @byteSwap(@TypeOf(int_32), int_32);
975 } else {
976 return int_32;
977 }
978 }
979 }
980
981 fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
982 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
983 // although it is a runtime value, is guaranteed to be one of the architectures in the set
984 // of the respective switch prong.
985 switch (builtin.cpu.arch) {
986 .x86_64, .i386 => {
987 return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
988 },
989 else => {},
990 }
991
992 switch (builtin.os.tag) {
993 .linux => return linux.detectNativeCpuAndFeatures(),
994 .macos => return darwin.macos.detectNativeCpuAndFeatures(),
995 else => {},
996 }
997
998 // This architecture does not have CPU model & feature detection yet.
999 // See https://github.com/ziglang/zig/issues/4591
1000 return null;
1001 }
1002};
6pub const linux = @import("system/linux.zig");
10037
10048test {
1005 _ = @import("system/darwin.zig");
1006 _ = @import("system/linux.zig");
9 _ = NativePaths;
10 _ = NativeTargetInfo;
11
12 _ = darwin;
13 _ = linux;
14 _ = windows;
100715}
lib/std/zig/system/NativePaths.zig created+205
......@@ -0,0 +1,205 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const ArrayList = std.ArrayList;
4const Allocator = std.mem.Allocator;
5const process = std.process;
6const mem = std.mem;
7
8const NativePaths = @This();
9const NativeTargetInfo = std.zig.system.NativeTargetInfo;
10
11include_dirs: ArrayList([:0]u8),
12lib_dirs: ArrayList([:0]u8),
13framework_dirs: ArrayList([:0]u8),
14rpaths: ArrayList([:0]u8),
15warnings: ArrayList([:0]u8),
16
17pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths {
18 const native_target = native_info.target;
19
20 var self: NativePaths = .{
21 .include_dirs = ArrayList([:0]u8).init(allocator),
22 .lib_dirs = ArrayList([:0]u8).init(allocator),
23 .framework_dirs = ArrayList([:0]u8).init(allocator),
24 .rpaths = ArrayList([:0]u8).init(allocator),
25 .warnings = ArrayList([:0]u8).init(allocator),
26 };
27 errdefer self.deinit();
28
29 var is_nix = false;
30 if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
31 defer allocator.free(nix_cflags_compile);
32
33 is_nix = true;
34 var it = mem.tokenize(u8, nix_cflags_compile, " ");
35 while (true) {
36 const word = it.next() orelse break;
37 if (mem.eql(u8, word, "-isystem")) {
38 const include_path = it.next() orelse {
39 try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE");
40 break;
41 };
42 try self.addIncludeDir(include_path);
43 } else {
44 if (mem.startsWith(u8, word, "-frandom-seed=")) {
45 continue;
46 }
47 try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});
48 }
49 }
50 } else |err| switch (err) {
51 error.InvalidUtf8 => {},
52 error.EnvironmentVariableNotFound => {},
53 error.OutOfMemory => |e| return e,
54 }
55 if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
56 defer allocator.free(nix_ldflags);
57
58 is_nix = true;
59 var it = mem.tokenize(u8, nix_ldflags, " ");
60 while (true) {
61 const word = it.next() orelse break;
62 if (mem.eql(u8, word, "-rpath")) {
63 const rpath = it.next() orelse {
64 try self.addWarning("Expected argument after -rpath in NIX_LDFLAGS");
65 break;
66 };
67 try self.addRPath(rpath);
68 } else if (word.len > 2 and word[0] == '-' and word[1] == 'L') {
69 const lib_path = word[2..];
70 try self.addLibDir(lib_path);
71 } else {
72 try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word});
73 break;
74 }
75 }
76 } else |err| switch (err) {
77 error.InvalidUtf8 => {},
78 error.EnvironmentVariableNotFound => {},
79 error.OutOfMemory => |e| return e,
80 }
81 if (is_nix) {
82 return self;
83 }
84
85 if (comptime builtin.target.isDarwin()) {
86 try self.addIncludeDir("/usr/include");
87 try self.addIncludeDir("/usr/local/include");
88
89 try self.addLibDir("/usr/lib");
90 try self.addLibDir("/usr/local/lib");
91
92 try self.addFrameworkDir("/Library/Frameworks");
93 try self.addFrameworkDir("/System/Library/Frameworks");
94
95 return self;
96 }
97
98 if (comptime native_target.os.tag == .solaris) {
99 try self.addLibDir("/usr/lib/64");
100 try self.addLibDir("/usr/local/lib/64");
101 try self.addLibDir("/lib/64");
102
103 try self.addIncludeDir("/usr/include");
104 try self.addIncludeDir("/usr/local/include");
105
106 return self;
107 }
108
109 if (native_target.os.tag != .windows) {
110 const triple = try native_target.linuxTriple(allocator);
111 const qual = native_target.cpu.arch.ptrBitWidth();
112
113 // TODO: $ ld --verbose | grep SEARCH_DIR
114 // the output contains some paths that end with lib64, maybe include them too?
115 // TODO: what is the best possible order of things?
116 // TODO: some of these are suspect and should only be added on some systems. audit needed.
117
118 try self.addIncludeDir("/usr/local/include");
119 try self.addLibDirFmt("/usr/local/lib{d}", .{qual});
120 try self.addLibDir("/usr/local/lib");
121
122 try self.addIncludeDirFmt("/usr/include/{s}", .{triple});
123 try self.addLibDirFmt("/usr/lib/{s}", .{triple});
124
125 try self.addIncludeDir("/usr/include");
126 try self.addLibDirFmt("/lib{d}", .{qual});
127 try self.addLibDir("/lib");
128 try self.addLibDirFmt("/usr/lib{d}", .{qual});
129 try self.addLibDir("/usr/lib");
130
131 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
132 // zlib.h is in /usr/include (added above)
133 // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
134 try self.addLibDirFmt("/lib/{s}", .{triple});
135 }
136
137 return self;
138}
139
140pub fn deinit(self: *NativePaths) void {
141 deinitArray(&self.include_dirs);
142 deinitArray(&self.lib_dirs);
143 deinitArray(&self.framework_dirs);
144 deinitArray(&self.rpaths);
145 deinitArray(&self.warnings);
146 self.* = undefined;
147}
148
149fn deinitArray(array: *ArrayList([:0]u8)) void {
150 for (array.items) |item| {
151 array.allocator.free(item);
152 }
153 array.deinit();
154}
155
156pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void {
157 return self.appendArray(&self.include_dirs, s);
158}
159
160pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
161 const item = try std.fmt.allocPrintZ(self.include_dirs.allocator, fmt, args);
162 errdefer self.include_dirs.allocator.free(item);
163 try self.include_dirs.append(item);
164}
165
166pub fn addLibDir(self: *NativePaths, s: []const u8) !void {
167 return self.appendArray(&self.lib_dirs, s);
168}
169
170pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
171 const item = try std.fmt.allocPrintZ(self.lib_dirs.allocator, fmt, args);
172 errdefer self.lib_dirs.allocator.free(item);
173 try self.lib_dirs.append(item);
174}
175
176pub fn addWarning(self: *NativePaths, s: []const u8) !void {
177 return self.appendArray(&self.warnings, s);
178}
179
180pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void {
181 return self.appendArray(&self.framework_dirs, s);
182}
183
184pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
185 const item = try std.fmt.allocPrintZ(self.framework_dirs.allocator, fmt, args);
186 errdefer self.framework_dirs.allocator.free(item);
187 try self.framework_dirs.append(item);
188}
189
190pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
191 const item = try std.fmt.allocPrintZ(self.warnings.allocator, fmt, args);
192 errdefer self.warnings.allocator.free(item);
193 try self.warnings.append(item);
194}
195
196pub fn addRPath(self: *NativePaths, s: []const u8) !void {
197 return self.appendArray(&self.rpaths, s);
198}
199
200fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void {
201 _ = self;
202 const item = try array.allocator.dupeZ(u8, s);
203 errdefer array.allocator.free(item);
204 try array.append(item);
205}
lib/std/zig/system/NativeTargetInfo.zig created+937
......@@ -0,0 +1,937 @@
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 CrossTarget = std.zig.CrossTarget;
13const windows = std.zig.system.windows;
14const darwin = std.zig.system.darwin;
15const linux = std.zig.system.linux;
16
17target: Target,
18dynamic_linker: DynamicLinker = DynamicLinker{},
19
20pub const DynamicLinker = Target.DynamicLinker;
21
22pub const DetectError = error{
23 OutOfMemory,
24 FileSystem,
25 SystemResources,
26 SymLinkLoop,
27 ProcessFdQuotaExceeded,
28 SystemFdQuotaExceeded,
29 DeviceBusy,
30 OSVersionDetectionFail,
31};
32
33/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
34/// natively, which should be standard or default, and which are provided explicitly, this function
35/// resolves the native components by detecting the native system, and then resolves standard/default parts
36/// relative to that.
37/// Any resources this function allocates are released before returning, and so there is no
38/// deinitialization method.
39/// TODO Remove the Allocator requirement from this function.
40pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
41 var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
42 if (cross_target.os_tag == null) {
43 switch (builtin.target.os.tag) {
44 .linux => {
45 const uts = std.os.uname();
46 const release = mem.sliceTo(&uts.release, 0);
47 // The release field sometimes has a weird format,
48 // `Version.parse` will attempt to find some meaningful interpretation.
49 if (std.builtin.Version.parse(release)) |ver| {
50 os.version_range.linux.range.min = ver;
51 os.version_range.linux.range.max = ver;
52 } else |err| switch (err) {
53 error.Overflow => {},
54 error.InvalidCharacter => {},
55 error.InvalidVersion => {},
56 }
57 },
58 .solaris => {
59 const uts = std.os.uname();
60 const release = mem.sliceTo(&uts.release, 0);
61 if (std.builtin.Version.parse(release)) |ver| {
62 os.version_range.semver.min = ver;
63 os.version_range.semver.max = ver;
64 } else |err| switch (err) {
65 error.Overflow => {},
66 error.InvalidCharacter => {},
67 error.InvalidVersion => {},
68 }
69 },
70 .windows => {
71 const detected_version = windows.detectRuntimeVersion();
72 os.version_range.windows.min = detected_version;
73 os.version_range.windows.max = detected_version;
74 },
75 .macos => try darwin.macos.detect(&os),
76 .freebsd, .netbsd, .dragonfly => {
77 const key = switch (builtin.target.os.tag) {
78 .freebsd => "kern.osreldate",
79 .netbsd, .dragonfly => "kern.osrevision",
80 else => unreachable,
81 };
82 var value: u32 = undefined;
83 var len: usize = @sizeOf(@TypeOf(value));
84
85 std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
86 error.NameTooLong => unreachable, // constant, known good value
87 error.PermissionDenied => unreachable, // only when setting values,
88 error.SystemResources => unreachable, // memory already on the stack
89 error.UnknownName => unreachable, // constant, known good value
90 error.Unexpected => return error.OSVersionDetectionFail,
91 };
92
93 switch (builtin.target.os.tag) {
94 .freebsd => {
95 // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
96 // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
97 // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
98 // e.g. 492101 = 4.11-STABLE = 4.(9+2)
99 const major = value / 100_000;
100 const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
101 const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
102 const patch = value % 1_000;
103 os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
104 os.version_range.semver.max = os.version_range.semver.min;
105 },
106 .netbsd => {
107 // #define __NetBSD_Version__ MMmmrrpp00
108 //
109 // M = major version
110 // m = minor version; a minor number of 99 indicates current.
111 // r = 0 (*)
112 // p = patchlevel
113 const major = value / 100_000_000;
114 const minor = value % 100_000_000 / 1_000_000;
115 const patch = value % 10_000 / 100;
116 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
117 os.version_range.semver.max = os.version_range.semver.min;
118 },
119 .dragonfly => {
120 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
121 // flat base10 format: Mmmmpp
122 // M = major
123 // m = minor; odd-numbers indicate current dev branch
124 // p = patch
125 const major = value / 100_000;
126 const minor = value % 100_000 / 100;
127 const patch = value % 100;
128 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
129 os.version_range.semver.max = os.version_range.semver.min;
130 },
131 else => unreachable,
132 }
133 },
134 .openbsd => {
135 const mib: [2]c_int = [_]c_int{
136 std.os.CTL.KERN,
137 std.os.KERN.OSRELEASE,
138 };
139 var buf: [64]u8 = undefined;
140 var len: usize = buf.len;
141
142 std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
143 error.NameTooLong => unreachable, // constant, known good value
144 error.PermissionDenied => unreachable, // only when setting values,
145 error.SystemResources => unreachable, // memory already on the stack
146 error.UnknownName => unreachable, // constant, known good value
147 error.Unexpected => return error.OSVersionDetectionFail,
148 };
149
150 if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| {
151 os.version_range.semver.min = ver;
152 os.version_range.semver.max = ver;
153 } else |_| {
154 return error.OSVersionDetectionFail;
155 }
156 },
157 else => {
158 // Unimplemented, fall back to default version range.
159 },
160 }
161 }
162
163 if (cross_target.os_version_min) |min| switch (min) {
164 .none => {},
165 .semver => |semver| switch (cross_target.getOsTag()) {
166 .linux => os.version_range.linux.range.min = semver,
167 else => os.version_range.semver.min = semver,
168 },
169 .windows => |win_ver| os.version_range.windows.min = win_ver,
170 };
171
172 if (cross_target.os_version_max) |max| switch (max) {
173 .none => {},
174 .semver => |semver| switch (cross_target.getOsTag()) {
175 .linux => os.version_range.linux.range.max = semver,
176 else => os.version_range.semver.max = semver,
177 },
178 .windows => |win_ver| os.version_range.windows.max = win_ver,
179 };
180
181 if (cross_target.glibc_version) |glibc| {
182 assert(cross_target.isGnuLibC());
183 os.version_range.linux.glibc = glibc;
184 }
185
186 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
187 // native CPU architecture as being different than the current target), we use this:
188 const cpu_arch = cross_target.getCpuArch();
189
190 var cpu = switch (cross_target.cpu_model) {
191 .native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target),
192 .baseline => Target.Cpu.baseline(cpu_arch),
193 .determined_by_cpu_arch => if (cross_target.cpu_arch == null)
194 detectNativeCpuAndFeatures(cpu_arch, os, cross_target)
195 else
196 Target.Cpu.baseline(cpu_arch),
197 .explicit => |model| model.toCpu(cpu_arch),
198 } orelse backup_cpu_detection: {
199 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
200 };
201 var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
202 // For x86, we need to populate some CPU feature flags depending on architecture
203 // and mode:
204 // * 16bit_mode => if the abi is code16
205 // * 32bit_mode => if the arch is i386
206 // However, the "mode" flags can be used as overrides, so if the user explicitly
207 // sets one of them, that takes precedence.
208 switch (cpu_arch) {
209 .i386 => {
210 if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{
211 .@"16bit_mode", .@"32bit_mode",
212 })) {
213 switch (result.target.abi) {
214 .code16 => result.target.cpu.features.addFeature(
215 @enumToInt(std.Target.x86.Feature.@"16bit_mode"),
216 ),
217 else => result.target.cpu.features.addFeature(
218 @enumToInt(std.Target.x86.Feature.@"32bit_mode"),
219 ),
220 }
221 }
222 },
223 .arm, .armeb => {
224 // XXX What do we do if the target has the noarm feature?
225 // What do we do if the user specifies +thumb_mode?
226 },
227 .thumb, .thumbeb => {
228 result.target.cpu.features.addFeature(
229 @enumToInt(std.Target.arm.Feature.thumb_mode),
230 );
231 },
232 else => {},
233 }
234 cross_target.updateCpuFeatures(&result.target.cpu.features);
235 return result;
236}
237
238/// First we attempt to use the executable's own binary. If it is dynamically
239/// linked, then it should answer both the C ABI question and the dynamic linker question.
240/// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
241/// we fall back to the defaults.
242/// TODO Remove the Allocator requirement from this function.
243fn detectAbiAndDynamicLinker(
244 allocator: Allocator,
245 cpu: Target.Cpu,
246 os: Target.Os,
247 cross_target: CrossTarget,
248) DetectError!NativeTargetInfo {
249 const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
250 const is_linux = builtin.target.os.tag == .linux;
251 const have_all_info = cross_target.dynamic_linker.get() != null and
252 cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
253 const os_is_non_native = cross_target.os_tag != null;
254 if (!native_target_has_ld or have_all_info or os_is_non_native) {
255 return defaultAbiAndDynamicLinker(cpu, os, cross_target);
256 }
257 if (cross_target.abi) |abi| {
258 if (abi.isMusl()) {
259 // musl implies static linking.
260 return defaultAbiAndDynamicLinker(cpu, os, cross_target);
261 }
262 }
263 // The current target's ABI cannot be relied on for this. For example, we may build the zig
264 // compiler for target riscv64-linux-musl and provide a tarball for users to download.
265 // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
266 // and supported by Zig. But that means that we must detect the system ABI here rather than
267 // relying on `builtin.target`.
268 const all_abis = comptime blk: {
269 assert(@enumToInt(Target.Abi.none) == 0);
270 const fields = std.meta.fields(Target.Abi)[1..];
271 var array: [fields.len]Target.Abi = undefined;
272 inline for (fields) |field, i| {
273 array[i] = @field(Target.Abi, field.name);
274 }
275 break :blk array;
276 };
277 var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
278 var ld_info_list_len: usize = 0;
279
280 for (all_abis) |abi| {
281 // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
282 // skip adding it to `ld_info_list`.
283 const target: Target = .{
284 .cpu = cpu,
285 .os = os,
286 .abi = abi,
287 };
288 const ld = target.standardDynamicLinkerPath();
289 if (ld.get() == null) continue;
290
291 ld_info_list_buffer[ld_info_list_len] = .{
292 .ld = ld,
293 .abi = abi,
294 };
295 ld_info_list_len += 1;
296 }
297 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
298
299 // Best case scenario: the executable is dynamically linked, and we can iterate
300 // over our own shared objects and find a dynamic linker.
301 self_exe: {
302 const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
303 defer {
304 for (lib_paths) |lib_path| {
305 allocator.free(lib_path);
306 }
307 allocator.free(lib_paths);
308 }
309
310 var found_ld_info: LdInfo = undefined;
311 var found_ld_path: [:0]const u8 = undefined;
312
313 // Look for dynamic linker.
314 // This is O(N^M) but typical case here is N=2 and M=10.
315 find_ld: for (lib_paths) |lib_path| {
316 for (ld_info_list) |ld_info| {
317 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
318 if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
319 found_ld_info = ld_info;
320 found_ld_path = lib_path;
321 break :find_ld;
322 }
323 }
324 } else break :self_exe;
325
326 // Look for glibc version.
327 var os_adjusted = os;
328 if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and
329 cross_target.glibc_version == null)
330 {
331 for (lib_paths) |lib_path| {
332 if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
333 os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
334 error.UnrecognizedGnuLibCFileName => continue,
335 error.InvalidGnuLibCVersion => continue,
336 error.GnuLibCVersionUnavailable => continue,
337 else => |e| return e,
338 };
339 break;
340 }
341 }
342 }
343
344 var result: NativeTargetInfo = .{
345 .target = .{
346 .cpu = cpu,
347 .os = os_adjusted,
348 .abi = cross_target.abi orelse found_ld_info.abi,
349 },
350 .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
351 DynamicLinker.init(found_ld_path)
352 else
353 cross_target.dynamic_linker,
354 };
355 return result;
356 }
357
358 const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) {
359 error.NoSpaceLeft => unreachable,
360 error.NameTooLong => unreachable,
361 error.PathAlreadyExists => unreachable,
362 error.SharingViolation => unreachable,
363 error.InvalidUtf8 => unreachable,
364 error.BadPathName => unreachable,
365 error.PipeBusy => unreachable,
366 error.FileLocksNotSupported => unreachable,
367 error.WouldBlock => unreachable,
368
369 error.IsDir,
370 error.NotDir,
371 error.AccessDenied,
372 error.NoDevice,
373 error.FileNotFound,
374 error.FileTooBig,
375 error.Unexpected,
376 => return defaultAbiAndDynamicLinker(cpu, os, cross_target),
377
378 else => |e| return e,
379 };
380 defer env_file.close();
381
382 // If Zig is statically linked, such as via distributed binary static builds, the above
383 // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
384 // Since that path is hard-coded into the shebang line of many portable scripts, it's a
385 // reasonably reliable path to check for.
386 return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
387 error.FileSystem,
388 error.SystemResources,
389 error.SymLinkLoop,
390 error.ProcessFdQuotaExceeded,
391 error.SystemFdQuotaExceeded,
392 => |e| return e,
393
394 error.UnableToReadElfFile,
395 error.InvalidElfClass,
396 error.InvalidElfVersion,
397 error.InvalidElfEndian,
398 error.InvalidElfFile,
399 error.InvalidElfMagic,
400 error.Unexpected,
401 error.UnexpectedEndOfFile,
402 error.NameTooLong,
403 // Finally, we fall back on the standard path.
404 => defaultAbiAndDynamicLinker(cpu, os, cross_target),
405 };
406}
407
408const glibc_so_basename = "libc.so.6";
409
410fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
411 var link_buf: [std.os.PATH_MAX]u8 = undefined;
412 const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {
413 error.AccessDenied => return error.GnuLibCVersionUnavailable,
414 error.FileSystem => return error.FileSystem,
415 error.SymLinkLoop => return error.SymLinkLoop,
416 error.NameTooLong => unreachable,
417 error.NotLink => return error.GnuLibCVersionUnavailable,
418 error.FileNotFound => return error.GnuLibCVersionUnavailable,
419 error.SystemResources => return error.SystemResources,
420 error.NotDir => return error.GnuLibCVersionUnavailable,
421 error.Unexpected => return error.GnuLibCVersionUnavailable,
422 error.InvalidUtf8 => unreachable, // Windows only
423 error.BadPathName => unreachable, // Windows only
424 error.UnsupportedReparsePointType => unreachable, // Windows only
425 };
426 return glibcVerFromLinkName(link_name);
427}
428
429fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version {
430 // example: "libc-2.3.4.so"
431 // example: "libc-2.27.so"
432 const prefix = "libc-";
433 const suffix = ".so";
434 if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
435 return error.UnrecognizedGnuLibCFileName;
436 }
437 // chop off "libc-" and ".so"
438 const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
439 return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
440 error.Overflow => return error.InvalidGnuLibCVersion,
441 error.InvalidCharacter => return error.InvalidGnuLibCVersion,
442 error.InvalidVersion => return error.InvalidGnuLibCVersion,
443 };
444}
445
446pub const AbiAndDynamicLinkerFromFileError = error{
447 FileSystem,
448 SystemResources,
449 SymLinkLoop,
450 ProcessFdQuotaExceeded,
451 SystemFdQuotaExceeded,
452 UnableToReadElfFile,
453 InvalidElfClass,
454 InvalidElfVersion,
455 InvalidElfEndian,
456 InvalidElfFile,
457 InvalidElfMagic,
458 Unexpected,
459 UnexpectedEndOfFile,
460 NameTooLong,
461};
462
463pub fn abiAndDynamicLinkerFromFile(
464 file: fs.File,
465 cpu: Target.Cpu,
466 os: Target.Os,
467 ld_info_list: []const LdInfo,
468 cross_target: CrossTarget,
469) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
470 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
471 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
472 const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
473 const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
474 if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
475 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
476 elf.ELFDATA2LSB => .Little,
477 elf.ELFDATA2MSB => .Big,
478 else => return error.InvalidElfEndian,
479 };
480 const need_bswap = elf_endian != native_endian;
481 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
482
483 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
484 elf.ELFCLASS32 => false,
485 elf.ELFCLASS64 => true,
486 else => return error.InvalidElfClass,
487 };
488 var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
489 const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
490 const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
491
492 var result: NativeTargetInfo = .{
493 .target = .{
494 .cpu = cpu,
495 .os = os,
496 .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
497 },
498 .dynamic_linker = cross_target.dynamic_linker,
499 };
500 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
501 const look_for_ld = cross_target.dynamic_linker.get() == null;
502
503 var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
504 if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
505
506 var ph_i: u16 = 0;
507 while (ph_i < phnum) {
508 // Reserve some bytes so that we can deref the 64-bit struct fields
509 // even when the ELF file is 32-bits.
510 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
511 const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
512 var ph_buf_i: usize = 0;
513 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
514 ph_i += 1;
515 phoff += phentsize;
516 ph_buf_i += phentsize;
517 }) {
518 const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
519 const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
520 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
521 switch (p_type) {
522 elf.PT_INTERP => if (look_for_ld) {
523 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
524 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
525 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
526 const filesz = @intCast(usize, p_filesz);
527 _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
528 // PT_INTERP includes a null byte in filesz.
529 const len = filesz - 1;
530 // dynamic_linker.max_byte is "max", not "len".
531 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
532 result.dynamic_linker.max_byte = @intCast(u8, len - 1);
533
534 // Use it to determine ABI.
535 const full_ld_path = result.dynamic_linker.buffer[0..len];
536 for (ld_info_list) |ld_info| {
537 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
538 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
539 result.target.abi = ld_info.abi;
540 break;
541 }
542 }
543 },
544 // We only need this for detecting glibc version.
545 elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
546 cross_target.glibc_version == null)
547 {
548 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
549 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
550 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
551 const dyn_num = p_filesz / dyn_size;
552 var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
553 var dyn_i: usize = 0;
554 dyn: while (dyn_i < dyn_num) {
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 dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
558 const dyn_read_byte_len = try preadMin(
559 file,
560 dyn_buf[0 .. dyn_buf.len - dyn_reserve],
561 dyn_off,
562 dyn_size,
563 );
564 var dyn_buf_i: usize = 0;
565 while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
566 dyn_i += 1;
567 dyn_off += dyn_size;
568 dyn_buf_i += dyn_size;
569 }) {
570 const dyn32 = @ptrCast(
571 *elf.Elf32_Dyn,
572 @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
573 );
574 const dyn64 = @ptrCast(
575 *elf.Elf64_Dyn,
576 @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
577 );
578 const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
579 const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
580 if (tag == elf.DT_RUNPATH) {
581 rpath_offset = val;
582 break :dyn;
583 }
584 }
585 }
586 },
587 else => continue,
588 }
589 }
590 }
591
592 if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) {
593 if (rpath_offset) |rpoff| {
594 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
595
596 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
597 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
598 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
599
600 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
601 if (sh_buf.len < shentsize) return error.InvalidElfFile;
602
603 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
604 const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
605 const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
606 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
607 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
608 var strtab_buf: [4096:0]u8 = undefined;
609 const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
610 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
611 const shstrtab = strtab_buf[0..shstrtab_read_len];
612
613 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
614 var sh_i: u16 = 0;
615 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
616 // Reserve some bytes so that we can deref the 64-bit struct fields
617 // even when the ELF file is 32-bits.
618 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
619 const sh_read_byte_len = try preadMin(
620 file,
621 sh_buf[0 .. sh_buf.len - sh_reserve],
622 shoff,
623 shentsize,
624 );
625 var sh_buf_i: usize = 0;
626 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
627 sh_i += 1;
628 shoff += shentsize;
629 sh_buf_i += shentsize;
630 }) {
631 const sh32 = @ptrCast(
632 *elf.Elf32_Shdr,
633 @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
634 );
635 const sh64 = @ptrCast(
636 *elf.Elf64_Shdr,
637 @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
638 );
639 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
640 // TODO this pointer cast should not be necessary
641 const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
642 if (mem.eql(u8, sh_name, ".dynstr")) {
643 break :find_dyn_str .{
644 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
645 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
646 };
647 }
648 }
649 } else null;
650
651 if (dynstr) |ds| {
652 const strtab_len = std.math.min(ds.size, strtab_buf.len);
653 const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len);
654 const strtab = strtab_buf[0..strtab_read_len];
655 // TODO this pointer cast should not be necessary
656 const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) {
657 error.Overflow => return error.InvalidElfFile,
658 };
659 const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0);
660 var it = mem.tokenize(u8, rpath_list, ":");
661 while (it.next()) |rpath| {
662 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
663 error.NameTooLong => unreachable,
664 error.InvalidUtf8 => unreachable,
665 error.BadPathName => unreachable,
666 error.DeviceBusy => unreachable,
667
668 error.FileNotFound,
669 error.NotDir,
670 error.AccessDenied,
671 error.NoDevice,
672 => continue,
673
674 error.ProcessFdQuotaExceeded,
675 error.SystemFdQuotaExceeded,
676 error.SystemResources,
677 error.SymLinkLoop,
678 error.Unexpected,
679 => |e| return e,
680 };
681 defer dir.close();
682
683 var link_buf: [std.os.PATH_MAX]u8 = undefined;
684 const link_name = std.os.readlinkatZ(
685 dir.fd,
686 glibc_so_basename,
687 &link_buf,
688 ) catch |err| switch (err) {
689 error.NameTooLong => unreachable,
690 error.InvalidUtf8 => unreachable, // Windows only
691 error.BadPathName => unreachable, // Windows only
692 error.UnsupportedReparsePointType => unreachable, // Windows only
693
694 error.AccessDenied,
695 error.FileNotFound,
696 error.NotLink,
697 error.NotDir,
698 => continue,
699
700 error.SystemResources,
701 error.FileSystem,
702 error.SymLinkLoop,
703 error.Unexpected,
704 => |e| return e,
705 };
706 result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
707 link_name,
708 ) catch |err| switch (err) {
709 error.UnrecognizedGnuLibCFileName,
710 error.InvalidGnuLibCVersion,
711 => continue,
712 };
713 break;
714 }
715 }
716 }
717 }
718
719 return result;
720}
721
722fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
723 var i: usize = 0;
724 while (i < min_read_len) {
725 const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
726 error.OperationAborted => unreachable, // Windows-only
727 error.WouldBlock => unreachable, // Did not request blocking mode
728 error.NotOpenForReading => unreachable,
729 error.SystemResources => return error.SystemResources,
730 error.IsDir => return error.UnableToReadElfFile,
731 error.BrokenPipe => return error.UnableToReadElfFile,
732 error.Unseekable => return error.UnableToReadElfFile,
733 error.ConnectionResetByPeer => return error.UnableToReadElfFile,
734 error.ConnectionTimedOut => return error.UnableToReadElfFile,
735 error.Unexpected => return error.Unexpected,
736 error.InputOutput => return error.FileSystem,
737 error.AccessDenied => return error.Unexpected,
738 };
739 if (len == 0) return error.UnexpectedEndOfFile;
740 i += len;
741 }
742 return i;
743}
744
745fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
746 const target: Target = .{
747 .cpu = cpu,
748 .os = os,
749 .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
750 };
751 return NativeTargetInfo{
752 .target = target,
753 .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
754 target.standardDynamicLinkerPath()
755 else
756 cross_target.dynamic_linker,
757 };
758}
759
760pub const LdInfo = struct {
761 ld: DynamicLinker,
762 abi: Target.Abi,
763};
764
765pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
766 if (is_64) {
767 if (need_bswap) {
768 return @byteSwap(@TypeOf(int_64), int_64);
769 } else {
770 return int_64;
771 }
772 } else {
773 if (need_bswap) {
774 return @byteSwap(@TypeOf(int_32), int_32);
775 } else {
776 return int_32;
777 }
778 }
779}
780
781fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
782 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
783 // although it is a runtime value, is guaranteed to be one of the architectures in the set
784 // of the respective switch prong.
785 switch (builtin.cpu.arch) {
786 .x86_64, .i386 => {
787 return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
788 },
789 else => {},
790 }
791
792 switch (builtin.os.tag) {
793 .linux => return linux.detectNativeCpuAndFeatures(),
794 .macos => return darwin.macos.detectNativeCpuAndFeatures(),
795 else => {},
796 }
797
798 // This architecture does not have CPU model & feature detection yet.
799 // See https://github.com/ziglang/zig/issues/4591
800 return null;
801}
802
803pub const Executor = union(enum) {
804 native,
805 rosetta,
806 qemu: []const u8,
807 wine: []const u8,
808 wasmtime: []const u8,
809 darling: []const u8,
810 bad_dl: []const u8,
811 bad_os_or_cpu,
812};
813
814pub const GetExternalExecutorOptions = struct {
815 allow_darling: bool = true,
816 allow_qemu: bool = true,
817 allow_rosetta: bool = true,
818 allow_wasmtime: bool = true,
819 allow_wine: bool = true,
820 qemu_fixes_dl: bool = false,
821 link_libc: bool = false,
822};
823
824/// Return whether or not the given host target is capable of executing natively executables
825/// of the other target.
826pub fn getExternalExecutor(
827 host: NativeTargetInfo,
828 candidate: NativeTargetInfo,
829 options: GetExternalExecutorOptions,
830) Executor {
831 const os_match = host.target.os.tag == candidate.target.os.tag;
832 const cpu_ok = cpu_ok: {
833 if (host.target.cpu.arch == candidate.target.cpu.arch)
834 break :cpu_ok true;
835
836 if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .i386)
837 break :cpu_ok true;
838
839 if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm)
840 break :cpu_ok true;
841
842 if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb)
843 break :cpu_ok true;
844
845 // TODO additionally detect incompatible CPU features.
846 // Note that in some cases the OS kernel will emulate missing CPU features
847 // when an illegal instruction is encountered.
848
849 break :cpu_ok false;
850 };
851
852 var bad_result: Executor = .bad_os_or_cpu;
853
854 if (os_match and cpu_ok) native: {
855 if (options.link_libc) {
856 if (candidate.dynamic_linker.get()) |candidate_dl| {
857 fs.cwd().access(candidate_dl, .{}) catch {
858 bad_result = .{ .bad_dl = candidate_dl };
859 break :native;
860 };
861 }
862 }
863 return .native;
864 }
865
866 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
867 // to emulate the foreign architecture.
868 if (options.allow_rosetta and os_match and
869 host.target.os.tag == .macos and host.target.cpu.arch == .aarch64)
870 {
871 switch (candidate.target.cpu.arch) {
872 .x86_64 => return .rosetta,
873 else => return bad_result,
874 }
875 }
876
877 // If the OS matches, we can use QEMU to emulate a foreign architecture.
878 if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
879 return switch (candidate.target.cpu.arch) {
880 .aarch64 => Executor{ .qemu = "qemu-aarch64" },
881 .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
882 .arm => Executor{ .qemu = "qemu-arm" },
883 .armeb => Executor{ .qemu = "qemu-armeb" },
884 .hexagon => Executor{ .qemu = "qemu-hexagon" },
885 .i386 => Executor{ .qemu = "qemu-i386" },
886 .m68k => Executor{ .qemu = "qemu-m68k" },
887 .mips => Executor{ .qemu = "qemu-mips" },
888 .mipsel => Executor{ .qemu = "qemu-mipsel" },
889 .mips64 => Executor{ .qemu = "qemu-mips64" },
890 .mips64el => Executor{ .qemu = "qemu-mips64el" },
891 .powerpc => Executor{ .qemu = "qemu-ppc" },
892 .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
893 .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
894 .riscv32 => Executor{ .qemu = "qemu-riscv32" },
895 .riscv64 => Executor{ .qemu = "qemu-riscv64" },
896 .s390x => Executor{ .qemu = "qemu-s390x" },
897 .sparc => Executor{ .qemu = "qemu-sparc" },
898 .x86_64 => Executor{ .qemu = "qemu-x86_64" },
899 else => return bad_result,
900 };
901 }
902
903 switch (candidate.target.os.tag) {
904 .windows => {
905 if (options.allow_wine) {
906 switch (candidate.target.cpu.arch.ptrBitWidth()) {
907 32 => return Executor{ .wine = "wine" },
908 64 => return Executor{ .wine = "wine64" },
909 else => return bad_result,
910 }
911 }
912 return bad_result;
913 },
914 .wasi => {
915 if (options.allow_wasmtime) {
916 switch (candidate.target.cpu.arch.ptrBitWidth()) {
917 32 => return Executor{ .wasmtime = "wasmtime" },
918 else => return bad_result,
919 }
920 }
921 return bad_result;
922 },
923 .macos => {
924 if (options.allow_darling) {
925 // This check can be loosened once darling adds a QEMU-based emulation
926 // layer for non-host architectures:
927 // https://github.com/darlinghq/darling/issues/863
928 if (candidate.target.cpu.arch != builtin.cpu.arch) {
929 return bad_result;
930 }
931 return Executor{ .darling = "darling" };
932 }
933 return bad_result;
934 },
935 else => return bad_result,
936 }
937}
src/main.zig+99-21
......@@ -2539,6 +2539,7 @@ fn buildOutputType(
25392539 &comp_destroyed,
25402540 all_args,
25412541 runtime_args_start,
2542 link_libc,
25422543 );
25432544 }
25442545
......@@ -2611,6 +2612,7 @@ fn buildOutputType(
26112612 &comp_destroyed,
26122613 all_args,
26132614 runtime_args_start,
2615 link_libc,
26142616 );
26152617 },
26162618 .update_and_run => {
......@@ -2636,6 +2638,7 @@ fn buildOutputType(
26362638 &comp_destroyed,
26372639 all_args,
26382640 runtime_args_start,
2641 link_libc,
26392642 );
26402643 },
26412644 }
......@@ -2700,6 +2703,7 @@ fn runOrTest(
27002703 comp_destroyed: *bool,
27012704 all_args: []const []const u8,
27022705 runtime_args_start: ?usize,
2706 link_libc: bool,
27032707) !void {
27042708 const exe_loc = emit_bin_loc orelse return;
27052709 const exe_directory = exe_loc.directory orelse comp.bin_file.options.emit.?.directory;
......@@ -2740,7 +2744,7 @@ fn runOrTest(
27402744 if (std.process.can_execv and arg_mode == .run and !watch) {
27412745 // execv releases the locks; no need to destroy the Compilation here.
27422746 const err = std.process.execv(gpa, argv.items);
2743 try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info);
2747 try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);
27442748 const cmd = try argvCmd(arena, argv.items);
27452749 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
27462750 } else {
......@@ -2759,7 +2763,7 @@ fn runOrTest(
27592763 }
27602764
27612765 const term = child.spawnAndWait() catch |err| {
2762 try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info);
2766 try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);
27632767 const cmd = try argvCmd(arena, argv.items);
27642768 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
27652769 };
......@@ -4662,34 +4666,108 @@ fn warnAboutForeignBinaries(
46624666 arena: Allocator,
46634667 arg_mode: ArgMode,
46644668 target_info: std.zig.system.NativeTargetInfo,
4669 link_libc: bool,
46654670) !void {
46664671 const host_cross_target: std.zig.CrossTarget = .{};
46674672 const host_target_info = try detectNativeTargetInfo(gpa, host_cross_target);
46684673
4669 if (!host_target_info.target.canExecBinariesOf(target_info.target)) {
4670 const host_name = try host_target_info.target.zigTriple(arena);
4671 const foreign_name = try target_info.target.zigTriple(arena);
4672 const tip_suffix = switch (arg_mode) {
4673 .zig_test => ". Consider using --test-no-exec or --test-cmd",
4674 else => "",
4675 };
4676 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}){s}", .{
4677 host_name, foreign_name, tip_suffix,
4678 });
4679 return;
4680 }
4681
4682 if (target_info.dynamic_linker.get()) |foreign_dl| {
4683 std.fs.cwd().access(foreign_dl, .{}) catch {
4674 switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
4675 .native => return,
4676 .rosetta => {
4677 const host_name = try host_target_info.target.zigTriple(arena);
4678 const foreign_name = try target_info.target.zigTriple(arena);
4679 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
4680 host_name, foreign_name,
4681 });
4682 },
4683 .qemu => |qemu| {
4684 const host_name = try host_target_info.target.zigTriple(arena);
4685 const foreign_name = try target_info.target.zigTriple(arena);
4686 switch (arg_mode) {
4687 .zig_test => warn(
4688 "the host system ({s}) does not appear to be capable of executing binaries " ++
4689 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
4690 "to run the tests",
4691 .{ host_name, foreign_name, qemu },
4692 ),
4693 else => warn(
4694 "the host system ({s}) does not appear to be capable of executing binaries " ++
4695 "from the target ({s}). Consider using '{s}' to run the binary",
4696 .{ host_name, foreign_name, qemu },
4697 ),
4698 }
4699 },
4700 .wine => |wine| {
4701 const host_name = try host_target_info.target.zigTriple(arena);
4702 const foreign_name = try target_info.target.zigTriple(arena);
4703 switch (arg_mode) {
4704 .zig_test => warn(
4705 "the host system ({s}) does not appear to be capable of executing binaries " ++
4706 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
4707 "to run the tests",
4708 .{ host_name, foreign_name, wine },
4709 ),
4710 else => warn(
4711 "the host system ({s}) does not appear to be capable of executing binaries " ++
4712 "from the target ({s}). Consider using '{s}' to run the binary",
4713 .{ host_name, foreign_name, wine },
4714 ),
4715 }
4716 },
4717 .wasmtime => |wasmtime| {
4718 const host_name = try host_target_info.target.zigTriple(arena);
4719 const foreign_name = try target_info.target.zigTriple(arena);
4720 switch (arg_mode) {
4721 .zig_test => warn(
4722 "the host system ({s}) does not appear to be capable of executing binaries " ++
4723 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
4724 "to run the tests",
4725 .{ host_name, foreign_name, wasmtime },
4726 ),
4727 else => warn(
4728 "the host system ({s}) does not appear to be capable of executing binaries " ++
4729 "from the target ({s}). Consider using '{s}' to run the binary",
4730 .{ host_name, foreign_name, wasmtime },
4731 ),
4732 }
4733 },
4734 .darling => |darling| {
4735 const host_name = try host_target_info.target.zigTriple(arena);
4736 const foreign_name = try target_info.target.zigTriple(arena);
4737 switch (arg_mode) {
4738 .zig_test => warn(
4739 "the host system ({s}) does not appear to be capable of executing binaries " ++
4740 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
4741 "to run the tests",
4742 .{ host_name, foreign_name, darling },
4743 ),
4744 else => warn(
4745 "the host system ({s}) does not appear to be capable of executing binaries " ++
4746 "from the target ({s}). Consider using '{s}' to run the binary",
4747 .{ host_name, foreign_name, darling },
4748 ),
4749 }
4750 },
4751 .bad_dl => |foreign_dl| {
46844752 const host_dl = host_target_info.dynamic_linker.get() orelse "(none)";
46854753 const tip_suffix = switch (arg_mode) {
4686 .zig_test => ", --test-no-exec, or --test-cmd",
4754 .zig_test => ", '--test-no-exec', or '--test-cmd'",
46874755 else => "",
46884756 };
4689 warn("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is located at '{s}', while the target dynamic linker path is '{s}'. Consider using --dynamic-linker{s}", .{
4757 warn("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider using '--dynamic-linker'{s}", .{
46904758 host_dl, foreign_dl, tip_suffix,
46914759 });
4692 return;
4693 };
4760 },
4761 .bad_os_or_cpu => {
4762 const host_name = try host_target_info.target.zigTriple(arena);
4763 const foreign_name = try target_info.target.zigTriple(arena);
4764 const tip_suffix = switch (arg_mode) {
4765 .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
4766 else => "",
4767 };
4768 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}){s}", .{
4769 host_name, foreign_name, tip_suffix,
4770 });
4771 },
46944772 }
46954773}
src/test.zig+9-4
......@@ -611,6 +611,8 @@ pub const TestContext = struct {
611611 }
612612
613613 fn run(self: *TestContext) !void {
614 const host = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, .{});
615
614616 var progress = std.Progress{};
615617 const root_node = try progress.start("compiler", self.cases.items.len);
616618 defer root_node.end();
......@@ -669,6 +671,7 @@ pub const TestContext = struct {
669671 zig_lib_directory,
670672 &thread_pool,
671673 global_cache_directory,
674 host,
672675 ) catch |err| {
673676 fail_count += 1;
674677 print("test '{s}' failed: {s}\n\n", .{ case.name, @errorName(err) });
......@@ -687,6 +690,7 @@ pub const TestContext = struct {
687690 zig_lib_directory: Compilation.Directory,
688691 thread_pool: *ThreadPool,
689692 global_cache_directory: Compilation.Directory,
693 host: std.zig.system.NativeTargetInfo,
690694 ) !void {
691695 const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
692696 const target = target_info.target;
......@@ -882,6 +886,7 @@ pub const TestContext = struct {
882886 .stage1 => true,
883887 else => null,
884888 };
889 const link_libc = case.backend == .llvm;
885890 const comp = try Compilation.create(allocator, .{
886891 .local_cache_directory = zig_cache_directory,
887892 .global_cache_directory = global_cache_directory,
......@@ -903,7 +908,7 @@ pub const TestContext = struct {
903908 .is_native_os = case.target.isNativeOs(),
904909 .is_native_abi = case.target.isNativeAbi(),
905910 .dynamic_linker = target_info.dynamic_linker.get(),
906 .link_libc = case.backend == .llvm,
911 .link_libc = link_libc,
907912 .use_llvm = use_llvm,
908913 .use_stage1 = use_stage1,
909914 .self_exe_path = std.testing.zig_exe_path,
......@@ -1113,7 +1118,7 @@ pub const TestContext = struct {
11131118 // child process.
11141119 const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{s}", .{bin_name});
11151120 if (case.object_format != null and case.object_format.? == .c) {
1116 if (case.target.getExternalExecutor() != .native) {
1121 if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
11171122 // We wouldn't be able to run the compiled C code.
11181123 return; // Pass test.
11191124 }
......@@ -1129,9 +1134,9 @@ pub const TestContext = struct {
11291134 "-lc",
11301135 exe_path,
11311136 });
1132 } else switch (case.target.getExternalExecutor()) {
1137 } else switch (host.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
11331138 .native => try argv.append(exe_path),
1134 .unavailable => return, // Pass test.
1139 .bad_dl, .bad_os_or_cpu => return, // Pass test.
11351140
11361141 .rosetta => if (enable_rosetta) {
11371142 try argv.append(exe_path);