Commit f3edff439e
Changed files (11)
doc/docgen.zig
@@ -1202,6 +1202,7 @@ fn genHtml(
var env_map = try process.getEnvMap(allocator);
try env_map.put("ZIG_DEBUG_COLOR", "1");
+ const host = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);
for (toc.nodes) |node| {
@@ -1424,13 +1425,17 @@ fn genHtml(
var test_args = std.ArrayList([]const u8).init(allocator);
defer test_args.deinit();
- try test_args.appendSlice(&[_][]const u8{ zig_exe, "test", tmp_source_file_name });
+ try test_args.appendSlice(&[_][]const u8{
+ zig_exe, "test", tmp_source_file_name,
+ });
try shell_out.print("$ zig test {s}.zig ", .{code.name});
switch (code.mode) {
.Debug => {},
else => {
- try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
+ try test_args.appendSlice(&[_][]const u8{
+ "-O", @tagName(code.mode),
+ });
try shell_out.print("-O {s} ", .{@tagName(code.mode)});
},
}
@@ -1441,8 +1446,26 @@ fn genHtml(
if (code.target_str) |triple| {
try test_args.appendSlice(&[_][]const u8{ "-target", triple });
try shell_out.print("-target {s} ", .{triple});
+
+ const cross_target = try std.zig.CrossTarget.parse(.{
+ .arch_os_abi = triple,
+ });
+ const target_info = try std.zig.system.NativeTargetInfo.detect(
+ allocator,
+ cross_target,
+ );
+ switch (host.getExternalExecutor(target_info, .{
+ .link_libc = code.link_libc,
+ })) {
+ .native => {},
+ else => {
+ try test_args.appendSlice(&[_][]const u8{"--test-no-exec"});
+ try shell_out.writeAll("--test-no-exec");
+ },
+ }
}
- const result = exec(allocator, &env_map, test_args.items) catch return parseError(tokenizer, code.source_token, "test failed", .{});
+ const result = exec(allocator, &env_map, test_args.items) catch
+ return parseError(tokenizer, code.source_token, "test failed", .{});
const escaped_stderr = try escapeHtml(allocator, result.stderr);
const escaped_stdout = try escapeHtml(allocator, result.stdout);
try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
@@ -1504,9 +1527,7 @@ fn genHtml(
defer test_args.deinit();
try test_args.appendSlice(&[_][]const u8{
- zig_exe,
- "test",
- tmp_source_file_name,
+ zig_exe, "test", tmp_source_file_name,
});
var mode_arg: []const u8 = "";
switch (code.mode) {
doc/langref.html.in
@@ -4763,7 +4763,13 @@ test "noreturn" {
<p>Another use case for {#syntax#}noreturn{#endsyntax#} is the {#syntax#}exit{#endsyntax#} function:</p>
{#code_begin|test|noreturn_from_exit#}
{#target_windows#}
-pub extern "kernel32" fn ExitProcess(exit_code: c_uint) callconv(if (@import("builtin").target.cpu.arch == .i386) .Stdcall else .C) noreturn;
+const std = @import("std");
+const builtin = @import("builtin");
+const native_arch = builtin.cpu.arch;
+const expect = std.testing.expect;
+
+const WINAPI: std.builtin.CallingConvention = if (native_arch == .i386) .Stdcall else .C;
+extern "kernel32" fn ExitProcess(exit_code: c_uint) callconv(WINAPI) noreturn;
test "foo" {
const value = bar() catch ExitProcess(1);
@@ -4774,12 +4780,15 @@ fn bar() anyerror!u32 {
return 1234;
}
-const expect = @import("std").testing.expect;
{#code_end#}
{#header_close#}
+
{#header_open|Functions#}
{#code_begin|test|functions#}
-const expect = @import("std").testing.expect;
+const std = @import("std");
+const builtin = @import("builtin");
+const native_arch = builtin.cpu.arch;
+const expect = std.testing.expect;
// Functions are declared like this
fn add(a: i8, b: i8) i8 {
@@ -4798,7 +4807,8 @@ export fn sub(a: i8, b: i8) i8 { return a - b; }
// at link time, when linking statically, or at runtime, when linking
// dynamically.
// The callconv specifier changes the calling convention of the function.
-extern "kernel32" fn ExitProcess(exit_code: u32) callconv(if (@import("builtin").target.cpu.arch == .i386) .Stdcall else .C) noreturn;
+const WINAPI: std.builtin.CallingConvention = if (native_arch == .i386) .Stdcall else .C;
+extern "kernel32" fn ExitProcess(exit_code: u32) callconv(WINAPI) noreturn;
extern "c" fn atan2(a: f64, b: f64) f64;
// The @setCold builtin tells the optimizer that a function is rarely called.
lib/std/zig/system/NativePaths.zig
@@ -0,0 +1,205 @@
+const std = @import("../../std.zig");
+const builtin = @import("builtin");
+const ArrayList = std.ArrayList;
+const Allocator = std.mem.Allocator;
+const process = std.process;
+const mem = std.mem;
+
+const NativePaths = @This();
+const NativeTargetInfo = std.zig.system.NativeTargetInfo;
+
+include_dirs: ArrayList([:0]u8),
+lib_dirs: ArrayList([:0]u8),
+framework_dirs: ArrayList([:0]u8),
+rpaths: ArrayList([:0]u8),
+warnings: ArrayList([:0]u8),
+
+pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths {
+ const native_target = native_info.target;
+
+ var self: NativePaths = .{
+ .include_dirs = ArrayList([:0]u8).init(allocator),
+ .lib_dirs = ArrayList([:0]u8).init(allocator),
+ .framework_dirs = ArrayList([:0]u8).init(allocator),
+ .rpaths = ArrayList([:0]u8).init(allocator),
+ .warnings = ArrayList([:0]u8).init(allocator),
+ };
+ errdefer self.deinit();
+
+ var is_nix = false;
+ if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
+ defer allocator.free(nix_cflags_compile);
+
+ is_nix = true;
+ var it = mem.tokenize(u8, nix_cflags_compile, " ");
+ while (true) {
+ const word = it.next() orelse break;
+ if (mem.eql(u8, word, "-isystem")) {
+ const include_path = it.next() orelse {
+ try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE");
+ break;
+ };
+ try self.addIncludeDir(include_path);
+ } else {
+ if (mem.startsWith(u8, word, "-frandom-seed=")) {
+ continue;
+ }
+ try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});
+ }
+ }
+ } else |err| switch (err) {
+ error.InvalidUtf8 => {},
+ error.EnvironmentVariableNotFound => {},
+ error.OutOfMemory => |e| return e,
+ }
+ if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
+ defer allocator.free(nix_ldflags);
+
+ is_nix = true;
+ var it = mem.tokenize(u8, nix_ldflags, " ");
+ while (true) {
+ const word = it.next() orelse break;
+ if (mem.eql(u8, word, "-rpath")) {
+ const rpath = it.next() orelse {
+ try self.addWarning("Expected argument after -rpath in NIX_LDFLAGS");
+ break;
+ };
+ try self.addRPath(rpath);
+ } else if (word.len > 2 and word[0] == '-' and word[1] == 'L') {
+ const lib_path = word[2..];
+ try self.addLibDir(lib_path);
+ } else {
+ try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word});
+ break;
+ }
+ }
+ } else |err| switch (err) {
+ error.InvalidUtf8 => {},
+ error.EnvironmentVariableNotFound => {},
+ error.OutOfMemory => |e| return e,
+ }
+ if (is_nix) {
+ return self;
+ }
+
+ if (comptime builtin.target.isDarwin()) {
+ try self.addIncludeDir("/usr/include");
+ try self.addIncludeDir("/usr/local/include");
+
+ try self.addLibDir("/usr/lib");
+ try self.addLibDir("/usr/local/lib");
+
+ try self.addFrameworkDir("/Library/Frameworks");
+ try self.addFrameworkDir("/System/Library/Frameworks");
+
+ return self;
+ }
+
+ if (comptime native_target.os.tag == .solaris) {
+ try self.addLibDir("/usr/lib/64");
+ try self.addLibDir("/usr/local/lib/64");
+ try self.addLibDir("/lib/64");
+
+ try self.addIncludeDir("/usr/include");
+ try self.addIncludeDir("/usr/local/include");
+
+ return self;
+ }
+
+ if (native_target.os.tag != .windows) {
+ const triple = try native_target.linuxTriple(allocator);
+ const qual = native_target.cpu.arch.ptrBitWidth();
+
+ // TODO: $ ld --verbose | grep SEARCH_DIR
+ // the output contains some paths that end with lib64, maybe include them too?
+ // TODO: what is the best possible order of things?
+ // TODO: some of these are suspect and should only be added on some systems. audit needed.
+
+ try self.addIncludeDir("/usr/local/include");
+ try self.addLibDirFmt("/usr/local/lib{d}", .{qual});
+ try self.addLibDir("/usr/local/lib");
+
+ try self.addIncludeDirFmt("/usr/include/{s}", .{triple});
+ try self.addLibDirFmt("/usr/lib/{s}", .{triple});
+
+ try self.addIncludeDir("/usr/include");
+ try self.addLibDirFmt("/lib{d}", .{qual});
+ try self.addLibDir("/lib");
+ try self.addLibDirFmt("/usr/lib{d}", .{qual});
+ try self.addLibDir("/usr/lib");
+
+ // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
+ // zlib.h is in /usr/include (added above)
+ // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
+ try self.addLibDirFmt("/lib/{s}", .{triple});
+ }
+
+ return self;
+}
+
+pub fn deinit(self: *NativePaths) void {
+ deinitArray(&self.include_dirs);
+ deinitArray(&self.lib_dirs);
+ deinitArray(&self.framework_dirs);
+ deinitArray(&self.rpaths);
+ deinitArray(&self.warnings);
+ self.* = undefined;
+}
+
+fn deinitArray(array: *ArrayList([:0]u8)) void {
+ for (array.items) |item| {
+ array.allocator.free(item);
+ }
+ array.deinit();
+}
+
+pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void {
+ return self.appendArray(&self.include_dirs, s);
+}
+
+pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
+ const item = try std.fmt.allocPrintZ(self.include_dirs.allocator, fmt, args);
+ errdefer self.include_dirs.allocator.free(item);
+ try self.include_dirs.append(item);
+}
+
+pub fn addLibDir(self: *NativePaths, s: []const u8) !void {
+ return self.appendArray(&self.lib_dirs, s);
+}
+
+pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
+ const item = try std.fmt.allocPrintZ(self.lib_dirs.allocator, fmt, args);
+ errdefer self.lib_dirs.allocator.free(item);
+ try self.lib_dirs.append(item);
+}
+
+pub fn addWarning(self: *NativePaths, s: []const u8) !void {
+ return self.appendArray(&self.warnings, s);
+}
+
+pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void {
+ return self.appendArray(&self.framework_dirs, s);
+}
+
+pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
+ const item = try std.fmt.allocPrintZ(self.framework_dirs.allocator, fmt, args);
+ errdefer self.framework_dirs.allocator.free(item);
+ try self.framework_dirs.append(item);
+}
+
+pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
+ const item = try std.fmt.allocPrintZ(self.warnings.allocator, fmt, args);
+ errdefer self.warnings.allocator.free(item);
+ try self.warnings.append(item);
+}
+
+pub fn addRPath(self: *NativePaths, s: []const u8) !void {
+ return self.appendArray(&self.rpaths, s);
+}
+
+fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void {
+ _ = self;
+ const item = try array.allocator.dupeZ(u8, s);
+ errdefer array.allocator.free(item);
+ try array.append(item);
+}
lib/std/zig/system/NativeTargetInfo.zig
@@ -0,0 +1,937 @@
+const std = @import("../../std.zig");
+const builtin = @import("builtin");
+const mem = std.mem;
+const assert = std.debug.assert;
+const fs = std.fs;
+const elf = std.elf;
+const native_endian = builtin.cpu.arch.endian();
+
+const NativeTargetInfo = @This();
+const Target = std.Target;
+const Allocator = std.mem.Allocator;
+const CrossTarget = std.zig.CrossTarget;
+const windows = std.zig.system.windows;
+const darwin = std.zig.system.darwin;
+const linux = std.zig.system.linux;
+
+target: Target,
+dynamic_linker: DynamicLinker = DynamicLinker{},
+
+pub const DynamicLinker = Target.DynamicLinker;
+
+pub const DetectError = error{
+ OutOfMemory,
+ FileSystem,
+ SystemResources,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ DeviceBusy,
+ OSVersionDetectionFail,
+};
+
+/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
+/// natively, which should be standard or default, and which are provided explicitly, this function
+/// resolves the native components by detecting the native system, and then resolves standard/default parts
+/// relative to that.
+/// Any resources this function allocates are released before returning, and so there is no
+/// deinitialization method.
+/// TODO Remove the Allocator requirement from this function.
+pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
+ var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
+ if (cross_target.os_tag == null) {
+ switch (builtin.target.os.tag) {
+ .linux => {
+ const uts = std.os.uname();
+ const release = mem.sliceTo(&uts.release, 0);
+ // The release field sometimes has a weird format,
+ // `Version.parse` will attempt to find some meaningful interpretation.
+ if (std.builtin.Version.parse(release)) |ver| {
+ os.version_range.linux.range.min = ver;
+ os.version_range.linux.range.max = ver;
+ } else |err| switch (err) {
+ error.Overflow => {},
+ error.InvalidCharacter => {},
+ error.InvalidVersion => {},
+ }
+ },
+ .solaris => {
+ const uts = std.os.uname();
+ const release = mem.sliceTo(&uts.release, 0);
+ if (std.builtin.Version.parse(release)) |ver| {
+ os.version_range.semver.min = ver;
+ os.version_range.semver.max = ver;
+ } else |err| switch (err) {
+ error.Overflow => {},
+ error.InvalidCharacter => {},
+ error.InvalidVersion => {},
+ }
+ },
+ .windows => {
+ const detected_version = windows.detectRuntimeVersion();
+ os.version_range.windows.min = detected_version;
+ os.version_range.windows.max = detected_version;
+ },
+ .macos => try darwin.macos.detect(&os),
+ .freebsd, .netbsd, .dragonfly => {
+ const key = switch (builtin.target.os.tag) {
+ .freebsd => "kern.osreldate",
+ .netbsd, .dragonfly => "kern.osrevision",
+ else => unreachable,
+ };
+ var value: u32 = undefined;
+ var len: usize = @sizeOf(@TypeOf(value));
+
+ std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
+ error.NameTooLong => unreachable, // constant, known good value
+ error.PermissionDenied => unreachable, // only when setting values,
+ error.SystemResources => unreachable, // memory already on the stack
+ error.UnknownName => unreachable, // constant, known good value
+ error.Unexpected => return error.OSVersionDetectionFail,
+ };
+
+ switch (builtin.target.os.tag) {
+ .freebsd => {
+ // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
+ // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
+ // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
+ // e.g. 492101 = 4.11-STABLE = 4.(9+2)
+ const major = value / 100_000;
+ const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
+ const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
+ const patch = value % 1_000;
+ os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
+ os.version_range.semver.max = os.version_range.semver.min;
+ },
+ .netbsd => {
+ // #define __NetBSD_Version__ MMmmrrpp00
+ //
+ // M = major version
+ // m = minor version; a minor number of 99 indicates current.
+ // r = 0 (*)
+ // p = patchlevel
+ const major = value / 100_000_000;
+ const minor = value % 100_000_000 / 1_000_000;
+ const patch = value % 10_000 / 100;
+ os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
+ os.version_range.semver.max = os.version_range.semver.min;
+ },
+ .dragonfly => {
+ // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
+ // flat base10 format: Mmmmpp
+ // M = major
+ // m = minor; odd-numbers indicate current dev branch
+ // p = patch
+ const major = value / 100_000;
+ const minor = value % 100_000 / 100;
+ const patch = value % 100;
+ os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
+ os.version_range.semver.max = os.version_range.semver.min;
+ },
+ else => unreachable,
+ }
+ },
+ .openbsd => {
+ const mib: [2]c_int = [_]c_int{
+ std.os.CTL.KERN,
+ std.os.KERN.OSRELEASE,
+ };
+ var buf: [64]u8 = undefined;
+ var len: usize = buf.len;
+
+ std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
+ error.NameTooLong => unreachable, // constant, known good value
+ error.PermissionDenied => unreachable, // only when setting values,
+ error.SystemResources => unreachable, // memory already on the stack
+ error.UnknownName => unreachable, // constant, known good value
+ error.Unexpected => return error.OSVersionDetectionFail,
+ };
+
+ if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| {
+ os.version_range.semver.min = ver;
+ os.version_range.semver.max = ver;
+ } else |_| {
+ return error.OSVersionDetectionFail;
+ }
+ },
+ else => {
+ // Unimplemented, fall back to default version range.
+ },
+ }
+ }
+
+ if (cross_target.os_version_min) |min| switch (min) {
+ .none => {},
+ .semver => |semver| switch (cross_target.getOsTag()) {
+ .linux => os.version_range.linux.range.min = semver,
+ else => os.version_range.semver.min = semver,
+ },
+ .windows => |win_ver| os.version_range.windows.min = win_ver,
+ };
+
+ if (cross_target.os_version_max) |max| switch (max) {
+ .none => {},
+ .semver => |semver| switch (cross_target.getOsTag()) {
+ .linux => os.version_range.linux.range.max = semver,
+ else => os.version_range.semver.max = semver,
+ },
+ .windows => |win_ver| os.version_range.windows.max = win_ver,
+ };
+
+ if (cross_target.glibc_version) |glibc| {
+ assert(cross_target.isGnuLibC());
+ os.version_range.linux.glibc = glibc;
+ }
+
+ // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
+ // native CPU architecture as being different than the current target), we use this:
+ const cpu_arch = cross_target.getCpuArch();
+
+ var cpu = switch (cross_target.cpu_model) {
+ .native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target),
+ .baseline => Target.Cpu.baseline(cpu_arch),
+ .determined_by_cpu_arch => if (cross_target.cpu_arch == null)
+ detectNativeCpuAndFeatures(cpu_arch, os, cross_target)
+ else
+ Target.Cpu.baseline(cpu_arch),
+ .explicit => |model| model.toCpu(cpu_arch),
+ } orelse backup_cpu_detection: {
+ break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
+ };
+ var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
+ // For x86, we need to populate some CPU feature flags depending on architecture
+ // and mode:
+ // * 16bit_mode => if the abi is code16
+ // * 32bit_mode => if the arch is i386
+ // However, the "mode" flags can be used as overrides, so if the user explicitly
+ // sets one of them, that takes precedence.
+ switch (cpu_arch) {
+ .i386 => {
+ if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{
+ .@"16bit_mode", .@"32bit_mode",
+ })) {
+ switch (result.target.abi) {
+ .code16 => result.target.cpu.features.addFeature(
+ @enumToInt(std.Target.x86.Feature.@"16bit_mode"),
+ ),
+ else => result.target.cpu.features.addFeature(
+ @enumToInt(std.Target.x86.Feature.@"32bit_mode"),
+ ),
+ }
+ }
+ },
+ .arm, .armeb => {
+ // XXX What do we do if the target has the noarm feature?
+ // What do we do if the user specifies +thumb_mode?
+ },
+ .thumb, .thumbeb => {
+ result.target.cpu.features.addFeature(
+ @enumToInt(std.Target.arm.Feature.thumb_mode),
+ );
+ },
+ else => {},
+ }
+ cross_target.updateCpuFeatures(&result.target.cpu.features);
+ return result;
+}
+
+/// First we attempt to use the executable's own binary. If it is dynamically
+/// linked, then it should answer both the C ABI question and the dynamic linker question.
+/// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
+/// we fall back to the defaults.
+/// TODO Remove the Allocator requirement from this function.
+fn detectAbiAndDynamicLinker(
+ allocator: Allocator,
+ cpu: Target.Cpu,
+ os: Target.Os,
+ cross_target: CrossTarget,
+) DetectError!NativeTargetInfo {
+ const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
+ const is_linux = builtin.target.os.tag == .linux;
+ const have_all_info = cross_target.dynamic_linker.get() != null and
+ cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
+ const os_is_non_native = cross_target.os_tag != null;
+ if (!native_target_has_ld or have_all_info or os_is_non_native) {
+ return defaultAbiAndDynamicLinker(cpu, os, cross_target);
+ }
+ if (cross_target.abi) |abi| {
+ if (abi.isMusl()) {
+ // musl implies static linking.
+ return defaultAbiAndDynamicLinker(cpu, os, cross_target);
+ }
+ }
+ // The current target's ABI cannot be relied on for this. For example, we may build the zig
+ // compiler for target riscv64-linux-musl and provide a tarball for users to download.
+ // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
+ // and supported by Zig. But that means that we must detect the system ABI here rather than
+ // relying on `builtin.target`.
+ const all_abis = comptime blk: {
+ assert(@enumToInt(Target.Abi.none) == 0);
+ const fields = std.meta.fields(Target.Abi)[1..];
+ var array: [fields.len]Target.Abi = undefined;
+ inline for (fields) |field, i| {
+ array[i] = @field(Target.Abi, field.name);
+ }
+ break :blk array;
+ };
+ var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
+ var ld_info_list_len: usize = 0;
+
+ for (all_abis) |abi| {
+ // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
+ // skip adding it to `ld_info_list`.
+ const target: Target = .{
+ .cpu = cpu,
+ .os = os,
+ .abi = abi,
+ };
+ const ld = target.standardDynamicLinkerPath();
+ if (ld.get() == null) continue;
+
+ ld_info_list_buffer[ld_info_list_len] = .{
+ .ld = ld,
+ .abi = abi,
+ };
+ ld_info_list_len += 1;
+ }
+ const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
+
+ // Best case scenario: the executable is dynamically linked, and we can iterate
+ // over our own shared objects and find a dynamic linker.
+ self_exe: {
+ const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
+ defer {
+ for (lib_paths) |lib_path| {
+ allocator.free(lib_path);
+ }
+ allocator.free(lib_paths);
+ }
+
+ var found_ld_info: LdInfo = undefined;
+ var found_ld_path: [:0]const u8 = undefined;
+
+ // Look for dynamic linker.
+ // This is O(N^M) but typical case here is N=2 and M=10.
+ find_ld: for (lib_paths) |lib_path| {
+ for (ld_info_list) |ld_info| {
+ const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
+ if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
+ found_ld_info = ld_info;
+ found_ld_path = lib_path;
+ break :find_ld;
+ }
+ }
+ } else break :self_exe;
+
+ // Look for glibc version.
+ var os_adjusted = os;
+ if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and
+ cross_target.glibc_version == null)
+ {
+ for (lib_paths) |lib_path| {
+ if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
+ os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
+ error.UnrecognizedGnuLibCFileName => continue,
+ error.InvalidGnuLibCVersion => continue,
+ error.GnuLibCVersionUnavailable => continue,
+ else => |e| return e,
+ };
+ break;
+ }
+ }
+ }
+
+ var result: NativeTargetInfo = .{
+ .target = .{
+ .cpu = cpu,
+ .os = os_adjusted,
+ .abi = cross_target.abi orelse found_ld_info.abi,
+ },
+ .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
+ DynamicLinker.init(found_ld_path)
+ else
+ cross_target.dynamic_linker,
+ };
+ return result;
+ }
+
+ const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) {
+ error.NoSpaceLeft => unreachable,
+ error.NameTooLong => unreachable,
+ error.PathAlreadyExists => unreachable,
+ error.SharingViolation => unreachable,
+ error.InvalidUtf8 => unreachable,
+ error.BadPathName => unreachable,
+ error.PipeBusy => unreachable,
+ error.FileLocksNotSupported => unreachable,
+ error.WouldBlock => unreachable,
+
+ error.IsDir,
+ error.NotDir,
+ error.AccessDenied,
+ error.NoDevice,
+ error.FileNotFound,
+ error.FileTooBig,
+ error.Unexpected,
+ => return defaultAbiAndDynamicLinker(cpu, os, cross_target),
+
+ else => |e| return e,
+ };
+ defer env_file.close();
+
+ // If Zig is statically linked, such as via distributed binary static builds, the above
+ // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
+ // Since that path is hard-coded into the shebang line of many portable scripts, it's a
+ // reasonably reliable path to check for.
+ return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
+ error.FileSystem,
+ error.SystemResources,
+ error.SymLinkLoop,
+ error.ProcessFdQuotaExceeded,
+ error.SystemFdQuotaExceeded,
+ => |e| return e,
+
+ error.UnableToReadElfFile,
+ error.InvalidElfClass,
+ error.InvalidElfVersion,
+ error.InvalidElfEndian,
+ error.InvalidElfFile,
+ error.InvalidElfMagic,
+ error.Unexpected,
+ error.UnexpectedEndOfFile,
+ error.NameTooLong,
+ // Finally, we fall back on the standard path.
+ => defaultAbiAndDynamicLinker(cpu, os, cross_target),
+ };
+}
+
+const glibc_so_basename = "libc.so.6";
+
+fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
+ var link_buf: [std.os.PATH_MAX]u8 = undefined;
+ const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {
+ error.AccessDenied => return error.GnuLibCVersionUnavailable,
+ error.FileSystem => return error.FileSystem,
+ error.SymLinkLoop => return error.SymLinkLoop,
+ error.NameTooLong => unreachable,
+ error.NotLink => return error.GnuLibCVersionUnavailable,
+ error.FileNotFound => return error.GnuLibCVersionUnavailable,
+ error.SystemResources => return error.SystemResources,
+ error.NotDir => return error.GnuLibCVersionUnavailable,
+ error.Unexpected => return error.GnuLibCVersionUnavailable,
+ error.InvalidUtf8 => unreachable, // Windows only
+ error.BadPathName => unreachable, // Windows only
+ error.UnsupportedReparsePointType => unreachable, // Windows only
+ };
+ return glibcVerFromLinkName(link_name);
+}
+
+fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version {
+ // example: "libc-2.3.4.so"
+ // example: "libc-2.27.so"
+ const prefix = "libc-";
+ const suffix = ".so";
+ if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
+ return error.UnrecognizedGnuLibCFileName;
+ }
+ // chop off "libc-" and ".so"
+ const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
+ return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
+ error.Overflow => return error.InvalidGnuLibCVersion,
+ error.InvalidCharacter => return error.InvalidGnuLibCVersion,
+ error.InvalidVersion => return error.InvalidGnuLibCVersion,
+ };
+}
+
+pub const AbiAndDynamicLinkerFromFileError = error{
+ FileSystem,
+ SystemResources,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ UnableToReadElfFile,
+ InvalidElfClass,
+ InvalidElfVersion,
+ InvalidElfEndian,
+ InvalidElfFile,
+ InvalidElfMagic,
+ Unexpected,
+ UnexpectedEndOfFile,
+ NameTooLong,
+};
+
+pub fn abiAndDynamicLinkerFromFile(
+ file: fs.File,
+ cpu: Target.Cpu,
+ os: Target.Os,
+ ld_info_list: []const LdInfo,
+ cross_target: CrossTarget,
+) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
+ var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
+ _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
+ const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
+ const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
+ if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
+ const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
+ elf.ELFDATA2LSB => .Little,
+ elf.ELFDATA2MSB => .Big,
+ else => return error.InvalidElfEndian,
+ };
+ const need_bswap = elf_endian != native_endian;
+ if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
+
+ const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
+ elf.ELFCLASS32 => false,
+ elf.ELFCLASS64 => true,
+ else => return error.InvalidElfClass,
+ };
+ var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
+ const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
+ const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
+
+ var result: NativeTargetInfo = .{
+ .target = .{
+ .cpu = cpu,
+ .os = os,
+ .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
+ },
+ .dynamic_linker = cross_target.dynamic_linker,
+ };
+ var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
+ const look_for_ld = cross_target.dynamic_linker.get() == null;
+
+ var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
+ if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
+
+ var ph_i: u16 = 0;
+ while (ph_i < phnum) {
+ // Reserve some bytes so that we can deref the 64-bit struct fields
+ // even when the ELF file is 32-bits.
+ const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
+ const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
+ var ph_buf_i: usize = 0;
+ while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
+ ph_i += 1;
+ phoff += phentsize;
+ ph_buf_i += phentsize;
+ }) {
+ const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
+ const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
+ const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
+ switch (p_type) {
+ elf.PT_INTERP => if (look_for_ld) {
+ const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
+ const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
+ if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
+ const filesz = @intCast(usize, p_filesz);
+ _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
+ // PT_INTERP includes a null byte in filesz.
+ const len = filesz - 1;
+ // dynamic_linker.max_byte is "max", not "len".
+ // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
+ result.dynamic_linker.max_byte = @intCast(u8, len - 1);
+
+ // Use it to determine ABI.
+ const full_ld_path = result.dynamic_linker.buffer[0..len];
+ for (ld_info_list) |ld_info| {
+ const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
+ if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
+ result.target.abi = ld_info.abi;
+ break;
+ }
+ }
+ },
+ // We only need this for detecting glibc version.
+ elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
+ cross_target.glibc_version == null)
+ {
+ var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
+ const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
+ const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
+ const dyn_num = p_filesz / dyn_size;
+ var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
+ var dyn_i: usize = 0;
+ dyn: while (dyn_i < dyn_num) {
+ // Reserve some bytes so that we can deref the 64-bit struct fields
+ // even when the ELF file is 32-bits.
+ const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
+ const dyn_read_byte_len = try preadMin(
+ file,
+ dyn_buf[0 .. dyn_buf.len - dyn_reserve],
+ dyn_off,
+ dyn_size,
+ );
+ var dyn_buf_i: usize = 0;
+ while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
+ dyn_i += 1;
+ dyn_off += dyn_size;
+ dyn_buf_i += dyn_size;
+ }) {
+ const dyn32 = @ptrCast(
+ *elf.Elf32_Dyn,
+ @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
+ );
+ const dyn64 = @ptrCast(
+ *elf.Elf64_Dyn,
+ @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
+ );
+ const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
+ const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
+ if (tag == elf.DT_RUNPATH) {
+ rpath_offset = val;
+ break :dyn;
+ }
+ }
+ }
+ },
+ else => continue,
+ }
+ }
+ }
+
+ if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) {
+ if (rpath_offset) |rpoff| {
+ const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
+
+ var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
+ const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
+ const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
+
+ var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
+ if (sh_buf.len < shentsize) return error.InvalidElfFile;
+
+ _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
+ const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
+ const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
+ const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
+ const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
+ var strtab_buf: [4096:0]u8 = undefined;
+ const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
+ const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
+ const shstrtab = strtab_buf[0..shstrtab_read_len];
+
+ const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
+ var sh_i: u16 = 0;
+ const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
+ // Reserve some bytes so that we can deref the 64-bit struct fields
+ // even when the ELF file is 32-bits.
+ const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
+ const sh_read_byte_len = try preadMin(
+ file,
+ sh_buf[0 .. sh_buf.len - sh_reserve],
+ shoff,
+ shentsize,
+ );
+ var sh_buf_i: usize = 0;
+ while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
+ sh_i += 1;
+ shoff += shentsize;
+ sh_buf_i += shentsize;
+ }) {
+ const sh32 = @ptrCast(
+ *elf.Elf32_Shdr,
+ @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
+ );
+ const sh64 = @ptrCast(
+ *elf.Elf64_Shdr,
+ @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
+ );
+ const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
+ // TODO this pointer cast should not be necessary
+ const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
+ if (mem.eql(u8, sh_name, ".dynstr")) {
+ break :find_dyn_str .{
+ .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
+ .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
+ };
+ }
+ }
+ } else null;
+
+ if (dynstr) |ds| {
+ const strtab_len = std.math.min(ds.size, strtab_buf.len);
+ const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len);
+ const strtab = strtab_buf[0..strtab_read_len];
+ // TODO this pointer cast should not be necessary
+ const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) {
+ error.Overflow => return error.InvalidElfFile,
+ };
+ const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0);
+ var it = mem.tokenize(u8, rpath_list, ":");
+ while (it.next()) |rpath| {
+ var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
+ error.NameTooLong => unreachable,
+ error.InvalidUtf8 => unreachable,
+ error.BadPathName => unreachable,
+ error.DeviceBusy => unreachable,
+
+ error.FileNotFound,
+ error.NotDir,
+ error.AccessDenied,
+ error.NoDevice,
+ => continue,
+
+ error.ProcessFdQuotaExceeded,
+ error.SystemFdQuotaExceeded,
+ error.SystemResources,
+ error.SymLinkLoop,
+ error.Unexpected,
+ => |e| return e,
+ };
+ defer dir.close();
+
+ var link_buf: [std.os.PATH_MAX]u8 = undefined;
+ const link_name = std.os.readlinkatZ(
+ dir.fd,
+ glibc_so_basename,
+ &link_buf,
+ ) catch |err| switch (err) {
+ error.NameTooLong => unreachable,
+ error.InvalidUtf8 => unreachable, // Windows only
+ error.BadPathName => unreachable, // Windows only
+ error.UnsupportedReparsePointType => unreachable, // Windows only
+
+ error.AccessDenied,
+ error.FileNotFound,
+ error.NotLink,
+ error.NotDir,
+ => continue,
+
+ error.SystemResources,
+ error.FileSystem,
+ error.SymLinkLoop,
+ error.Unexpected,
+ => |e| return e,
+ };
+ result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
+ link_name,
+ ) catch |err| switch (err) {
+ error.UnrecognizedGnuLibCFileName,
+ error.InvalidGnuLibCVersion,
+ => continue,
+ };
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
+ var i: usize = 0;
+ while (i < min_read_len) {
+ const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
+ error.OperationAborted => unreachable, // Windows-only
+ error.WouldBlock => unreachable, // Did not request blocking mode
+ error.NotOpenForReading => unreachable,
+ error.SystemResources => return error.SystemResources,
+ error.IsDir => return error.UnableToReadElfFile,
+ error.BrokenPipe => return error.UnableToReadElfFile,
+ error.Unseekable => return error.UnableToReadElfFile,
+ error.ConnectionResetByPeer => return error.UnableToReadElfFile,
+ error.ConnectionTimedOut => return error.UnableToReadElfFile,
+ error.Unexpected => return error.Unexpected,
+ error.InputOutput => return error.FileSystem,
+ error.AccessDenied => return error.Unexpected,
+ };
+ if (len == 0) return error.UnexpectedEndOfFile;
+ i += len;
+ }
+ return i;
+}
+
+fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
+ const target: Target = .{
+ .cpu = cpu,
+ .os = os,
+ .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
+ };
+ return NativeTargetInfo{
+ .target = target,
+ .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
+ target.standardDynamicLinkerPath()
+ else
+ cross_target.dynamic_linker,
+ };
+}
+
+pub const LdInfo = struct {
+ ld: DynamicLinker,
+ abi: Target.Abi,
+};
+
+pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
+ if (is_64) {
+ if (need_bswap) {
+ return @byteSwap(@TypeOf(int_64), int_64);
+ } else {
+ return int_64;
+ }
+ } else {
+ if (need_bswap) {
+ return @byteSwap(@TypeOf(int_32), int_32);
+ } else {
+ return int_32;
+ }
+ }
+}
+
+fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
+ // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
+ // although it is a runtime value, is guaranteed to be one of the architectures in the set
+ // of the respective switch prong.
+ switch (builtin.cpu.arch) {
+ .x86_64, .i386 => {
+ return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
+ },
+ else => {},
+ }
+
+ switch (builtin.os.tag) {
+ .linux => return linux.detectNativeCpuAndFeatures(),
+ .macos => return darwin.macos.detectNativeCpuAndFeatures(),
+ else => {},
+ }
+
+ // This architecture does not have CPU model & feature detection yet.
+ // See https://github.com/ziglang/zig/issues/4591
+ return null;
+}
+
+pub const Executor = union(enum) {
+ native,
+ rosetta,
+ qemu: []const u8,
+ wine: []const u8,
+ wasmtime: []const u8,
+ darling: []const u8,
+ bad_dl: []const u8,
+ bad_os_or_cpu,
+};
+
+pub const GetExternalExecutorOptions = struct {
+ allow_darling: bool = true,
+ allow_qemu: bool = true,
+ allow_rosetta: bool = true,
+ allow_wasmtime: bool = true,
+ allow_wine: bool = true,
+ qemu_fixes_dl: bool = false,
+ link_libc: bool = false,
+};
+
+/// Return whether or not the given host target is capable of executing natively executables
+/// of the other target.
+pub fn getExternalExecutor(
+ host: NativeTargetInfo,
+ candidate: NativeTargetInfo,
+ options: GetExternalExecutorOptions,
+) Executor {
+ const os_match = host.target.os.tag == candidate.target.os.tag;
+ const cpu_ok = cpu_ok: {
+ if (host.target.cpu.arch == candidate.target.cpu.arch)
+ break :cpu_ok true;
+
+ if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .i386)
+ break :cpu_ok true;
+
+ if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm)
+ break :cpu_ok true;
+
+ if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb)
+ break :cpu_ok true;
+
+ // TODO additionally detect incompatible CPU features.
+ // Note that in some cases the OS kernel will emulate missing CPU features
+ // when an illegal instruction is encountered.
+
+ break :cpu_ok false;
+ };
+
+ var bad_result: Executor = .bad_os_or_cpu;
+
+ if (os_match and cpu_ok) native: {
+ if (options.link_libc) {
+ if (candidate.dynamic_linker.get()) |candidate_dl| {
+ fs.cwd().access(candidate_dl, .{}) catch {
+ bad_result = .{ .bad_dl = candidate_dl };
+ break :native;
+ };
+ }
+ }
+ return .native;
+ }
+
+ // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
+ // to emulate the foreign architecture.
+ if (options.allow_rosetta and os_match and
+ host.target.os.tag == .macos and host.target.cpu.arch == .aarch64)
+ {
+ switch (candidate.target.cpu.arch) {
+ .x86_64 => return .rosetta,
+ else => return bad_result,
+ }
+ }
+
+ // If the OS matches, we can use QEMU to emulate a foreign architecture.
+ if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
+ return switch (candidate.target.cpu.arch) {
+ .aarch64 => Executor{ .qemu = "qemu-aarch64" },
+ .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
+ .arm => Executor{ .qemu = "qemu-arm" },
+ .armeb => Executor{ .qemu = "qemu-armeb" },
+ .hexagon => Executor{ .qemu = "qemu-hexagon" },
+ .i386 => Executor{ .qemu = "qemu-i386" },
+ .m68k => Executor{ .qemu = "qemu-m68k" },
+ .mips => Executor{ .qemu = "qemu-mips" },
+ .mipsel => Executor{ .qemu = "qemu-mipsel" },
+ .mips64 => Executor{ .qemu = "qemu-mips64" },
+ .mips64el => Executor{ .qemu = "qemu-mips64el" },
+ .powerpc => Executor{ .qemu = "qemu-ppc" },
+ .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
+ .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
+ .riscv32 => Executor{ .qemu = "qemu-riscv32" },
+ .riscv64 => Executor{ .qemu = "qemu-riscv64" },
+ .s390x => Executor{ .qemu = "qemu-s390x" },
+ .sparc => Executor{ .qemu = "qemu-sparc" },
+ .x86_64 => Executor{ .qemu = "qemu-x86_64" },
+ else => return bad_result,
+ };
+ }
+
+ switch (candidate.target.os.tag) {
+ .windows => {
+ if (options.allow_wine) {
+ switch (candidate.target.cpu.arch.ptrBitWidth()) {
+ 32 => return Executor{ .wine = "wine" },
+ 64 => return Executor{ .wine = "wine64" },
+ else => return bad_result,
+ }
+ }
+ return bad_result;
+ },
+ .wasi => {
+ if (options.allow_wasmtime) {
+ switch (candidate.target.cpu.arch.ptrBitWidth()) {
+ 32 => return Executor{ .wasmtime = "wasmtime" },
+ else => return bad_result,
+ }
+ }
+ return bad_result;
+ },
+ .macos => {
+ if (options.allow_darling) {
+ // This check can be loosened once darling adds a QEMU-based emulation
+ // layer for non-host architectures:
+ // https://github.com/darlinghq/darling/issues/863
+ if (candidate.target.cpu.arch != builtin.cpu.arch) {
+ return bad_result;
+ }
+ return Executor{ .darling = "darling" };
+ }
+ return bad_result;
+ },
+ else => return bad_result,
+ }
+}
lib/std/zig/CrossTarget.zig
@@ -610,95 +610,6 @@ pub fn vcpkgTriplet(self: CrossTarget, allocator: mem.Allocator, linkage: VcpkgL
return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix });
}
-pub const Executor = union(enum) {
- native,
- rosetta,
- qemu: []const u8,
- wine: []const u8,
- wasmtime: []const u8,
- darling: []const u8,
- unavailable,
-};
-
-/// Note that even a `CrossTarget` which returns `false` for `isNative` could still be natively executed.
-/// For example `-target arm-native` running on an aarch64 host.
-pub fn getExternalExecutor(self: CrossTarget) Executor {
- const cpu_arch = self.getCpuArch();
- const os_tag = self.getOsTag();
- const os_match = os_tag == builtin.os.tag;
-
- // If the OS and CPU arch match, the binary can be considered native.
- // TODO additionally match the CPU features. This `getExternalExecutor` function should
- // be moved to std.Target and match any chosen target against the native target.
- if (os_match and cpu_arch == builtin.cpu.arch) {
- // However, we also need to verify that the dynamic linker path is valid.
- if (self.os_tag == null) {
- return .native;
- }
- // TODO here we call toTarget, a deprecated function, because of the above TODO about moving
- // this code to std.Target.
- const opt_dl = self.dynamic_linker.get() orelse self.toTarget().standardDynamicLinkerPath().get();
- if (opt_dl) |dl| blk: {
- std.fs.cwd().access(dl, .{}) catch break :blk;
- return .native;
- }
- }
- // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
- // to emulate the foreign architecture.
- if (os_match and os_tag == .macos and builtin.cpu.arch == .aarch64) {
- return switch (cpu_arch) {
- .x86_64 => .rosetta,
- else => .unavailable,
- };
- }
-
- // If the OS matches, we can use QEMU to emulate a foreign architecture.
- if (os_match) {
- return switch (cpu_arch) {
- .aarch64 => Executor{ .qemu = "qemu-aarch64" },
- .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
- .arm => Executor{ .qemu = "qemu-arm" },
- .armeb => Executor{ .qemu = "qemu-armeb" },
- .i386 => Executor{ .qemu = "qemu-i386" },
- .mips => Executor{ .qemu = "qemu-mips" },
- .mipsel => Executor{ .qemu = "qemu-mipsel" },
- .mips64 => Executor{ .qemu = "qemu-mips64" },
- .mips64el => Executor{ .qemu = "qemu-mips64el" },
- .powerpc => Executor{ .qemu = "qemu-ppc" },
- .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
- .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
- .riscv32 => Executor{ .qemu = "qemu-riscv32" },
- .riscv64 => Executor{ .qemu = "qemu-riscv64" },
- .s390x => Executor{ .qemu = "qemu-s390x" },
- .sparc => Executor{ .qemu = "qemu-sparc" },
- .x86_64 => Executor{ .qemu = "qemu-x86_64" },
- else => return .unavailable,
- };
- }
-
- switch (os_tag) {
- .windows => switch (cpu_arch.ptrBitWidth()) {
- 32 => return Executor{ .wine = "wine" },
- 64 => return Executor{ .wine = "wine64" },
- else => return .unavailable,
- },
- .wasi => switch (cpu_arch.ptrBitWidth()) {
- 32 => return Executor{ .wasmtime = "wasmtime" },
- else => return .unavailable,
- },
- .macos => {
- // TODO loosen this check once upstream adds QEMU-based emulation
- // layer for non-host architectures:
- // https://github.com/darlinghq/darling/issues/863
- if (cpu_arch != builtin.cpu.arch) {
- return .unavailable;
- }
- return Executor{ .darling = "darling" };
- },
- else => return .unavailable,
- }
-}
-
pub fn isGnuLibC(self: CrossTarget) bool {
return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi());
}
lib/std/zig/system.zig
@@ -1,1007 +1,15 @@
-const std = @import("../std.zig");
-const builtin = @import("builtin");
-const elf = std.elf;
-const mem = std.mem;
-const fs = std.fs;
-const Allocator = std.mem.Allocator;
-const ArrayList = std.ArrayList;
-const assert = std.debug.assert;
-const process = std.process;
-const Target = std.Target;
-const CrossTarget = std.zig.CrossTarget;
-const native_endian = builtin.cpu.arch.endian();
-const linux = @import("system/linux.zig");
+pub const NativePaths = @import("system/NativePaths.zig");
+pub const NativeTargetInfo = @import("system/NativeTargetInfo.zig");
+
pub const windows = @import("system/windows.zig");
pub const darwin = @import("system/darwin.zig");
-
-pub const NativePaths = struct {
- include_dirs: ArrayList([:0]u8),
- lib_dirs: ArrayList([:0]u8),
- framework_dirs: ArrayList([:0]u8),
- rpaths: ArrayList([:0]u8),
- warnings: ArrayList([:0]u8),
-
- pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths {
- const native_target = native_info.target;
-
- var self: NativePaths = .{
- .include_dirs = ArrayList([:0]u8).init(allocator),
- .lib_dirs = ArrayList([:0]u8).init(allocator),
- .framework_dirs = ArrayList([:0]u8).init(allocator),
- .rpaths = ArrayList([:0]u8).init(allocator),
- .warnings = ArrayList([:0]u8).init(allocator),
- };
- errdefer self.deinit();
-
- var is_nix = false;
- if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
- defer allocator.free(nix_cflags_compile);
-
- is_nix = true;
- var it = mem.tokenize(u8, nix_cflags_compile, " ");
- while (true) {
- const word = it.next() orelse break;
- if (mem.eql(u8, word, "-isystem")) {
- const include_path = it.next() orelse {
- try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE");
- break;
- };
- try self.addIncludeDir(include_path);
- } else {
- if (mem.startsWith(u8, word, "-frandom-seed=")) {
- continue;
- }
- try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});
- }
- }
- } else |err| switch (err) {
- error.InvalidUtf8 => {},
- error.EnvironmentVariableNotFound => {},
- error.OutOfMemory => |e| return e,
- }
- if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
- defer allocator.free(nix_ldflags);
-
- is_nix = true;
- var it = mem.tokenize(u8, nix_ldflags, " ");
- while (true) {
- const word = it.next() orelse break;
- if (mem.eql(u8, word, "-rpath")) {
- const rpath = it.next() orelse {
- try self.addWarning("Expected argument after -rpath in NIX_LDFLAGS");
- break;
- };
- try self.addRPath(rpath);
- } else if (word.len > 2 and word[0] == '-' and word[1] == 'L') {
- const lib_path = word[2..];
- try self.addLibDir(lib_path);
- } else {
- try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word});
- break;
- }
- }
- } else |err| switch (err) {
- error.InvalidUtf8 => {},
- error.EnvironmentVariableNotFound => {},
- error.OutOfMemory => |e| return e,
- }
- if (is_nix) {
- return self;
- }
-
- if (comptime builtin.target.isDarwin()) {
- try self.addIncludeDir("/usr/include");
- try self.addIncludeDir("/usr/local/include");
-
- try self.addLibDir("/usr/lib");
- try self.addLibDir("/usr/local/lib");
-
- try self.addFrameworkDir("/Library/Frameworks");
- try self.addFrameworkDir("/System/Library/Frameworks");
-
- return self;
- }
-
- if (comptime native_target.os.tag == .solaris) {
- try self.addLibDir("/usr/lib/64");
- try self.addLibDir("/usr/local/lib/64");
- try self.addLibDir("/lib/64");
-
- try self.addIncludeDir("/usr/include");
- try self.addIncludeDir("/usr/local/include");
-
- return self;
- }
-
- if (native_target.os.tag != .windows) {
- const triple = try native_target.linuxTriple(allocator);
- const qual = native_target.cpu.arch.ptrBitWidth();
-
- // TODO: $ ld --verbose | grep SEARCH_DIR
- // the output contains some paths that end with lib64, maybe include them too?
- // TODO: what is the best possible order of things?
- // TODO: some of these are suspect and should only be added on some systems. audit needed.
-
- try self.addIncludeDir("/usr/local/include");
- try self.addLibDirFmt("/usr/local/lib{d}", .{qual});
- try self.addLibDir("/usr/local/lib");
-
- try self.addIncludeDirFmt("/usr/include/{s}", .{triple});
- try self.addLibDirFmt("/usr/lib/{s}", .{triple});
-
- try self.addIncludeDir("/usr/include");
- try self.addLibDirFmt("/lib{d}", .{qual});
- try self.addLibDir("/lib");
- try self.addLibDirFmt("/usr/lib{d}", .{qual});
- try self.addLibDir("/usr/lib");
-
- // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
- // zlib.h is in /usr/include (added above)
- // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
- try self.addLibDirFmt("/lib/{s}", .{triple});
- }
-
- return self;
- }
-
- pub fn deinit(self: *NativePaths) void {
- deinitArray(&self.include_dirs);
- deinitArray(&self.lib_dirs);
- deinitArray(&self.framework_dirs);
- deinitArray(&self.rpaths);
- deinitArray(&self.warnings);
- self.* = undefined;
- }
-
- fn deinitArray(array: *ArrayList([:0]u8)) void {
- for (array.items) |item| {
- array.allocator.free(item);
- }
- array.deinit();
- }
-
- pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void {
- return self.appendArray(&self.include_dirs, s);
- }
-
- pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
- const item = try std.fmt.allocPrintZ(self.include_dirs.allocator, fmt, args);
- errdefer self.include_dirs.allocator.free(item);
- try self.include_dirs.append(item);
- }
-
- pub fn addLibDir(self: *NativePaths, s: []const u8) !void {
- return self.appendArray(&self.lib_dirs, s);
- }
-
- pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
- const item = try std.fmt.allocPrintZ(self.lib_dirs.allocator, fmt, args);
- errdefer self.lib_dirs.allocator.free(item);
- try self.lib_dirs.append(item);
- }
-
- pub fn addWarning(self: *NativePaths, s: []const u8) !void {
- return self.appendArray(&self.warnings, s);
- }
-
- pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void {
- return self.appendArray(&self.framework_dirs, s);
- }
-
- pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
- const item = try std.fmt.allocPrintZ(self.framework_dirs.allocator, fmt, args);
- errdefer self.framework_dirs.allocator.free(item);
- try self.framework_dirs.append(item);
- }
-
- pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
- const item = try std.fmt.allocPrintZ(self.warnings.allocator, fmt, args);
- errdefer self.warnings.allocator.free(item);
- try self.warnings.append(item);
- }
-
- pub fn addRPath(self: *NativePaths, s: []const u8) !void {
- return self.appendArray(&self.rpaths, s);
- }
-
- fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void {
- _ = self;
- const item = try array.allocator.dupeZ(u8, s);
- errdefer array.allocator.free(item);
- try array.append(item);
- }
-};
-
-pub const NativeTargetInfo = struct {
- target: Target,
-
- dynamic_linker: DynamicLinker = DynamicLinker{},
-
- pub const DynamicLinker = Target.DynamicLinker;
-
- pub const DetectError = error{
- OutOfMemory,
- FileSystem,
- SystemResources,
- SymLinkLoop,
- ProcessFdQuotaExceeded,
- SystemFdQuotaExceeded,
- DeviceBusy,
- OSVersionDetectionFail,
- };
-
- /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
- /// natively, which should be standard or default, and which are provided explicitly, this function
- /// resolves the native components by detecting the native system, and then resolves standard/default parts
- /// relative to that.
- /// Any resources this function allocates are released before returning, and so there is no
- /// deinitialization method.
- /// TODO Remove the Allocator requirement from this function.
- pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
- var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
- if (cross_target.os_tag == null) {
- switch (builtin.target.os.tag) {
- .linux => {
- const uts = std.os.uname();
- const release = mem.sliceTo(&uts.release, 0);
- // The release field sometimes has a weird format,
- // `Version.parse` will attempt to find some meaningful interpretation.
- if (std.builtin.Version.parse(release)) |ver| {
- os.version_range.linux.range.min = ver;
- os.version_range.linux.range.max = ver;
- } else |err| switch (err) {
- error.Overflow => {},
- error.InvalidCharacter => {},
- error.InvalidVersion => {},
- }
- },
- .solaris => {
- const uts = std.os.uname();
- const release = mem.sliceTo(&uts.release, 0);
- if (std.builtin.Version.parse(release)) |ver| {
- os.version_range.semver.min = ver;
- os.version_range.semver.max = ver;
- } else |err| switch (err) {
- error.Overflow => {},
- error.InvalidCharacter => {},
- error.InvalidVersion => {},
- }
- },
- .windows => {
- const detected_version = windows.detectRuntimeVersion();
- os.version_range.windows.min = detected_version;
- os.version_range.windows.max = detected_version;
- },
- .macos => try darwin.macos.detect(&os),
- .freebsd, .netbsd, .dragonfly => {
- const key = switch (builtin.target.os.tag) {
- .freebsd => "kern.osreldate",
- .netbsd, .dragonfly => "kern.osrevision",
- else => unreachable,
- };
- var value: u32 = undefined;
- var len: usize = @sizeOf(@TypeOf(value));
-
- std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
- error.NameTooLong => unreachable, // constant, known good value
- error.PermissionDenied => unreachable, // only when setting values,
- error.SystemResources => unreachable, // memory already on the stack
- error.UnknownName => unreachable, // constant, known good value
- error.Unexpected => return error.OSVersionDetectionFail,
- };
-
- switch (builtin.target.os.tag) {
- .freebsd => {
- // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
- // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
- // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
- // e.g. 492101 = 4.11-STABLE = 4.(9+2)
- const major = value / 100_000;
- const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
- const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
- const patch = value % 1_000;
- os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
- os.version_range.semver.max = os.version_range.semver.min;
- },
- .netbsd => {
- // #define __NetBSD_Version__ MMmmrrpp00
- //
- // M = major version
- // m = minor version; a minor number of 99 indicates current.
- // r = 0 (*)
- // p = patchlevel
- const major = value / 100_000_000;
- const minor = value % 100_000_000 / 1_000_000;
- const patch = value % 10_000 / 100;
- os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
- os.version_range.semver.max = os.version_range.semver.min;
- },
- .dragonfly => {
- // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
- // flat base10 format: Mmmmpp
- // M = major
- // m = minor; odd-numbers indicate current dev branch
- // p = patch
- const major = value / 100_000;
- const minor = value % 100_000 / 100;
- const patch = value % 100;
- os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
- os.version_range.semver.max = os.version_range.semver.min;
- },
- else => unreachable,
- }
- },
- .openbsd => {
- const mib: [2]c_int = [_]c_int{
- std.os.CTL.KERN,
- std.os.KERN.OSRELEASE,
- };
- var buf: [64]u8 = undefined;
- var len: usize = buf.len;
-
- std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
- error.NameTooLong => unreachable, // constant, known good value
- error.PermissionDenied => unreachable, // only when setting values,
- error.SystemResources => unreachable, // memory already on the stack
- error.UnknownName => unreachable, // constant, known good value
- error.Unexpected => return error.OSVersionDetectionFail,
- };
-
- if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| {
- os.version_range.semver.min = ver;
- os.version_range.semver.max = ver;
- } else |_| {
- return error.OSVersionDetectionFail;
- }
- },
- else => {
- // Unimplemented, fall back to default version range.
- },
- }
- }
-
- if (cross_target.os_version_min) |min| switch (min) {
- .none => {},
- .semver => |semver| switch (cross_target.getOsTag()) {
- .linux => os.version_range.linux.range.min = semver,
- else => os.version_range.semver.min = semver,
- },
- .windows => |win_ver| os.version_range.windows.min = win_ver,
- };
-
- if (cross_target.os_version_max) |max| switch (max) {
- .none => {},
- .semver => |semver| switch (cross_target.getOsTag()) {
- .linux => os.version_range.linux.range.max = semver,
- else => os.version_range.semver.max = semver,
- },
- .windows => |win_ver| os.version_range.windows.max = win_ver,
- };
-
- if (cross_target.glibc_version) |glibc| {
- assert(cross_target.isGnuLibC());
- os.version_range.linux.glibc = glibc;
- }
-
- // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
- // native CPU architecture as being different than the current target), we use this:
- const cpu_arch = cross_target.getCpuArch();
-
- var cpu = switch (cross_target.cpu_model) {
- .native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target),
- .baseline => Target.Cpu.baseline(cpu_arch),
- .determined_by_cpu_arch => if (cross_target.cpu_arch == null)
- detectNativeCpuAndFeatures(cpu_arch, os, cross_target)
- else
- Target.Cpu.baseline(cpu_arch),
- .explicit => |model| model.toCpu(cpu_arch),
- } orelse backup_cpu_detection: {
- break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
- };
- var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
- // For x86, we need to populate some CPU feature flags depending on architecture
- // and mode:
- // * 16bit_mode => if the abi is code16
- // * 32bit_mode => if the arch is i386
- // However, the "mode" flags can be used as overrides, so if the user explicitly
- // sets one of them, that takes precedence.
- switch (cpu_arch) {
- .i386 => {
- if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{
- .@"16bit_mode", .@"32bit_mode",
- })) {
- switch (result.target.abi) {
- .code16 => result.target.cpu.features.addFeature(
- @enumToInt(std.Target.x86.Feature.@"16bit_mode"),
- ),
- else => result.target.cpu.features.addFeature(
- @enumToInt(std.Target.x86.Feature.@"32bit_mode"),
- ),
- }
- }
- },
- .arm, .armeb => {
- // XXX What do we do if the target has the noarm feature?
- // What do we do if the user specifies +thumb_mode?
- },
- .thumb, .thumbeb => {
- result.target.cpu.features.addFeature(
- @enumToInt(std.Target.arm.Feature.thumb_mode),
- );
- },
- else => {},
- }
- cross_target.updateCpuFeatures(&result.target.cpu.features);
- return result;
- }
-
- /// First we attempt to use the executable's own binary. If it is dynamically
- /// linked, then it should answer both the C ABI question and the dynamic linker question.
- /// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
- /// we fall back to the defaults.
- /// TODO Remove the Allocator requirement from this function.
- fn detectAbiAndDynamicLinker(
- allocator: Allocator,
- cpu: Target.Cpu,
- os: Target.Os,
- cross_target: CrossTarget,
- ) DetectError!NativeTargetInfo {
- const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
- const is_linux = builtin.target.os.tag == .linux;
- const have_all_info = cross_target.dynamic_linker.get() != null and
- cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
- const os_is_non_native = cross_target.os_tag != null;
- if (!native_target_has_ld or have_all_info or os_is_non_native) {
- return defaultAbiAndDynamicLinker(cpu, os, cross_target);
- }
- if (cross_target.abi) |abi| {
- if (abi.isMusl()) {
- // musl implies static linking.
- return defaultAbiAndDynamicLinker(cpu, os, cross_target);
- }
- }
- // The current target's ABI cannot be relied on for this. For example, we may build the zig
- // compiler for target riscv64-linux-musl and provide a tarball for users to download.
- // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
- // and supported by Zig. But that means that we must detect the system ABI here rather than
- // relying on `builtin.target`.
- const all_abis = comptime blk: {
- assert(@enumToInt(Target.Abi.none) == 0);
- const fields = std.meta.fields(Target.Abi)[1..];
- var array: [fields.len]Target.Abi = undefined;
- inline for (fields) |field, i| {
- array[i] = @field(Target.Abi, field.name);
- }
- break :blk array;
- };
- var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
- var ld_info_list_len: usize = 0;
-
- for (all_abis) |abi| {
- // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
- // skip adding it to `ld_info_list`.
- const target: Target = .{
- .cpu = cpu,
- .os = os,
- .abi = abi,
- };
- const ld = target.standardDynamicLinkerPath();
- if (ld.get() == null) continue;
-
- ld_info_list_buffer[ld_info_list_len] = .{
- .ld = ld,
- .abi = abi,
- };
- ld_info_list_len += 1;
- }
- const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
-
- // Best case scenario: the executable is dynamically linked, and we can iterate
- // over our own shared objects and find a dynamic linker.
- self_exe: {
- const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
- defer {
- for (lib_paths) |lib_path| {
- allocator.free(lib_path);
- }
- allocator.free(lib_paths);
- }
-
- var found_ld_info: LdInfo = undefined;
- var found_ld_path: [:0]const u8 = undefined;
-
- // Look for dynamic linker.
- // This is O(N^M) but typical case here is N=2 and M=10.
- find_ld: for (lib_paths) |lib_path| {
- for (ld_info_list) |ld_info| {
- const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
- if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
- found_ld_info = ld_info;
- found_ld_path = lib_path;
- break :find_ld;
- }
- }
- } else break :self_exe;
-
- // Look for glibc version.
- var os_adjusted = os;
- if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and
- cross_target.glibc_version == null)
- {
- for (lib_paths) |lib_path| {
- if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
- os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
- error.UnrecognizedGnuLibCFileName => continue,
- error.InvalidGnuLibCVersion => continue,
- error.GnuLibCVersionUnavailable => continue,
- else => |e| return e,
- };
- break;
- }
- }
- }
-
- var result: NativeTargetInfo = .{
- .target = .{
- .cpu = cpu,
- .os = os_adjusted,
- .abi = cross_target.abi orelse found_ld_info.abi,
- },
- .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
- DynamicLinker.init(found_ld_path)
- else
- cross_target.dynamic_linker,
- };
- return result;
- }
-
- const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) {
- error.NoSpaceLeft => unreachable,
- error.NameTooLong => unreachable,
- error.PathAlreadyExists => unreachable,
- error.SharingViolation => unreachable,
- error.InvalidUtf8 => unreachable,
- error.BadPathName => unreachable,
- error.PipeBusy => unreachable,
- error.FileLocksNotSupported => unreachable,
- error.WouldBlock => unreachable,
-
- error.IsDir,
- error.NotDir,
- error.AccessDenied,
- error.NoDevice,
- error.FileNotFound,
- error.FileTooBig,
- error.Unexpected,
- => return defaultAbiAndDynamicLinker(cpu, os, cross_target),
-
- else => |e| return e,
- };
- defer env_file.close();
-
- // If Zig is statically linked, such as via distributed binary static builds, the above
- // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
- // Since that path is hard-coded into the shebang line of many portable scripts, it's a
- // reasonably reliable path to check for.
- return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
- error.FileSystem,
- error.SystemResources,
- error.SymLinkLoop,
- error.ProcessFdQuotaExceeded,
- error.SystemFdQuotaExceeded,
- => |e| return e,
-
- error.UnableToReadElfFile,
- error.InvalidElfClass,
- error.InvalidElfVersion,
- error.InvalidElfEndian,
- error.InvalidElfFile,
- error.InvalidElfMagic,
- error.Unexpected,
- error.UnexpectedEndOfFile,
- error.NameTooLong,
- // Finally, we fall back on the standard path.
- => defaultAbiAndDynamicLinker(cpu, os, cross_target),
- };
- }
-
- const glibc_so_basename = "libc.so.6";
-
- fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
- var link_buf: [std.os.PATH_MAX]u8 = undefined;
- const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {
- error.AccessDenied => return error.GnuLibCVersionUnavailable,
- error.FileSystem => return error.FileSystem,
- error.SymLinkLoop => return error.SymLinkLoop,
- error.NameTooLong => unreachable,
- error.NotLink => return error.GnuLibCVersionUnavailable,
- error.FileNotFound => return error.GnuLibCVersionUnavailable,
- error.SystemResources => return error.SystemResources,
- error.NotDir => return error.GnuLibCVersionUnavailable,
- error.Unexpected => return error.GnuLibCVersionUnavailable,
- error.InvalidUtf8 => unreachable, // Windows only
- error.BadPathName => unreachable, // Windows only
- error.UnsupportedReparsePointType => unreachable, // Windows only
- };
- return glibcVerFromLinkName(link_name);
- }
-
- fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version {
- // example: "libc-2.3.4.so"
- // example: "libc-2.27.so"
- const prefix = "libc-";
- const suffix = ".so";
- if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
- return error.UnrecognizedGnuLibCFileName;
- }
- // chop off "libc-" and ".so"
- const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
- return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
- error.Overflow => return error.InvalidGnuLibCVersion,
- error.InvalidCharacter => return error.InvalidGnuLibCVersion,
- error.InvalidVersion => return error.InvalidGnuLibCVersion,
- };
- }
-
- pub const AbiAndDynamicLinkerFromFileError = error{
- FileSystem,
- SystemResources,
- SymLinkLoop,
- ProcessFdQuotaExceeded,
- SystemFdQuotaExceeded,
- UnableToReadElfFile,
- InvalidElfClass,
- InvalidElfVersion,
- InvalidElfEndian,
- InvalidElfFile,
- InvalidElfMagic,
- Unexpected,
- UnexpectedEndOfFile,
- NameTooLong,
- };
-
- pub fn abiAndDynamicLinkerFromFile(
- file: fs.File,
- cpu: Target.Cpu,
- os: Target.Os,
- ld_info_list: []const LdInfo,
- cross_target: CrossTarget,
- ) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
- var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
- _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
- const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
- const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
- if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
- const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
- elf.ELFDATA2LSB => .Little,
- elf.ELFDATA2MSB => .Big,
- else => return error.InvalidElfEndian,
- };
- const need_bswap = elf_endian != native_endian;
- if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
-
- const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
- elf.ELFCLASS32 => false,
- elf.ELFCLASS64 => true,
- else => return error.InvalidElfClass,
- };
- var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
- const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
- const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
-
- var result: NativeTargetInfo = .{
- .target = .{
- .cpu = cpu,
- .os = os,
- .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
- },
- .dynamic_linker = cross_target.dynamic_linker,
- };
- var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
- const look_for_ld = cross_target.dynamic_linker.get() == null;
-
- var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
- if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
-
- var ph_i: u16 = 0;
- while (ph_i < phnum) {
- // Reserve some bytes so that we can deref the 64-bit struct fields
- // even when the ELF file is 32-bits.
- const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
- const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
- var ph_buf_i: usize = 0;
- while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
- ph_i += 1;
- phoff += phentsize;
- ph_buf_i += phentsize;
- }) {
- const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
- const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
- const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
- switch (p_type) {
- elf.PT_INTERP => if (look_for_ld) {
- const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
- const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
- if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
- const filesz = @intCast(usize, p_filesz);
- _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
- // PT_INTERP includes a null byte in filesz.
- const len = filesz - 1;
- // dynamic_linker.max_byte is "max", not "len".
- // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
- result.dynamic_linker.max_byte = @intCast(u8, len - 1);
-
- // Use it to determine ABI.
- const full_ld_path = result.dynamic_linker.buffer[0..len];
- for (ld_info_list) |ld_info| {
- const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
- if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
- result.target.abi = ld_info.abi;
- break;
- }
- }
- },
- // We only need this for detecting glibc version.
- elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
- cross_target.glibc_version == null)
- {
- var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
- const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
- const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
- const dyn_num = p_filesz / dyn_size;
- var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
- var dyn_i: usize = 0;
- dyn: while (dyn_i < dyn_num) {
- // Reserve some bytes so that we can deref the 64-bit struct fields
- // even when the ELF file is 32-bits.
- const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
- const dyn_read_byte_len = try preadMin(
- file,
- dyn_buf[0 .. dyn_buf.len - dyn_reserve],
- dyn_off,
- dyn_size,
- );
- var dyn_buf_i: usize = 0;
- while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
- dyn_i += 1;
- dyn_off += dyn_size;
- dyn_buf_i += dyn_size;
- }) {
- const dyn32 = @ptrCast(
- *elf.Elf32_Dyn,
- @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
- );
- const dyn64 = @ptrCast(
- *elf.Elf64_Dyn,
- @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
- );
- const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
- const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
- if (tag == elf.DT_RUNPATH) {
- rpath_offset = val;
- break :dyn;
- }
- }
- }
- },
- else => continue,
- }
- }
- }
-
- if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) {
- if (rpath_offset) |rpoff| {
- const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
-
- var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
- const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
- const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
-
- var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
- if (sh_buf.len < shentsize) return error.InvalidElfFile;
-
- _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
- const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
- const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
- const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
- const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
- var strtab_buf: [4096:0]u8 = undefined;
- const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
- const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
- const shstrtab = strtab_buf[0..shstrtab_read_len];
-
- const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
- var sh_i: u16 = 0;
- const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
- // Reserve some bytes so that we can deref the 64-bit struct fields
- // even when the ELF file is 32-bits.
- const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
- const sh_read_byte_len = try preadMin(
- file,
- sh_buf[0 .. sh_buf.len - sh_reserve],
- shoff,
- shentsize,
- );
- var sh_buf_i: usize = 0;
- while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
- sh_i += 1;
- shoff += shentsize;
- sh_buf_i += shentsize;
- }) {
- const sh32 = @ptrCast(
- *elf.Elf32_Shdr,
- @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
- );
- const sh64 = @ptrCast(
- *elf.Elf64_Shdr,
- @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
- );
- const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
- // TODO this pointer cast should not be necessary
- const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
- if (mem.eql(u8, sh_name, ".dynstr")) {
- break :find_dyn_str .{
- .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
- .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
- };
- }
- }
- } else null;
-
- if (dynstr) |ds| {
- const strtab_len = std.math.min(ds.size, strtab_buf.len);
- const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len);
- const strtab = strtab_buf[0..strtab_read_len];
- // TODO this pointer cast should not be necessary
- const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) {
- error.Overflow => return error.InvalidElfFile,
- };
- const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0);
- var it = mem.tokenize(u8, rpath_list, ":");
- while (it.next()) |rpath| {
- var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
- error.NameTooLong => unreachable,
- error.InvalidUtf8 => unreachable,
- error.BadPathName => unreachable,
- error.DeviceBusy => unreachable,
-
- error.FileNotFound,
- error.NotDir,
- error.AccessDenied,
- error.NoDevice,
- => continue,
-
- error.ProcessFdQuotaExceeded,
- error.SystemFdQuotaExceeded,
- error.SystemResources,
- error.SymLinkLoop,
- error.Unexpected,
- => |e| return e,
- };
- defer dir.close();
-
- var link_buf: [std.os.PATH_MAX]u8 = undefined;
- const link_name = std.os.readlinkatZ(
- dir.fd,
- glibc_so_basename,
- &link_buf,
- ) catch |err| switch (err) {
- error.NameTooLong => unreachable,
- error.InvalidUtf8 => unreachable, // Windows only
- error.BadPathName => unreachable, // Windows only
- error.UnsupportedReparsePointType => unreachable, // Windows only
-
- error.AccessDenied,
- error.FileNotFound,
- error.NotLink,
- error.NotDir,
- => continue,
-
- error.SystemResources,
- error.FileSystem,
- error.SymLinkLoop,
- error.Unexpected,
- => |e| return e,
- };
- result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
- link_name,
- ) catch |err| switch (err) {
- error.UnrecognizedGnuLibCFileName,
- error.InvalidGnuLibCVersion,
- => continue,
- };
- break;
- }
- }
- }
- }
-
- return result;
- }
-
- fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
- var i: usize = 0;
- while (i < min_read_len) {
- const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
- error.OperationAborted => unreachable, // Windows-only
- error.WouldBlock => unreachable, // Did not request blocking mode
- error.NotOpenForReading => unreachable,
- error.SystemResources => return error.SystemResources,
- error.IsDir => return error.UnableToReadElfFile,
- error.BrokenPipe => return error.UnableToReadElfFile,
- error.Unseekable => return error.UnableToReadElfFile,
- error.ConnectionResetByPeer => return error.UnableToReadElfFile,
- error.ConnectionTimedOut => return error.UnableToReadElfFile,
- error.Unexpected => return error.Unexpected,
- error.InputOutput => return error.FileSystem,
- error.AccessDenied => return error.Unexpected,
- };
- if (len == 0) return error.UnexpectedEndOfFile;
- i += len;
- }
- return i;
- }
-
- fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
- const target: Target = .{
- .cpu = cpu,
- .os = os,
- .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
- };
- return NativeTargetInfo{
- .target = target,
- .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
- target.standardDynamicLinkerPath()
- else
- cross_target.dynamic_linker,
- };
- }
-
- pub const LdInfo = struct {
- ld: DynamicLinker,
- abi: Target.Abi,
- };
-
- pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
- if (is_64) {
- if (need_bswap) {
- return @byteSwap(@TypeOf(int_64), int_64);
- } else {
- return int_64;
- }
- } else {
- if (need_bswap) {
- return @byteSwap(@TypeOf(int_32), int_32);
- } else {
- return int_32;
- }
- }
- }
-
- fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
- // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
- // although it is a runtime value, is guaranteed to be one of the architectures in the set
- // of the respective switch prong.
- switch (builtin.cpu.arch) {
- .x86_64, .i386 => {
- return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
- },
- else => {},
- }
-
- switch (builtin.os.tag) {
- .linux => return linux.detectNativeCpuAndFeatures(),
- .macos => return darwin.macos.detectNativeCpuAndFeatures(),
- else => {},
- }
-
- // This architecture does not have CPU model & feature detection yet.
- // See https://github.com/ziglang/zig/issues/4591
- return null;
- }
-};
+pub const linux = @import("system/linux.zig");
test {
- _ = @import("system/darwin.zig");
- _ = @import("system/linux.zig");
+ _ = NativePaths;
+ _ = NativeTargetInfo;
+
+ _ = darwin;
+ _ = linux;
+ _ = windows;
}
lib/std/build.zig
@@ -16,6 +16,7 @@ const BufMap = std.BufMap;
const fmt_lib = std.fmt;
const File = std.fs.File;
const CrossTarget = std.zig.CrossTarget;
+const NativeTargetInfo = std.zig.system.NativeTargetInfo;
pub const FmtStep = @import("build/FmtStep.zig");
pub const TranslateCStep = @import("build/TranslateCStep.zig");
@@ -86,6 +87,9 @@ pub const Builder = struct {
/// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`.
glibc_runtimes_dir: ?[]const u8 = null,
+ /// Information about the native target. Computed before build() is invoked.
+ host: NativeTargetInfo,
+
const PkgConfigError = error{
PkgConfigCrashed,
PkgConfigFailed,
@@ -159,6 +163,8 @@ pub const Builder = struct {
const env_map = try allocator.create(BufMap);
env_map.* = try process.getEnvMap(allocator);
+ const host = try NativeTargetInfo.detect(allocator, .{});
+
const self = try allocator.create(Builder);
self.* = Builder{
.zig_exe = zig_exe,
@@ -204,6 +210,7 @@ pub const Builder = struct {
.install_path = undefined,
.vcpkg_root = VcpkgRoot{ .unattempted = {} },
.args = null,
+ .host = host,
};
try self.top_level_steps.append(&self.install_tls);
try self.top_level_steps.append(&self.uninstall_tls);
@@ -1436,6 +1443,7 @@ pub const LibExeObjStep = struct {
builder: *Builder,
name: []const u8,
target: CrossTarget = CrossTarget{},
+ target_info: NativeTargetInfo,
linker_script: ?FileSource = null,
version_script: ?[]const u8 = null,
out_filename: []const u8,
@@ -1655,6 +1663,8 @@ pub const LibExeObjStep = struct {
.output_lib_path_source = GeneratedFile{ .step = &self.step },
.output_h_path_source = GeneratedFile{ .step = &self.step },
.output_pdb_path_source = GeneratedFile{ .step = &self.step },
+
+ .target_info = undefined, // populated in computeOutFileNames
};
self.computeOutFileNames();
if (root_src) |rs| rs.addStepDependencies(&self.step);
@@ -1662,11 +1672,11 @@ pub const LibExeObjStep = struct {
}
fn computeOutFileNames(self: *LibExeObjStep) void {
- const target_info = std.zig.system.NativeTargetInfo.detect(
- self.builder.allocator,
- self.target,
- ) catch unreachable;
- const target = target_info.target;
+ self.target_info = NativeTargetInfo.detect(self.builder.allocator, self.target) catch
+ unreachable;
+
+ const target = self.target_info.target;
+
self.out_filename = std.zig.binNameAlloc(self.builder.allocator, .{
.root_name = self.name,
.target = target,
@@ -2526,74 +2536,80 @@ pub const LibExeObjStep = struct {
try zig_args.append("--test-cmd-bin");
}
}
- } else switch (self.target.getExternalExecutor()) {
- .native => {},
- .unavailable => {
- try zig_args.append("--test-no-exec");
- },
- .rosetta => if (builder.enable_rosetta) {
- try zig_args.append("--test-cmd-bin");
- } else {
- try zig_args.append("--test-no-exec");
- },
- .qemu => |bin_name| ok: {
- if (builder.enable_qemu) qemu: {
- const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc;
- const glibc_dir_arg = if (need_cross_glibc)
- builder.glibc_runtimes_dir orelse break :qemu
- else
- null;
- try zig_args.append("--test-cmd");
- try zig_args.append(bin_name);
- if (glibc_dir_arg) |dir| {
- // TODO look into making this a call to `linuxTriple`. This
- // needs the directory to be called "i686" rather than
- // "i386" which is why we do it manually here.
- const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
- const cpu_arch = self.target.getCpuArch();
- const os_tag = self.target.getOsTag();
- const abi = self.target.getAbi();
- const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
- "i686"
+ } else {
+ const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc;
+
+ switch (self.builder.host.getExternalExecutor(self.target_info, .{
+ .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
+ .link_libc = self.is_linking_libc,
+ })) {
+ .native => {},
+ .bad_dl, .bad_os_or_cpu => {
+ try zig_args.append("--test-no-exec");
+ },
+ .rosetta => if (builder.enable_rosetta) {
+ try zig_args.append("--test-cmd-bin");
+ } else {
+ try zig_args.append("--test-no-exec");
+ },
+ .qemu => |bin_name| ok: {
+ if (builder.enable_qemu) qemu: {
+ const glibc_dir_arg = if (need_cross_glibc)
+ builder.glibc_runtimes_dir orelse break :qemu
else
- @tagName(cpu_arch);
- const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{
- dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
- });
-
+ null;
try zig_args.append("--test-cmd");
- try zig_args.append("-L");
- try zig_args.append("--test-cmd");
- try zig_args.append(full_dir);
+ try zig_args.append(bin_name);
+ if (glibc_dir_arg) |dir| {
+ // TODO look into making this a call to `linuxTriple`. This
+ // needs the directory to be called "i686" rather than
+ // "i386" which is why we do it manually here.
+ const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
+ const cpu_arch = self.target.getCpuArch();
+ const os_tag = self.target.getOsTag();
+ const abi = self.target.getAbi();
+ const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
+ "i686"
+ else
+ @tagName(cpu_arch);
+ const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{
+ dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
+ });
+
+ try zig_args.append("--test-cmd");
+ try zig_args.append("-L");
+ try zig_args.append("--test-cmd");
+ try zig_args.append(full_dir);
+ }
+ try zig_args.append("--test-cmd-bin");
+ break :ok;
}
+ try zig_args.append("--test-no-exec");
+ },
+ .wine => |bin_name| if (builder.enable_wine) {
+ try zig_args.append("--test-cmd");
+ try zig_args.append(bin_name);
try zig_args.append("--test-cmd-bin");
- break :ok;
- }
- try zig_args.append("--test-no-exec");
- },
- .wine => |bin_name| if (builder.enable_wine) {
- try zig_args.append("--test-cmd");
- try zig_args.append(bin_name);
- try zig_args.append("--test-cmd-bin");
- } else {
- try zig_args.append("--test-no-exec");
- },
- .wasmtime => |bin_name| if (builder.enable_wasmtime) {
- try zig_args.append("--test-cmd");
- try zig_args.append(bin_name);
- try zig_args.append("--test-cmd");
- try zig_args.append("--dir=.");
- try zig_args.append("--test-cmd-bin");
- } else {
- try zig_args.append("--test-no-exec");
- },
- .darling => |bin_name| if (builder.enable_darling) {
- try zig_args.append("--test-cmd");
- try zig_args.append(bin_name);
- try zig_args.append("--test-cmd-bin");
- } else {
- try zig_args.append("--test-no-exec");
- },
+ } else {
+ try zig_args.append("--test-no-exec");
+ },
+ .wasmtime => |bin_name| if (builder.enable_wasmtime) {
+ try zig_args.append("--test-cmd");
+ try zig_args.append(bin_name);
+ try zig_args.append("--test-cmd");
+ try zig_args.append("--dir=.");
+ try zig_args.append("--test-cmd-bin");
+ } else {
+ try zig_args.append("--test-no-exec");
+ },
+ .darling => |bin_name| if (builder.enable_darling) {
+ try zig_args.append("--test-cmd");
+ try zig_args.append(bin_name);
+ try zig_args.append("--test-cmd-bin");
+ } else {
+ try zig_args.append("--test-no-exec");
+ },
+ }
}
for (self.packages.items) |pkg| {
lib/std/target.zig
@@ -1689,27 +1689,6 @@ pub const Target = struct {
}
}
- /// Return whether or not the given host target is capable of executing natively executables
- /// of the other target.
- pub fn canExecBinariesOf(host_target: Target, binary_target: Target) bool {
- if (host_target.os.tag != binary_target.os.tag)
- return false;
-
- if (host_target.cpu.arch == binary_target.cpu.arch)
- return true;
-
- if (host_target.cpu.arch == .x86_64 and binary_target.cpu.arch == .i386)
- return true;
-
- if (host_target.cpu.arch == .aarch64 and binary_target.cpu.arch == .arm)
- return true;
-
- if (host_target.cpu.arch == .aarch64_be and binary_target.cpu.arch == .armeb)
- return true;
-
- return false;
- }
-
/// 0c spim little-endian MIPS 3000 family
/// 1c 68000 Motorola MC68000
/// 2c 68020 Motorola MC68020
src/main.zig
@@ -2539,6 +2539,7 @@ fn buildOutputType(
&comp_destroyed,
all_args,
runtime_args_start,
+ link_libc,
);
}
@@ -2611,6 +2612,7 @@ fn buildOutputType(
&comp_destroyed,
all_args,
runtime_args_start,
+ link_libc,
);
},
.update_and_run => {
@@ -2636,6 +2638,7 @@ fn buildOutputType(
&comp_destroyed,
all_args,
runtime_args_start,
+ link_libc,
);
},
}
@@ -2700,6 +2703,7 @@ fn runOrTest(
comp_destroyed: *bool,
all_args: []const []const u8,
runtime_args_start: ?usize,
+ link_libc: bool,
) !void {
const exe_loc = emit_bin_loc orelse return;
const exe_directory = exe_loc.directory orelse comp.bin_file.options.emit.?.directory;
@@ -2740,7 +2744,7 @@ fn runOrTest(
if (std.process.can_execv and arg_mode == .run and !watch) {
// execv releases the locks; no need to destroy the Compilation here.
const err = std.process.execv(gpa, argv.items);
- try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info);
+ try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);
const cmd = try argvCmd(arena, argv.items);
fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
} else {
@@ -2759,7 +2763,7 @@ fn runOrTest(
}
const term = child.spawnAndWait() catch |err| {
- try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info);
+ try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);
const cmd = try argvCmd(arena, argv.items);
fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
};
@@ -4662,34 +4666,108 @@ fn warnAboutForeignBinaries(
arena: Allocator,
arg_mode: ArgMode,
target_info: std.zig.system.NativeTargetInfo,
+ link_libc: bool,
) !void {
const host_cross_target: std.zig.CrossTarget = .{};
const host_target_info = try detectNativeTargetInfo(gpa, host_cross_target);
- if (!host_target_info.target.canExecBinariesOf(target_info.target)) {
- const host_name = try host_target_info.target.zigTriple(arena);
- const foreign_name = try target_info.target.zigTriple(arena);
- const tip_suffix = switch (arg_mode) {
- .zig_test => ". Consider using --test-no-exec or --test-cmd",
- else => "",
- };
- warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}){s}", .{
- host_name, foreign_name, tip_suffix,
- });
- return;
- }
-
- if (target_info.dynamic_linker.get()) |foreign_dl| {
- std.fs.cwd().access(foreign_dl, .{}) catch {
+ switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
+ .native => return,
+ .rosetta => {
+ const host_name = try host_target_info.target.zigTriple(arena);
+ const foreign_name = try target_info.target.zigTriple(arena);
+ warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
+ host_name, foreign_name,
+ });
+ },
+ .qemu => |qemu| {
+ const host_name = try host_target_info.target.zigTriple(arena);
+ const foreign_name = try target_info.target.zigTriple(arena);
+ switch (arg_mode) {
+ .zig_test => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
+ "to run the tests",
+ .{ host_name, foreign_name, qemu },
+ ),
+ else => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '{s}' to run the binary",
+ .{ host_name, foreign_name, qemu },
+ ),
+ }
+ },
+ .wine => |wine| {
+ const host_name = try host_target_info.target.zigTriple(arena);
+ const foreign_name = try target_info.target.zigTriple(arena);
+ switch (arg_mode) {
+ .zig_test => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
+ "to run the tests",
+ .{ host_name, foreign_name, wine },
+ ),
+ else => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '{s}' to run the binary",
+ .{ host_name, foreign_name, wine },
+ ),
+ }
+ },
+ .wasmtime => |wasmtime| {
+ const host_name = try host_target_info.target.zigTriple(arena);
+ const foreign_name = try target_info.target.zigTriple(arena);
+ switch (arg_mode) {
+ .zig_test => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
+ "to run the tests",
+ .{ host_name, foreign_name, wasmtime },
+ ),
+ else => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '{s}' to run the binary",
+ .{ host_name, foreign_name, wasmtime },
+ ),
+ }
+ },
+ .darling => |darling| {
+ const host_name = try host_target_info.target.zigTriple(arena);
+ const foreign_name = try target_info.target.zigTriple(arena);
+ switch (arg_mode) {
+ .zig_test => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
+ "to run the tests",
+ .{ host_name, foreign_name, darling },
+ ),
+ else => warn(
+ "the host system ({s}) does not appear to be capable of executing binaries " ++
+ "from the target ({s}). Consider using '{s}' to run the binary",
+ .{ host_name, foreign_name, darling },
+ ),
+ }
+ },
+ .bad_dl => |foreign_dl| {
const host_dl = host_target_info.dynamic_linker.get() orelse "(none)";
const tip_suffix = switch (arg_mode) {
- .zig_test => ", --test-no-exec, or --test-cmd",
+ .zig_test => ", '--test-no-exec', or '--test-cmd'",
else => "",
};
- 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}", .{
+ 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}", .{
host_dl, foreign_dl, tip_suffix,
});
- return;
- };
+ },
+ .bad_os_or_cpu => {
+ const host_name = try host_target_info.target.zigTriple(arena);
+ const foreign_name = try target_info.target.zigTriple(arena);
+ const tip_suffix = switch (arg_mode) {
+ .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
+ else => "",
+ };
+ warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}){s}", .{
+ host_name, foreign_name, tip_suffix,
+ });
+ },
}
}
src/test.zig
@@ -611,6 +611,8 @@ pub const TestContext = struct {
}
fn run(self: *TestContext) !void {
+ const host = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, .{});
+
var progress = std.Progress{};
const root_node = try progress.start("compiler", self.cases.items.len);
defer root_node.end();
@@ -669,6 +671,7 @@ pub const TestContext = struct {
zig_lib_directory,
&thread_pool,
global_cache_directory,
+ host,
) catch |err| {
fail_count += 1;
print("test '{s}' failed: {s}\n\n", .{ case.name, @errorName(err) });
@@ -687,6 +690,7 @@ pub const TestContext = struct {
zig_lib_directory: Compilation.Directory,
thread_pool: *ThreadPool,
global_cache_directory: Compilation.Directory,
+ host: std.zig.system.NativeTargetInfo,
) !void {
const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
const target = target_info.target;
@@ -882,6 +886,7 @@ pub const TestContext = struct {
.stage1 => true,
else => null,
};
+ const link_libc = case.backend == .llvm;
const comp = try Compilation.create(allocator, .{
.local_cache_directory = zig_cache_directory,
.global_cache_directory = global_cache_directory,
@@ -903,7 +908,7 @@ pub const TestContext = struct {
.is_native_os = case.target.isNativeOs(),
.is_native_abi = case.target.isNativeAbi(),
.dynamic_linker = target_info.dynamic_linker.get(),
- .link_libc = case.backend == .llvm,
+ .link_libc = link_libc,
.use_llvm = use_llvm,
.use_stage1 = use_stage1,
.self_exe_path = std.testing.zig_exe_path,
@@ -1113,7 +1118,7 @@ pub const TestContext = struct {
// child process.
const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{s}", .{bin_name});
if (case.object_format != null and case.object_format.? == .c) {
- if (case.target.getExternalExecutor() != .native) {
+ if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
// We wouldn't be able to run the compiled C code.
return; // Pass test.
}
@@ -1129,9 +1134,9 @@ pub const TestContext = struct {
"-lc",
exe_path,
});
- } else switch (case.target.getExternalExecutor()) {
+ } else switch (host.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
.native => try argv.append(exe_path),
- .unavailable => return, // Pass test.
+ .bad_dl, .bad_os_or_cpu => return, // Pass test.
.rosetta => if (enable_rosetta) {
try argv.append(exe_path);
CMakeLists.txt
@@ -540,6 +540,8 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
+ "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
+ "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativeTargetInfo.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig"
"${CMAKE_SOURCE_DIR}/src/Air.zig"