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