| author | |
| committer | |
| log | dbdb87502d6936597424fe86842c15978ba86918 |
| tree | 8e9fc73fad1d855368a85b7bceab4a725494b47b |
| parent | 3179f58c414b5e4845b9bf3acdf276fe8e2b88a0 |
16 files changed, 1280 insertions(+), 1295 deletions(-)
CMakeLists.txt-1| ... | ... | @@ -516,7 +516,6 @@ set(ZIG_STAGE2_SOURCES |
| 516 | 516 | "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig" |
| 517 | 517 | "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig" |
| 518 | 518 | "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig" |
| 519 | "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativeTargetInfo.zig" | |
| 520 | 519 | "${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig" |
| 521 | 520 | "${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig" |
| 522 | 521 | "${CMAKE_SOURCE_DIR}/src/Air.zig" |
lib/build_runner.zig+1-3| ... | ... | @@ -46,11 +46,9 @@ pub fn main() !void { |
| 46 | 46 | return error.InvalidArgs; |
| 47 | 47 | }; |
| 48 | 48 | |
| 49 | const detected = try std.zig.system.NativeTargetInfo.detect(.{}); | |
| 50 | 49 | const host: std.Build.ResolvedTarget = .{ |
| 51 | 50 | .query = .{}, |
| 52 | .target = detected.target, | |
| 53 | .dynamic_linker = detected.dynamic_linker, | |
| 51 | .target = try std.zig.system.resolveTargetQuery(.{}), | |
| 54 | 52 | }; |
| 55 | 53 | |
| 56 | 54 | const build_root_directory: std.Build.Cache.Directory = .{ |
lib/std/Build.zig+2-13| ... | ... | @@ -2129,14 +2129,6 @@ pub fn hex64(x: u64) [16]u8 { |
| 2129 | 2129 | pub const ResolvedTarget = struct { |
| 2130 | 2130 | query: Target.Query, |
| 2131 | 2131 | target: Target, |
| 2132 | dynamic_linker: Target.DynamicLinker, | |
| 2133 | ||
| 2134 | pub fn toNativeTargetInfo(self: ResolvedTarget) std.zig.system.NativeTargetInfo { | |
| 2135 | return .{ | |
| 2136 | .target = self.target, | |
| 2137 | .dynamic_linker = self.dynamic_linker, | |
| 2138 | }; | |
| 2139 | } | |
| 2140 | 2132 | }; |
| 2141 | 2133 | |
| 2142 | 2134 | /// Converts a target query into a fully resolved target that can be passed to |
| ... | ... | @@ -2146,13 +2138,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget { |
| 2146 | 2138 | // resolved via a WASI API or via the build protocol. |
| 2147 | 2139 | _ = b; |
| 2148 | 2140 | |
| 2149 | const result = std.zig.system.NativeTargetInfo.detect(query) catch | |
| 2150 | @panic("unable to resolve target query"); | |
| 2151 | ||
| 2152 | 2141 | return .{ |
| 2153 | 2142 | .query = query, |
| 2154 | .target = result.target, | |
| 2155 | .dynamic_linker = result.dynamic_linker, | |
| 2143 | .target = std.zig.system.resolveTargetQuery(query) catch | |
| 2144 | @panic("unable to resolve target query"), | |
| 2156 | 2145 | }; |
| 2157 | 2146 | } |
| 2158 | 2147 |
lib/std/Build/Module.zig-1| ... | ... | @@ -746,5 +746,4 @@ const Module = @This(); |
| 746 | 746 | const std = @import("std"); |
| 747 | 747 | const assert = std.debug.assert; |
| 748 | 748 | const LazyPath = std.Build.LazyPath; |
| 749 | const NativeTargetInfo = std.zig.system.NativeTargetInfo; | |
| 750 | 749 | const Step = std.Build.Step; |
lib/std/Build/Step/Compile.zig-1| ... | ... | @@ -9,7 +9,6 @@ const StringHashMap = std.StringHashMap; |
| 9 | 9 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 10 | 10 | const Allocator = mem.Allocator; |
| 11 | 11 | const Step = std.Build.Step; |
| 12 | const NativeTargetInfo = std.zig.system.NativeTargetInfo; | |
| 13 | 12 | const LazyPath = std.Build.LazyPath; |
| 14 | 13 | const PkgConfigPkg = std.Build.PkgConfigPkg; |
| 15 | 14 | const PkgConfigError = std.Build.PkgConfigError; |
lib/std/Build/Step/Options.zig+1-3| ... | ... | @@ -294,11 +294,9 @@ test Options { |
| 294 | 294 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 295 | 295 | defer arena.deinit(); |
| 296 | 296 | |
| 297 | const detected = try std.zig.system.NativeTargetInfo.detect(.{}); | |
| 298 | 297 | const host: std.Build.ResolvedTarget = .{ |
| 299 | 298 | .query = .{}, |
| 300 | .target = detected.target, | |
| 301 | .dynamic_linker = detected.dynamic_linker, | |
| 299 | .target = try std.zig.system.resolveTargetQuery(.{}), | |
| 302 | 300 | }; |
| 303 | 301 | |
| 304 | 302 | var cache: std.Build.Cache = .{ |
lib/std/Build/Step/Run.zig+3-3| ... | ... | @@ -678,8 +678,8 @@ fn runCommand( |
| 678 | 678 | |
| 679 | 679 | const need_cross_glibc = exe.rootModuleTarget().isGnuLibC() and |
| 680 | 680 | exe.is_linking_libc; |
| 681 | const other_target_info = exe.root_module.target.?.toNativeTargetInfo(); | |
| 682 | switch (b.host.toNativeTargetInfo().getExternalExecutor(&other_target_info, .{ | |
| 681 | const other_target = exe.root_module.target.?.target; | |
| 682 | switch (std.zig.system.getExternalExecutor(b.host.target, &other_target, .{ | |
| 683 | 683 | .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, |
| 684 | 684 | .link_libc = exe.is_linking_libc, |
| 685 | 685 | })) { |
| ... | ... | @@ -752,7 +752,7 @@ fn runCommand( |
| 752 | 752 | .bad_dl => |foreign_dl| { |
| 753 | 753 | if (allow_skip) return error.MakeSkipped; |
| 754 | 754 | |
| 755 | const host_dl = b.host.dynamic_linker.get() orelse "(none)"; | |
| 755 | const host_dl = b.host.target.dynamic_linker.get() orelse "(none)"; | |
| 756 | 756 | |
| 757 | 757 | return step.fail( |
| 758 | 758 | \\the host system is unable to execute binaries from the target |
lib/std/Target.zig+46-26| ... | ... | @@ -1,7 +1,13 @@ |
| 1 | //! All the details about the machine that will be executing code. | |
| 2 | //! Unlike `Query` which might leave some things as "default" or "host", this | |
| 3 | //! data is fully resolved into a concrete set of OS versions, CPU features, | |
| 4 | //! etc. | |
| 5 | ||
| 1 | 6 | cpu: Cpu, |
| 2 | 7 | os: Os, |
| 3 | 8 | abi: Abi, |
| 4 | 9 | ofmt: ObjectFormat, |
| 10 | dynamic_linker: DynamicLinker = DynamicLinker.none, | |
| 5 | 11 | |
| 6 | 12 | pub const Query = @import("Target/Query.zig"); |
| 7 | 13 | |
| ... | ... | @@ -1529,13 +1535,19 @@ pub inline fn hasDynamicLinker(self: Target) bool { |
| 1529 | 1535 | } |
| 1530 | 1536 | |
| 1531 | 1537 | pub const DynamicLinker = struct { |
| 1532 | /// Contains the memory used to store the dynamic linker path. This field should | |
| 1533 | /// not be used directly. See `get` and `set`. This field exists so that this API requires no allocator. | |
| 1534 | buffer: [255]u8 = undefined, | |
| 1538 | /// Contains the memory used to store the dynamic linker path. This field | |
| 1539 | /// should not be used directly. See `get` and `set`. This field exists so | |
| 1540 | /// that this API requires no allocator. | |
| 1541 | buffer: [255]u8, | |
| 1535 | 1542 | |
| 1536 | 1543 | /// Used to construct the dynamic linker path. This field should not be used |
| 1537 | 1544 | /// directly. See `get` and `set`. |
| 1538 | max_byte: ?u8 = null, | |
| 1545 | max_byte: ?u8, | |
| 1546 | ||
| 1547 | pub const none: DynamicLinker = .{ | |
| 1548 | .buffer = undefined, | |
| 1549 | .max_byte = null, | |
| 1550 | }; | |
| 1539 | 1551 | |
| 1540 | 1552 | /// Asserts that the length is less than or equal to 255 bytes. |
| 1541 | 1553 | pub fn init(dl_or_null: ?[]const u8) DynamicLinker { |
| ... | ... | @@ -1561,8 +1573,12 @@ pub const DynamicLinker = struct { |
| 1561 | 1573 | } |
| 1562 | 1574 | }; |
| 1563 | 1575 | |
| 1564 | pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { | |
| 1565 | var result: DynamicLinker = .{}; | |
| 1576 | pub fn standardDynamicLinkerPath(target: Target) DynamicLinker { | |
| 1577 | return standardDynamicLinkerPath_cpu_os_abi(target.cpu, target.os.tag, target.abi); | |
| 1578 | } | |
| 1579 | ||
| 1580 | pub fn standardDynamicLinkerPath_cpu_os_abi(cpu: Cpu, os_tag: Os.Tag, abi: Abi) DynamicLinker { | |
| 1581 | var result = DynamicLinker.none; | |
| 1566 | 1582 | const S = struct { |
| 1567 | 1583 | fn print(r: *DynamicLinker, comptime fmt: []const u8, args: anytype) DynamicLinker { |
| 1568 | 1584 | r.max_byte = @as(u8, @intCast((std.fmt.bufPrint(&r.buffer, fmt, args) catch unreachable).len - 1)); |
| ... | ... | @@ -1577,32 +1593,32 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { |
| 1577 | 1593 | const print = S.print; |
| 1578 | 1594 | const copy = S.copy; |
| 1579 | 1595 | |
| 1580 | if (self.abi == .android) { | |
| 1581 | const suffix = if (self.ptrBitWidth() == 64) "64" else ""; | |
| 1596 | if (abi == .android) { | |
| 1597 | const suffix = if (ptrBitWidth_cpu_abi(cpu, abi) == 64) "64" else ""; | |
| 1582 | 1598 | return print(&result, "/system/bin/linker{s}", .{suffix}); |
| 1583 | 1599 | } |
| 1584 | 1600 | |
| 1585 | if (self.abi.isMusl()) { | |
| 1586 | const is_arm = switch (self.cpu.arch) { | |
| 1601 | if (abi.isMusl()) { | |
| 1602 | const is_arm = switch (cpu.arch) { | |
| 1587 | 1603 | .arm, .armeb, .thumb, .thumbeb => true, |
| 1588 | 1604 | else => false, |
| 1589 | 1605 | }; |
| 1590 | const arch_part = switch (self.cpu.arch) { | |
| 1606 | const arch_part = switch (cpu.arch) { | |
| 1591 | 1607 | .arm, .thumb => "arm", |
| 1592 | 1608 | .armeb, .thumbeb => "armeb", |
| 1593 | 1609 | else => |arch| @tagName(arch), |
| 1594 | 1610 | }; |
| 1595 | const arch_suffix = if (is_arm and self.abi.floatAbi() == .hard) "hf" else ""; | |
| 1611 | const arch_suffix = if (is_arm and abi.floatAbi() == .hard) "hf" else ""; | |
| 1596 | 1612 | return print(&result, "/lib/ld-musl-{s}{s}.so.1", .{ arch_part, arch_suffix }); |
| 1597 | 1613 | } |
| 1598 | 1614 | |
| 1599 | switch (self.os.tag) { | |
| 1615 | switch (os_tag) { | |
| 1600 | 1616 | .freebsd => return copy(&result, "/libexec/ld-elf.so.1"), |
| 1601 | 1617 | .netbsd => return copy(&result, "/libexec/ld.elf_so"), |
| 1602 | 1618 | .openbsd => return copy(&result, "/usr/libexec/ld.so"), |
| 1603 | 1619 | .dragonfly => return copy(&result, "/libexec/ld-elf.so.2"), |
| 1604 | 1620 | .solaris, .illumos => return copy(&result, "/lib/64/ld.so.1"), |
| 1605 | .linux => switch (self.cpu.arch) { | |
| 1621 | .linux => switch (cpu.arch) { | |
| 1606 | 1622 | .x86, |
| 1607 | 1623 | .sparc, |
| 1608 | 1624 | .sparcel, |
| ... | ... | @@ -1616,7 +1632,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { |
| 1616 | 1632 | .armeb, |
| 1617 | 1633 | .thumb, |
| 1618 | 1634 | .thumbeb, |
| 1619 | => return copy(&result, switch (self.abi.floatAbi()) { | |
| 1635 | => return copy(&result, switch (abi.floatAbi()) { | |
| 1620 | 1636 | .hard => "/lib/ld-linux-armhf.so.3", |
| 1621 | 1637 | else => "/lib/ld-linux.so.3", |
| 1622 | 1638 | }), |
| ... | ... | @@ -1626,12 +1642,12 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { |
| 1626 | 1642 | .mips64, |
| 1627 | 1643 | .mips64el, |
| 1628 | 1644 | => { |
| 1629 | const lib_suffix = switch (self.abi) { | |
| 1645 | const lib_suffix = switch (abi) { | |
| 1630 | 1646 | .gnuabin32, .gnux32 => "32", |
| 1631 | 1647 | .gnuabi64 => "64", |
| 1632 | 1648 | else => "", |
| 1633 | 1649 | }; |
| 1634 | const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008); | |
| 1650 | const is_nan_2008 = mips.featureSetHas(cpu.features, .nan2008); | |
| 1635 | 1651 | const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1"; |
| 1636 | 1652 | return print(&result, "/lib{s}/{s}", .{ lib_suffix, loader }); |
| 1637 | 1653 | }, |
| ... | ... | @@ -1640,7 +1656,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { |
| 1640 | 1656 | .powerpc64, .powerpc64le => return copy(&result, "/lib64/ld64.so.2"), |
| 1641 | 1657 | .s390x => return copy(&result, "/lib64/ld64.so.1"), |
| 1642 | 1658 | .sparc64 => return copy(&result, "/lib64/ld-linux.so.2"), |
| 1643 | .x86_64 => return copy(&result, switch (self.abi) { | |
| 1659 | .x86_64 => return copy(&result, switch (abi) { | |
| 1644 | 1660 | .gnux32 => "/libx32/ld-linux-x32.so.2", |
| 1645 | 1661 | else => "/lib64/ld-linux-x86-64.so.2", |
| 1646 | 1662 | }), |
| ... | ... | @@ -1862,17 +1878,17 @@ pub fn maxIntAlignment(target: Target) u16 { |
| 1862 | 1878 | }; |
| 1863 | 1879 | } |
| 1864 | 1880 | |
| 1865 | pub fn ptrBitWidth(target: Target) u16 { | |
| 1866 | switch (target.abi) { | |
| 1881 | pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { | |
| 1882 | switch (abi) { | |
| 1867 | 1883 | .gnux32, .muslx32, .gnuabin32, .gnuilp32 => return 32, |
| 1868 | 1884 | .gnuabi64 => return 64, |
| 1869 | 1885 | else => {}, |
| 1870 | 1886 | } |
| 1871 | switch (target.cpu.arch) { | |
| 1887 | return switch (cpu.arch) { | |
| 1872 | 1888 | .avr, |
| 1873 | 1889 | .msp430, |
| 1874 | 1890 | .spu_2, |
| 1875 | => return 16, | |
| 1891 | => 16, | |
| 1876 | 1892 | |
| 1877 | 1893 | .arc, |
| 1878 | 1894 | .arm, |
| ... | ... | @@ -1908,7 +1924,7 @@ pub fn ptrBitWidth(target: Target) u16 { |
| 1908 | 1924 | .loongarch32, |
| 1909 | 1925 | .dxil, |
| 1910 | 1926 | .xtensa, |
| 1911 | => return 32, | |
| 1927 | => 32, | |
| 1912 | 1928 | |
| 1913 | 1929 | .aarch64, |
| 1914 | 1930 | .aarch64_be, |
| ... | ... | @@ -1933,10 +1949,14 @@ pub fn ptrBitWidth(target: Target) u16 { |
| 1933 | 1949 | .ve, |
| 1934 | 1950 | .spirv64, |
| 1935 | 1951 | .loongarch64, |
| 1936 | => return 64, | |
| 1952 | => 64, | |
| 1937 | 1953 | |
| 1938 | .sparc => return if (std.Target.sparc.featureSetHas(target.cpu.features, .v9)) 64 else 32, | |
| 1939 | } | |
| 1954 | .sparc => if (std.Target.sparc.featureSetHas(cpu.features, .v9)) 64 else 32, | |
| 1955 | }; | |
| 1956 | } | |
| 1957 | ||
| 1958 | pub fn ptrBitWidth(target: Target) u16 { | |
| 1959 | return ptrBitWidth_cpu_abi(target.cpu, target.abi); | |
| 1940 | 1960 | } |
| 1941 | 1961 | |
| 1942 | 1962 | pub fn stackAlignment(target: Target) u16 { |
lib/std/Target/Query.zig+12-14| ... | ... | @@ -34,7 +34,7 @@ abi: ?Target.Abi = null, |
| 34 | 34 | |
| 35 | 35 | /// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path |
| 36 | 36 | /// based on the `os_tag`. |
| 37 | dynamic_linker: DynamicLinker = DynamicLinker{}, | |
| 37 | dynamic_linker: Target.DynamicLinker = Target.DynamicLinker.none, | |
| 38 | 38 | |
| 39 | 39 | /// `null` means default for the cpu/arch/os combo. |
| 40 | 40 | ofmt: ?Target.ObjectFormat = null, |
| ... | ... | @@ -61,8 +61,6 @@ pub const OsVersion = union(enum) { |
| 61 | 61 | |
| 62 | 62 | pub const SemanticVersion = std.SemanticVersion; |
| 63 | 63 | |
| 64 | pub const DynamicLinker = Target.DynamicLinker; | |
| 65 | ||
| 66 | 64 | pub fn fromTarget(target: Target) Query { |
| 67 | 65 | var result: Query = .{ |
| 68 | 66 | .cpu_arch = target.cpu.arch, |
| ... | ... | @@ -164,7 +162,7 @@ fn updateOsVersionRange(self: *Query, os: Target.Os) void { |
| 164 | 162 | } |
| 165 | 163 | } |
| 166 | 164 | |
| 167 | /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. | |
| 165 | /// TODO deprecated, use `std.zig.system.resolveTargetQuery`. | |
| 168 | 166 | pub fn toTarget(self: Query) Target { |
| 169 | 167 | return .{ |
| 170 | 168 | .cpu = self.getCpu(), |
| ... | ... | @@ -232,7 +230,7 @@ pub fn parse(args: ParseOptions) !Query { |
| 232 | 230 | const diags = args.diagnostics orelse &dummy_diags; |
| 233 | 231 | |
| 234 | 232 | var result: Query = .{ |
| 235 | .dynamic_linker = DynamicLinker.init(args.dynamic_linker), | |
| 233 | .dynamic_linker = Target.DynamicLinker.init(args.dynamic_linker), | |
| 236 | 234 | }; |
| 237 | 235 | |
| 238 | 236 | var it = mem.splitScalar(u8, args.arch_os_abi, '-'); |
| ... | ... | @@ -379,13 +377,13 @@ test parseVersion { |
| 379 | 377 | try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3.4")); |
| 380 | 378 | } |
| 381 | 379 | |
| 382 | /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. | |
| 380 | /// TODO deprecated, use `std.zig.system.resolveTargetQuery`. | |
| 383 | 381 | pub fn getCpu(self: Query) Target.Cpu { |
| 384 | 382 | switch (self.cpu_model) { |
| 385 | 383 | .native => { |
| 386 | 384 | // This works when doing `zig build` because Zig generates a build executable using |
| 387 | 385 | // native CPU model & features. However this will not be accurate otherwise, and |
| 388 | // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. | |
| 386 | // will need to be integrated with `std.zig.system.resolveTargetQuery`. | |
| 389 | 387 | return builtin.cpu; |
| 390 | 388 | }, |
| 391 | 389 | .baseline => { |
| ... | ... | @@ -396,7 +394,7 @@ pub fn getCpu(self: Query) Target.Cpu { |
| 396 | 394 | .determined_by_cpu_arch => if (self.cpu_arch == null) { |
| 397 | 395 | // This works when doing `zig build` because Zig generates a build executable using |
| 398 | 396 | // native CPU model & features. However this will not be accurate otherwise, and |
| 399 | // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. | |
| 397 | // will need to be integrated with `std.zig.system.resolveTargetQuery`. | |
| 400 | 398 | return builtin.cpu; |
| 401 | 399 | } else { |
| 402 | 400 | var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); |
| ... | ... | @@ -426,11 +424,11 @@ pub fn getCpuFeatures(self: Query) Target.Cpu.Feature.Set { |
| 426 | 424 | return self.getCpu().features; |
| 427 | 425 | } |
| 428 | 426 | |
| 429 | /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. | |
| 427 | /// TODO deprecated, use `std.zig.system.resolveTargetQuery`. | |
| 430 | 428 | pub fn getOs(self: Query) Target.Os { |
| 431 | 429 | // `builtin.os` works when doing `zig build` because Zig generates a build executable using |
| 432 | 430 | // native OS version range. However this will not be accurate otherwise, and |
| 433 | // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. | |
| 431 | // will need to be integrated with `std.zig.system.resolveTargetQuery`. | |
| 434 | 432 | var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange(self.getCpuArch()) else builtin.os; |
| 435 | 433 | |
| 436 | 434 | if (self.os_version_min) |min| switch (min) { |
| ... | ... | @@ -463,7 +461,7 @@ pub fn getOsTag(self: Query) Target.Os.Tag { |
| 463 | 461 | return self.os_tag orelse builtin.os.tag; |
| 464 | 462 | } |
| 465 | 463 | |
| 466 | /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. | |
| 464 | /// TODO deprecated, use `std.zig.system.resolveTargetQuery`. | |
| 467 | 465 | pub fn getOsVersionMin(self: Query) OsVersion { |
| 468 | 466 | if (self.os_version_min) |version_min| return version_min; |
| 469 | 467 | var tmp: Query = undefined; |
| ... | ... | @@ -471,7 +469,7 @@ pub fn getOsVersionMin(self: Query) OsVersion { |
| 471 | 469 | return tmp.os_version_min.?; |
| 472 | 470 | } |
| 473 | 471 | |
| 474 | /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. | |
| 472 | /// TODO deprecated, use `std.zig.system.resolveTargetQuery`. | |
| 475 | 473 | pub fn getOsVersionMax(self: Query) OsVersion { |
| 476 | 474 | if (self.os_version_max) |version_max| return version_max; |
| 477 | 475 | var tmp: Query = undefined; |
| ... | ... | @@ -479,14 +477,14 @@ pub fn getOsVersionMax(self: Query) OsVersion { |
| 479 | 477 | return tmp.os_version_max.?; |
| 480 | 478 | } |
| 481 | 479 | |
| 482 | /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. | |
| 480 | /// TODO deprecated, use `std.zig.system.resolveTargetQuery`. | |
| 483 | 481 | pub fn getAbi(self: Query) Target.Abi { |
| 484 | 482 | if (self.abi) |abi| return abi; |
| 485 | 483 | |
| 486 | 484 | if (self.os_tag == null) { |
| 487 | 485 | // This works when doing `zig build` because Zig generates a build executable using |
| 488 | 486 | // native CPU model & features. However this will not be accurate otherwise, and |
| 489 | // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. | |
| 487 | // will need to be integrated with `std.zig.system.resolveTargetQuery`. | |
| 490 | 488 | return builtin.abi; |
| 491 | 489 | } |
| 492 | 490 |
lib/std/zig/system.zig+1116-2| ... | ... | @@ -1,13 +1,1127 @@ |
| 1 | 1 | pub const NativePaths = @import("system/NativePaths.zig"); |
| 2 | pub const NativeTargetInfo = @import("system/NativeTargetInfo.zig"); | |
| 3 | 2 | |
| 4 | 3 | pub const windows = @import("system/windows.zig"); |
| 5 | 4 | pub const darwin = @import("system/darwin.zig"); |
| 6 | 5 | pub const linux = @import("system/linux.zig"); |
| 7 | 6 | |
| 7 | pub const Executor = union(enum) { | |
| 8 | native, | |
| 9 | rosetta, | |
| 10 | qemu: []const u8, | |
| 11 | wine: []const u8, | |
| 12 | wasmtime: []const u8, | |
| 13 | darling: []const u8, | |
| 14 | bad_dl: []const u8, | |
| 15 | bad_os_or_cpu, | |
| 16 | }; | |
| 17 | ||
| 18 | pub const GetExternalExecutorOptions = struct { | |
| 19 | allow_darling: bool = true, | |
| 20 | allow_qemu: bool = true, | |
| 21 | allow_rosetta: bool = true, | |
| 22 | allow_wasmtime: bool = true, | |
| 23 | allow_wine: bool = true, | |
| 24 | qemu_fixes_dl: bool = false, | |
| 25 | link_libc: bool = false, | |
| 26 | }; | |
| 27 | ||
| 28 | /// Return whether or not the given host is capable of running executables of | |
| 29 | /// the other target. | |
| 30 | pub fn getExternalExecutor( | |
| 31 | host: std.Target, | |
| 32 | candidate: *const std.Target, | |
| 33 | options: GetExternalExecutorOptions, | |
| 34 | ) Executor { | |
| 35 | const os_match = host.os.tag == candidate.os.tag; | |
| 36 | const cpu_ok = cpu_ok: { | |
| 37 | if (host.cpu.arch == candidate.cpu.arch) | |
| 38 | break :cpu_ok true; | |
| 39 | ||
| 40 | if (host.cpu.arch == .x86_64 and candidate.cpu.arch == .x86) | |
| 41 | break :cpu_ok true; | |
| 42 | ||
| 43 | if (host.cpu.arch == .aarch64 and candidate.cpu.arch == .arm) | |
| 44 | break :cpu_ok true; | |
| 45 | ||
| 46 | if (host.cpu.arch == .aarch64_be and candidate.cpu.arch == .armeb) | |
| 47 | break :cpu_ok true; | |
| 48 | ||
| 49 | // TODO additionally detect incompatible CPU features. | |
| 50 | // Note that in some cases the OS kernel will emulate missing CPU features | |
| 51 | // when an illegal instruction is encountered. | |
| 52 | ||
| 53 | break :cpu_ok false; | |
| 54 | }; | |
| 55 | ||
| 56 | var bad_result: Executor = .bad_os_or_cpu; | |
| 57 | ||
| 58 | if (os_match and cpu_ok) native: { | |
| 59 | if (options.link_libc) { | |
| 60 | if (candidate.dynamic_linker.get()) |candidate_dl| { | |
| 61 | fs.cwd().access(candidate_dl, .{}) catch { | |
| 62 | bad_result = .{ .bad_dl = candidate_dl }; | |
| 63 | break :native; | |
| 64 | }; | |
| 65 | } | |
| 66 | } | |
| 67 | return .native; | |
| 68 | } | |
| 69 | ||
| 70 | // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 | |
| 71 | // to emulate the foreign architecture. | |
| 72 | if (options.allow_rosetta and os_match and | |
| 73 | host.os.tag == .macos and host.cpu.arch == .aarch64) | |
| 74 | { | |
| 75 | switch (candidate.cpu.arch) { | |
| 76 | .x86_64 => return .rosetta, | |
| 77 | else => return bad_result, | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | // If the OS matches, we can use QEMU to emulate a foreign architecture. | |
| 82 | if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { | |
| 83 | return switch (candidate.cpu.arch) { | |
| 84 | .aarch64 => Executor{ .qemu = "qemu-aarch64" }, | |
| 85 | .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, | |
| 86 | .arm => Executor{ .qemu = "qemu-arm" }, | |
| 87 | .armeb => Executor{ .qemu = "qemu-armeb" }, | |
| 88 | .hexagon => Executor{ .qemu = "qemu-hexagon" }, | |
| 89 | .x86 => Executor{ .qemu = "qemu-i386" }, | |
| 90 | .m68k => Executor{ .qemu = "qemu-m68k" }, | |
| 91 | .mips => Executor{ .qemu = "qemu-mips" }, | |
| 92 | .mipsel => Executor{ .qemu = "qemu-mipsel" }, | |
| 93 | .mips64 => Executor{ .qemu = "qemu-mips64" }, | |
| 94 | .mips64el => Executor{ .qemu = "qemu-mips64el" }, | |
| 95 | .powerpc => Executor{ .qemu = "qemu-ppc" }, | |
| 96 | .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, | |
| 97 | .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, | |
| 98 | .riscv32 => Executor{ .qemu = "qemu-riscv32" }, | |
| 99 | .riscv64 => Executor{ .qemu = "qemu-riscv64" }, | |
| 100 | .s390x => Executor{ .qemu = "qemu-s390x" }, | |
| 101 | .sparc => Executor{ .qemu = "qemu-sparc" }, | |
| 102 | .sparc64 => Executor{ .qemu = "qemu-sparc64" }, | |
| 103 | .x86_64 => Executor{ .qemu = "qemu-x86_64" }, | |
| 104 | else => return bad_result, | |
| 105 | }; | |
| 106 | } | |
| 107 | ||
| 108 | switch (candidate.os.tag) { | |
| 109 | .windows => { | |
| 110 | if (options.allow_wine) { | |
| 111 | // x86_64 wine does not support emulating aarch64-windows and | |
| 112 | // vice versa. | |
| 113 | if (candidate.cpu.arch != builtin.cpu.arch) { | |
| 114 | return bad_result; | |
| 115 | } | |
| 116 | switch (candidate.ptrBitWidth()) { | |
| 117 | 32 => return Executor{ .wine = "wine" }, | |
| 118 | 64 => return Executor{ .wine = "wine64" }, | |
| 119 | else => return bad_result, | |
| 120 | } | |
| 121 | } | |
| 122 | return bad_result; | |
| 123 | }, | |
| 124 | .wasi => { | |
| 125 | if (options.allow_wasmtime) { | |
| 126 | switch (candidate.ptrBitWidth()) { | |
| 127 | 32 => return Executor{ .wasmtime = "wasmtime" }, | |
| 128 | else => return bad_result, | |
| 129 | } | |
| 130 | } | |
| 131 | return bad_result; | |
| 132 | }, | |
| 133 | .macos => { | |
| 134 | if (options.allow_darling) { | |
| 135 | // This check can be loosened once darling adds a QEMU-based emulation | |
| 136 | // layer for non-host architectures: | |
| 137 | // https://github.com/darlinghq/darling/issues/863 | |
| 138 | if (candidate.cpu.arch != builtin.cpu.arch) { | |
| 139 | return bad_result; | |
| 140 | } | |
| 141 | return Executor{ .darling = "darling" }; | |
| 142 | } | |
| 143 | return bad_result; | |
| 144 | }, | |
| 145 | else => return bad_result, | |
| 146 | } | |
| 147 | } | |
| 148 | ||
| 149 | pub const DetectError = error{ | |
| 150 | FileSystem, | |
| 151 | SystemResources, | |
| 152 | SymLinkLoop, | |
| 153 | ProcessFdQuotaExceeded, | |
| 154 | SystemFdQuotaExceeded, | |
| 155 | DeviceBusy, | |
| 156 | OSVersionDetectionFail, | |
| 157 | Unexpected, | |
| 158 | }; | |
| 159 | ||
| 160 | /// Given a `Target.Query`, which specifies in detail which parts of the | |
| 161 | /// target should be detected natively, which should be standard or default, | |
| 162 | /// and which are provided explicitly, this function resolves the native | |
| 163 | /// components by detecting the native system, and then resolves | |
| 164 | /// standard/default parts relative to that. | |
| 165 | pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { | |
| 166 | var os = query.getOsTag().defaultVersionRange(query.getCpuArch()); | |
| 167 | if (query.os_tag == null) { | |
| 168 | switch (builtin.target.os.tag) { | |
| 169 | .linux => { | |
| 170 | const uts = std.os.uname(); | |
| 171 | const release = mem.sliceTo(&uts.release, 0); | |
| 172 | // The release field sometimes has a weird format, | |
| 173 | // `Version.parse` will attempt to find some meaningful interpretation. | |
| 174 | if (std.SemanticVersion.parse(release)) |ver| { | |
| 175 | os.version_range.linux.range.min = ver; | |
| 176 | os.version_range.linux.range.max = ver; | |
| 177 | } else |err| switch (err) { | |
| 178 | error.Overflow => {}, | |
| 179 | error.InvalidVersion => {}, | |
| 180 | } | |
| 181 | }, | |
| 182 | .solaris, .illumos => { | |
| 183 | const uts = std.os.uname(); | |
| 184 | const release = mem.sliceTo(&uts.release, 0); | |
| 185 | if (std.SemanticVersion.parse(release)) |ver| { | |
| 186 | os.version_range.semver.min = ver; | |
| 187 | os.version_range.semver.max = ver; | |
| 188 | } else |err| switch (err) { | |
| 189 | error.Overflow => {}, | |
| 190 | error.InvalidVersion => {}, | |
| 191 | } | |
| 192 | }, | |
| 193 | .windows => { | |
| 194 | const detected_version = windows.detectRuntimeVersion(); | |
| 195 | os.version_range.windows.min = detected_version; | |
| 196 | os.version_range.windows.max = detected_version; | |
| 197 | }, | |
| 198 | .macos => try darwin.macos.detect(&os), | |
| 199 | .freebsd, .netbsd, .dragonfly => { | |
| 200 | const key = switch (builtin.target.os.tag) { | |
| 201 | .freebsd => "kern.osreldate", | |
| 202 | .netbsd, .dragonfly => "kern.osrevision", | |
| 203 | else => unreachable, | |
| 204 | }; | |
| 205 | var value: u32 = undefined; | |
| 206 | var len: usize = @sizeOf(@TypeOf(value)); | |
| 207 | ||
| 208 | std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { | |
| 209 | error.NameTooLong => unreachable, // constant, known good value | |
| 210 | error.PermissionDenied => unreachable, // only when setting values, | |
| 211 | error.SystemResources => unreachable, // memory already on the stack | |
| 212 | error.UnknownName => unreachable, // constant, known good value | |
| 213 | error.Unexpected => return error.OSVersionDetectionFail, | |
| 214 | }; | |
| 215 | ||
| 216 | switch (builtin.target.os.tag) { | |
| 217 | .freebsd => { | |
| 218 | // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html | |
| 219 | // Major * 100,000 has been convention since FreeBSD 2.2 (1997) | |
| 220 | // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997) | |
| 221 | // e.g. 492101 = 4.11-STABLE = 4.(9+2) | |
| 222 | const major = value / 100_000; | |
| 223 | const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1 | |
| 224 | const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since | |
| 225 | const patch = value % 1_000; | |
| 226 | os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch }; | |
| 227 | os.version_range.semver.max = os.version_range.semver.min; | |
| 228 | }, | |
| 229 | .netbsd => { | |
| 230 | // #define __NetBSD_Version__ MMmmrrpp00 | |
| 231 | // | |
| 232 | // M = major version | |
| 233 | // m = minor version; a minor number of 99 indicates current. | |
| 234 | // r = 0 (*) | |
| 235 | // p = patchlevel | |
| 236 | const major = value / 100_000_000; | |
| 237 | const minor = value % 100_000_000 / 1_000_000; | |
| 238 | const patch = value % 10_000 / 100; | |
| 239 | os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; | |
| 240 | os.version_range.semver.max = os.version_range.semver.min; | |
| 241 | }, | |
| 242 | .dragonfly => { | |
| 243 | // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17 | |
| 244 | // flat base10 format: Mmmmpp | |
| 245 | // M = major | |
| 246 | // m = minor; odd-numbers indicate current dev branch | |
| 247 | // p = patch | |
| 248 | const major = value / 100_000; | |
| 249 | const minor = value % 100_000 / 100; | |
| 250 | const patch = value % 100; | |
| 251 | os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; | |
| 252 | os.version_range.semver.max = os.version_range.semver.min; | |
| 253 | }, | |
| 254 | else => unreachable, | |
| 255 | } | |
| 256 | }, | |
| 257 | .openbsd => { | |
| 258 | const mib: [2]c_int = [_]c_int{ | |
| 259 | std.os.CTL.KERN, | |
| 260 | std.os.KERN.OSRELEASE, | |
| 261 | }; | |
| 262 | var buf: [64]u8 = undefined; | |
| 263 | // consider that sysctl result includes null-termination | |
| 264 | // reserve 1 byte to ensure we never overflow when appending ".0" | |
| 265 | var len: usize = buf.len - 1; | |
| 266 | ||
| 267 | std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { | |
| 268 | error.NameTooLong => unreachable, // constant, known good value | |
| 269 | error.PermissionDenied => unreachable, // only when setting values, | |
| 270 | error.SystemResources => unreachable, // memory already on the stack | |
| 271 | error.UnknownName => unreachable, // constant, known good value | |
| 272 | error.Unexpected => return error.OSVersionDetectionFail, | |
| 273 | }; | |
| 274 | ||
| 275 | // append ".0" to satisfy semver | |
| 276 | buf[len - 1] = '.'; | |
| 277 | buf[len] = '0'; | |
| 278 | len += 1; | |
| 279 | ||
| 280 | if (std.SemanticVersion.parse(buf[0..len])) |ver| { | |
| 281 | os.version_range.semver.min = ver; | |
| 282 | os.version_range.semver.max = ver; | |
| 283 | } else |_| { | |
| 284 | return error.OSVersionDetectionFail; | |
| 285 | } | |
| 286 | }, | |
| 287 | else => { | |
| 288 | // Unimplemented, fall back to default version range. | |
| 289 | }, | |
| 290 | } | |
| 291 | } | |
| 292 | ||
| 293 | if (query.os_version_min) |min| switch (min) { | |
| 294 | .none => {}, | |
| 295 | .semver => |semver| switch (query.getOsTag()) { | |
| 296 | .linux => os.version_range.linux.range.min = semver, | |
| 297 | else => os.version_range.semver.min = semver, | |
| 298 | }, | |
| 299 | .windows => |win_ver| os.version_range.windows.min = win_ver, | |
| 300 | }; | |
| 301 | ||
| 302 | if (query.os_version_max) |max| switch (max) { | |
| 303 | .none => {}, | |
| 304 | .semver => |semver| switch (query.getOsTag()) { | |
| 305 | .linux => os.version_range.linux.range.max = semver, | |
| 306 | else => os.version_range.semver.max = semver, | |
| 307 | }, | |
| 308 | .windows => |win_ver| os.version_range.windows.max = win_ver, | |
| 309 | }; | |
| 310 | ||
| 311 | if (query.glibc_version) |glibc| { | |
| 312 | assert(query.isGnuLibC()); | |
| 313 | os.version_range.linux.glibc = glibc; | |
| 314 | } | |
| 315 | ||
| 316 | // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the | |
| 317 | // native CPU architecture as being different than the current target), we use this: | |
| 318 | const cpu_arch = query.getCpuArch(); | |
| 319 | ||
| 320 | const cpu = switch (query.cpu_model) { | |
| 321 | .native => detectNativeCpuAndFeatures(cpu_arch, os, query), | |
| 322 | .baseline => Target.Cpu.baseline(cpu_arch), | |
| 323 | .determined_by_cpu_arch => if (query.cpu_arch == null) | |
| 324 | detectNativeCpuAndFeatures(cpu_arch, os, query) | |
| 325 | else | |
| 326 | Target.Cpu.baseline(cpu_arch), | |
| 327 | .explicit => |model| model.toCpu(cpu_arch), | |
| 328 | } orelse backup_cpu_detection: { | |
| 329 | break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); | |
| 330 | }; | |
| 331 | var result = try detectAbiAndDynamicLinker(cpu, os, query); | |
| 332 | // For x86, we need to populate some CPU feature flags depending on architecture | |
| 333 | // and mode: | |
| 334 | // * 16bit_mode => if the abi is code16 | |
| 335 | // * 32bit_mode => if the arch is x86 | |
| 336 | // However, the "mode" flags can be used as overrides, so if the user explicitly | |
| 337 | // sets one of them, that takes precedence. | |
| 338 | switch (cpu_arch) { | |
| 339 | .x86 => { | |
| 340 | if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{ | |
| 341 | .@"16bit_mode", .@"32bit_mode", | |
| 342 | })) { | |
| 343 | switch (result.abi) { | |
| 344 | .code16 => result.cpu.features.addFeature( | |
| 345 | @intFromEnum(Target.x86.Feature.@"16bit_mode"), | |
| 346 | ), | |
| 347 | else => result.cpu.features.addFeature( | |
| 348 | @intFromEnum(Target.x86.Feature.@"32bit_mode"), | |
| 349 | ), | |
| 350 | } | |
| 351 | } | |
| 352 | }, | |
| 353 | .arm, .armeb => { | |
| 354 | // XXX What do we do if the target has the noarm feature? | |
| 355 | // What do we do if the user specifies +thumb_mode? | |
| 356 | }, | |
| 357 | .thumb, .thumbeb => { | |
| 358 | result.cpu.features.addFeature( | |
| 359 | @intFromEnum(Target.arm.Feature.thumb_mode), | |
| 360 | ); | |
| 361 | }, | |
| 362 | else => {}, | |
| 363 | } | |
| 364 | query.updateCpuFeatures(&result.cpu.features); | |
| 365 | return result; | |
| 366 | } | |
| 367 | ||
| 368 | fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu { | |
| 369 | // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`, | |
| 370 | // although it is a runtime value, is guaranteed to be one of the architectures in the set | |
| 371 | // of the respective switch prong. | |
| 372 | switch (builtin.cpu.arch) { | |
| 373 | .x86_64, .x86 => { | |
| 374 | return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query); | |
| 375 | }, | |
| 376 | else => {}, | |
| 377 | } | |
| 378 | ||
| 379 | switch (builtin.os.tag) { | |
| 380 | .linux => return linux.detectNativeCpuAndFeatures(), | |
| 381 | .macos => return darwin.macos.detectNativeCpuAndFeatures(), | |
| 382 | .windows => return windows.detectNativeCpuAndFeatures(), | |
| 383 | else => {}, | |
| 384 | } | |
| 385 | ||
| 386 | // This architecture does not have CPU model & feature detection yet. | |
| 387 | // See https://github.com/ziglang/zig/issues/4591 | |
| 388 | return null; | |
| 389 | } | |
| 390 | ||
| 391 | pub const AbiAndDynamicLinkerFromFileError = error{ | |
| 392 | FileSystem, | |
| 393 | SystemResources, | |
| 394 | SymLinkLoop, | |
| 395 | ProcessFdQuotaExceeded, | |
| 396 | SystemFdQuotaExceeded, | |
| 397 | UnableToReadElfFile, | |
| 398 | InvalidElfClass, | |
| 399 | InvalidElfVersion, | |
| 400 | InvalidElfEndian, | |
| 401 | InvalidElfFile, | |
| 402 | InvalidElfMagic, | |
| 403 | Unexpected, | |
| 404 | UnexpectedEndOfFile, | |
| 405 | NameTooLong, | |
| 406 | }; | |
| 407 | ||
| 408 | pub fn abiAndDynamicLinkerFromFile( | |
| 409 | file: fs.File, | |
| 410 | cpu: Target.Cpu, | |
| 411 | os: Target.Os, | |
| 412 | ld_info_list: []const LdInfo, | |
| 413 | query: Target.Query, | |
| 414 | ) AbiAndDynamicLinkerFromFileError!Target { | |
| 415 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; | |
| 416 | _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); | |
| 417 | const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); | |
| 418 | const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); | |
| 419 | if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 420 | const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { | |
| 421 | elf.ELFDATA2LSB => .little, | |
| 422 | elf.ELFDATA2MSB => .big, | |
| 423 | else => return error.InvalidElfEndian, | |
| 424 | }; | |
| 425 | const need_bswap = elf_endian != native_endian; | |
| 426 | if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 427 | ||
| 428 | const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { | |
| 429 | elf.ELFCLASS32 => false, | |
| 430 | elf.ELFCLASS64 => true, | |
| 431 | else => return error.InvalidElfClass, | |
| 432 | }; | |
| 433 | var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); | |
| 434 | const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); | |
| 435 | const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); | |
| 436 | ||
| 437 | var result: Target = .{ | |
| 438 | .cpu = cpu, | |
| 439 | .os = os, | |
| 440 | .abi = query.abi orelse Target.Abi.default(cpu.arch, os), | |
| 441 | .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), | |
| 442 | .dynamic_linker = query.dynamic_linker, | |
| 443 | }; | |
| 444 | var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC | |
| 445 | const look_for_ld = query.dynamic_linker.get() == null; | |
| 446 | ||
| 447 | var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; | |
| 448 | if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; | |
| 449 | ||
| 450 | var ph_i: u16 = 0; | |
| 451 | while (ph_i < phnum) { | |
| 452 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 453 | // even when the ELF file is 32-bits. | |
| 454 | const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); | |
| 455 | const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); | |
| 456 | var ph_buf_i: usize = 0; | |
| 457 | while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ | |
| 458 | ph_i += 1; | |
| 459 | phoff += phentsize; | |
| 460 | ph_buf_i += phentsize; | |
| 461 | }) { | |
| 462 | const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); | |
| 463 | const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); | |
| 464 | const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); | |
| 465 | switch (p_type) { | |
| 466 | elf.PT_INTERP => if (look_for_ld) { | |
| 467 | const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); | |
| 468 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); | |
| 469 | if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; | |
| 470 | const filesz = @as(usize, @intCast(p_filesz)); | |
| 471 | _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz); | |
| 472 | // PT_INTERP includes a null byte in filesz. | |
| 473 | const len = filesz - 1; | |
| 474 | // dynamic_linker.max_byte is "max", not "len". | |
| 475 | // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. | |
| 476 | result.dynamic_linker.max_byte = @as(u8, @intCast(len - 1)); | |
| 477 | ||
| 478 | // Use it to determine ABI. | |
| 479 | const full_ld_path = result.dynamic_linker.buffer[0..len]; | |
| 480 | for (ld_info_list) |ld_info| { | |
| 481 | const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); | |
| 482 | if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { | |
| 483 | result.abi = ld_info.abi; | |
| 484 | break; | |
| 485 | } | |
| 486 | } | |
| 487 | }, | |
| 488 | // We only need this for detecting glibc version. | |
| 489 | elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.isGnuLibC() and | |
| 490 | query.glibc_version == null) | |
| 491 | { | |
| 492 | var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); | |
| 493 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); | |
| 494 | const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); | |
| 495 | const dyn_num = p_filesz / dyn_size; | |
| 496 | var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; | |
| 497 | var dyn_i: usize = 0; | |
| 498 | dyn: while (dyn_i < dyn_num) { | |
| 499 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 500 | // even when the ELF file is 32-bits. | |
| 501 | const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); | |
| 502 | const dyn_read_byte_len = try preadMin( | |
| 503 | file, | |
| 504 | dyn_buf[0 .. dyn_buf.len - dyn_reserve], | |
| 505 | dyn_off, | |
| 506 | dyn_size, | |
| 507 | ); | |
| 508 | var dyn_buf_i: usize = 0; | |
| 509 | while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ | |
| 510 | dyn_i += 1; | |
| 511 | dyn_off += dyn_size; | |
| 512 | dyn_buf_i += dyn_size; | |
| 513 | }) { | |
| 514 | const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); | |
| 515 | const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); | |
| 516 | const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); | |
| 517 | const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); | |
| 518 | if (tag == elf.DT_RUNPATH) { | |
| 519 | rpath_offset = val; | |
| 520 | break :dyn; | |
| 521 | } | |
| 522 | } | |
| 523 | } | |
| 524 | }, | |
| 525 | else => continue, | |
| 526 | } | |
| 527 | } | |
| 528 | } | |
| 529 | ||
| 530 | if (builtin.target.os.tag == .linux and result.isGnuLibC() and | |
| 531 | query.glibc_version == null) | |
| 532 | { | |
| 533 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); | |
| 534 | ||
| 535 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); | |
| 536 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); | |
| 537 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); | |
| 538 | ||
| 539 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; | |
| 540 | if (sh_buf.len < shentsize) return error.InvalidElfFile; | |
| 541 | ||
| 542 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); | |
| 543 | const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 544 | const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 545 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); | |
| 546 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); | |
| 547 | var strtab_buf: [4096:0]u8 = undefined; | |
| 548 | const shstrtab_len = @min(shstrtab_size, strtab_buf.len); | |
| 549 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); | |
| 550 | const shstrtab = strtab_buf[0..shstrtab_read_len]; | |
| 551 | ||
| 552 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); | |
| 553 | var sh_i: u16 = 0; | |
| 554 | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { | |
| 555 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 556 | // even when the ELF file is 32-bits. | |
| 557 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); | |
| 558 | const sh_read_byte_len = try preadMin( | |
| 559 | file, | |
| 560 | sh_buf[0 .. sh_buf.len - sh_reserve], | |
| 561 | shoff, | |
| 562 | shentsize, | |
| 563 | ); | |
| 564 | var sh_buf_i: usize = 0; | |
| 565 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ | |
| 566 | sh_i += 1; | |
| 567 | shoff += shentsize; | |
| 568 | sh_buf_i += shentsize; | |
| 569 | }) { | |
| 570 | const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 571 | const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 572 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); | |
| 573 | const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); | |
| 574 | if (mem.eql(u8, sh_name, ".dynstr")) { | |
| 575 | break :find_dyn_str .{ | |
| 576 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), | |
| 577 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), | |
| 578 | }; | |
| 579 | } | |
| 580 | } | |
| 581 | } else null; | |
| 582 | ||
| 583 | if (dynstr) |ds| { | |
| 584 | if (rpath_offset) |rpoff| { | |
| 585 | if (rpoff > ds.size) return error.InvalidElfFile; | |
| 586 | const rpoff_file = ds.offset + rpoff; | |
| 587 | const rp_max_size = ds.size - rpoff; | |
| 588 | ||
| 589 | const strtab_len = @min(rp_max_size, strtab_buf.len); | |
| 590 | const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len); | |
| 591 | const strtab = strtab_buf[0..strtab_read_len]; | |
| 592 | ||
| 593 | const rpath_list = mem.sliceTo(strtab, 0); | |
| 594 | var it = mem.tokenizeScalar(u8, rpath_list, ':'); | |
| 595 | while (it.next()) |rpath| { | |
| 596 | if (glibcVerFromRPath(rpath)) |ver| { | |
| 597 | result.os.version_range.linux.glibc = ver; | |
| 598 | return result; | |
| 599 | } else |err| switch (err) { | |
| 600 | error.GLibCNotFound => continue, | |
| 601 | else => |e| return e, | |
| 602 | } | |
| 603 | } | |
| 604 | } | |
| 605 | } | |
| 606 | ||
| 607 | if (result.dynamic_linker.get()) |dl_path| glibc_ver: { | |
| 608 | // There is no DT_RUNPATH so we try to find libc.so.6 inside the same | |
| 609 | // directory as the dynamic linker. | |
| 610 | if (fs.path.dirname(dl_path)) |rpath| { | |
| 611 | if (glibcVerFromRPath(rpath)) |ver| { | |
| 612 | result.os.version_range.linux.glibc = ver; | |
| 613 | return result; | |
| 614 | } else |err| switch (err) { | |
| 615 | error.GLibCNotFound => {}, | |
| 616 | else => |e| return e, | |
| 617 | } | |
| 618 | } | |
| 619 | ||
| 620 | // So far, no luck. Next we try to see if the information is | |
| 621 | // present in the symlink data for the dynamic linker path. | |
| 622 | var link_buf: [std.os.PATH_MAX]u8 = undefined; | |
| 623 | const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { | |
| 624 | error.NameTooLong => unreachable, | |
| 625 | error.InvalidUtf8 => unreachable, // Windows only | |
| 626 | error.BadPathName => unreachable, // Windows only | |
| 627 | error.UnsupportedReparsePointType => unreachable, // Windows only | |
| 628 | error.NetworkNotFound => unreachable, // Windows only | |
| 629 | ||
| 630 | error.AccessDenied, | |
| 631 | error.FileNotFound, | |
| 632 | error.NotLink, | |
| 633 | error.NotDir, | |
| 634 | => break :glibc_ver, | |
| 635 | ||
| 636 | error.SystemResources, | |
| 637 | error.FileSystem, | |
| 638 | error.SymLinkLoop, | |
| 639 | error.Unexpected, | |
| 640 | => |e| return e, | |
| 641 | }; | |
| 642 | result.os.version_range.linux.glibc = glibcVerFromLinkName( | |
| 643 | fs.path.basename(link_name), | |
| 644 | "ld-", | |
| 645 | ) catch |err| switch (err) { | |
| 646 | error.UnrecognizedGnuLibCFileName, | |
| 647 | error.InvalidGnuLibCVersion, | |
| 648 | => break :glibc_ver, | |
| 649 | }; | |
| 650 | return result; | |
| 651 | } | |
| 652 | ||
| 653 | // Nothing worked so far. Finally we fall back to hard-coded search paths. | |
| 654 | // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`. | |
| 655 | var path_buf: [std.os.PATH_MAX]u8 = undefined; | |
| 656 | var index: usize = 0; | |
| 657 | const prefix = "/lib/"; | |
| 658 | const cpu_arch = @tagName(result.cpu.arch); | |
| 659 | const os_tag = @tagName(result.os.tag); | |
| 660 | const abi = @tagName(result.abi); | |
| 661 | @memcpy(path_buf[index..][0..prefix.len], prefix); | |
| 662 | index += prefix.len; | |
| 663 | @memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch); | |
| 664 | index += cpu_arch.len; | |
| 665 | path_buf[index] = '-'; | |
| 666 | index += 1; | |
| 667 | @memcpy(path_buf[index..][0..os_tag.len], os_tag); | |
| 668 | index += os_tag.len; | |
| 669 | path_buf[index] = '-'; | |
| 670 | index += 1; | |
| 671 | @memcpy(path_buf[index..][0..abi.len], abi); | |
| 672 | index += abi.len; | |
| 673 | const rpath = path_buf[0..index]; | |
| 674 | if (glibcVerFromRPath(rpath)) |ver| { | |
| 675 | result.os.version_range.linux.glibc = ver; | |
| 676 | return result; | |
| 677 | } else |err| switch (err) { | |
| 678 | error.GLibCNotFound => {}, | |
| 679 | else => |e| return e, | |
| 680 | } | |
| 681 | } | |
| 682 | ||
| 683 | return result; | |
| 684 | } | |
| 685 | ||
| 686 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion { | |
| 687 | // example: "libc-2.3.4.so" | |
| 688 | // example: "libc-2.27.so" | |
| 689 | // example: "ld-2.33.so" | |
| 690 | const suffix = ".so"; | |
| 691 | if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { | |
| 692 | return error.UnrecognizedGnuLibCFileName; | |
| 693 | } | |
| 694 | // chop off "libc-" and ".so" | |
| 695 | const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; | |
| 696 | return Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) { | |
| 697 | error.Overflow => return error.InvalidGnuLibCVersion, | |
| 698 | error.InvalidVersion => return error.InvalidGnuLibCVersion, | |
| 699 | }; | |
| 700 | } | |
| 701 | ||
| 702 | test glibcVerFromLinkName { | |
| 703 | try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist")); | |
| 704 | try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-")); | |
| 705 | ||
| 706 | try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-")); | |
| 707 | try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-")); | |
| 708 | try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-")); | |
| 709 | try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-")); | |
| 710 | try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-")); | |
| 711 | } | |
| 712 | ||
| 713 | fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { | |
| 714 | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { | |
| 715 | error.NameTooLong => unreachable, | |
| 716 | error.InvalidUtf8 => unreachable, | |
| 717 | error.BadPathName => unreachable, | |
| 718 | error.DeviceBusy => unreachable, | |
| 719 | error.NetworkNotFound => unreachable, // Windows-only | |
| 720 | ||
| 721 | error.FileNotFound, | |
| 722 | error.NotDir, | |
| 723 | error.InvalidHandle, | |
| 724 | error.AccessDenied, | |
| 725 | error.NoDevice, | |
| 726 | => return error.GLibCNotFound, | |
| 727 | ||
| 728 | error.ProcessFdQuotaExceeded, | |
| 729 | error.SystemFdQuotaExceeded, | |
| 730 | error.SystemResources, | |
| 731 | error.SymLinkLoop, | |
| 732 | error.Unexpected, | |
| 733 | => |e| return e, | |
| 734 | }; | |
| 735 | defer dir.close(); | |
| 736 | ||
| 737 | // Now we have a candidate for the path to libc shared object. In | |
| 738 | // the past, we used readlink() here because the link name would | |
| 739 | // reveal the glibc version. However, in more recent GNU/Linux | |
| 740 | // installations, there is no symlink. Thus we instead use a more | |
| 741 | // robust check of opening the libc shared object and looking at the | |
| 742 | // .dynstr section, and finding the max version number of symbols | |
| 743 | // that start with "GLIBC_2.". | |
| 744 | const glibc_so_basename = "libc.so.6"; | |
| 745 | var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { | |
| 746 | error.NameTooLong => unreachable, | |
| 747 | error.InvalidUtf8 => unreachable, // Windows only | |
| 748 | error.BadPathName => unreachable, // Windows only | |
| 749 | error.PipeBusy => unreachable, // Windows-only | |
| 750 | error.SharingViolation => unreachable, // Windows-only | |
| 751 | error.NetworkNotFound => unreachable, // Windows-only | |
| 752 | error.FileLocksNotSupported => unreachable, // No lock requested. | |
| 753 | error.NoSpaceLeft => unreachable, // read-only | |
| 754 | error.PathAlreadyExists => unreachable, // read-only | |
| 755 | error.DeviceBusy => unreachable, // read-only | |
| 756 | error.FileBusy => unreachable, // read-only | |
| 757 | error.InvalidHandle => unreachable, // should not be in the error set | |
| 758 | error.WouldBlock => unreachable, // not using O_NONBLOCK | |
| 759 | error.NoDevice => unreachable, // not asking for a special device | |
| 760 | ||
| 761 | error.AccessDenied, | |
| 762 | error.FileNotFound, | |
| 763 | error.NotDir, | |
| 764 | error.IsDir, | |
| 765 | => return error.GLibCNotFound, | |
| 766 | ||
| 767 | error.FileTooBig => return error.Unexpected, | |
| 768 | ||
| 769 | error.ProcessFdQuotaExceeded, | |
| 770 | error.SystemFdQuotaExceeded, | |
| 771 | error.SystemResources, | |
| 772 | error.SymLinkLoop, | |
| 773 | error.Unexpected, | |
| 774 | => |e| return e, | |
| 775 | }; | |
| 776 | defer f.close(); | |
| 777 | ||
| 778 | return glibcVerFromSoFile(f) catch |err| switch (err) { | |
| 779 | error.InvalidElfMagic, | |
| 780 | error.InvalidElfEndian, | |
| 781 | error.InvalidElfClass, | |
| 782 | error.InvalidElfFile, | |
| 783 | error.InvalidElfVersion, | |
| 784 | error.InvalidGnuLibCVersion, | |
| 785 | error.UnexpectedEndOfFile, | |
| 786 | => return error.GLibCNotFound, | |
| 787 | ||
| 788 | error.SystemResources, | |
| 789 | error.UnableToReadElfFile, | |
| 790 | error.Unexpected, | |
| 791 | error.FileSystem, | |
| 792 | => |e| return e, | |
| 793 | }; | |
| 794 | } | |
| 795 | ||
| 796 | fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { | |
| 797 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; | |
| 798 | _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); | |
| 799 | const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); | |
| 800 | const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); | |
| 801 | if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 802 | const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { | |
| 803 | elf.ELFDATA2LSB => .little, | |
| 804 | elf.ELFDATA2MSB => .big, | |
| 805 | else => return error.InvalidElfEndian, | |
| 806 | }; | |
| 807 | const need_bswap = elf_endian != native_endian; | |
| 808 | if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 809 | ||
| 810 | const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { | |
| 811 | elf.ELFCLASS32 => false, | |
| 812 | elf.ELFCLASS64 => true, | |
| 813 | else => return error.InvalidElfClass, | |
| 814 | }; | |
| 815 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); | |
| 816 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); | |
| 817 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); | |
| 818 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); | |
| 819 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; | |
| 820 | if (sh_buf.len < shentsize) return error.InvalidElfFile; | |
| 821 | ||
| 822 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); | |
| 823 | const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 824 | const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 825 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); | |
| 826 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); | |
| 827 | var strtab_buf: [4096:0]u8 = undefined; | |
| 828 | const shstrtab_len = @min(shstrtab_size, strtab_buf.len); | |
| 829 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); | |
| 830 | const shstrtab = strtab_buf[0..shstrtab_read_len]; | |
| 831 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); | |
| 832 | var sh_i: u16 = 0; | |
| 833 | const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { | |
| 834 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 835 | // even when the ELF file is 32-bits. | |
| 836 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); | |
| 837 | const sh_read_byte_len = try preadMin( | |
| 838 | file, | |
| 839 | sh_buf[0 .. sh_buf.len - sh_reserve], | |
| 840 | shoff, | |
| 841 | shentsize, | |
| 842 | ); | |
| 843 | var sh_buf_i: usize = 0; | |
| 844 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ | |
| 845 | sh_i += 1; | |
| 846 | shoff += shentsize; | |
| 847 | sh_buf_i += shentsize; | |
| 848 | }) { | |
| 849 | const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 850 | const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 851 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); | |
| 852 | const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); | |
| 853 | if (mem.eql(u8, sh_name, ".dynstr")) { | |
| 854 | break :find_dyn_str .{ | |
| 855 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), | |
| 856 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), | |
| 857 | }; | |
| 858 | } | |
| 859 | } | |
| 860 | } else return error.InvalidGnuLibCVersion; | |
| 861 | ||
| 862 | // Here we loop over all the strings in the dynstr string table, assuming that any | |
| 863 | // strings that start with "GLIBC_2." indicate the existence of such a glibc version, | |
| 864 | // and furthermore, that the system-installed glibc is at minimum that version. | |
| 865 | ||
| 866 | // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. | |
| 867 | // Here I use double this value plus some headroom. This makes it only need | |
| 868 | // a single read syscall here. | |
| 869 | var buf: [80000]u8 = undefined; | |
| 870 | if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion; | |
| 871 | ||
| 872 | const dynstr_size: usize = @intCast(dynstr.size); | |
| 873 | const dynstr_bytes = buf[0..dynstr_size]; | |
| 874 | _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len); | |
| 875 | var it = mem.splitScalar(u8, dynstr_bytes, 0); | |
| 876 | var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 }; | |
| 877 | while (it.next()) |s| { | |
| 878 | if (mem.startsWith(u8, s, "GLIBC_2.")) { | |
| 879 | const chopped = s["GLIBC_".len..]; | |
| 880 | const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) { | |
| 881 | error.Overflow => return error.InvalidGnuLibCVersion, | |
| 882 | error.InvalidVersion => return error.InvalidGnuLibCVersion, | |
| 883 | }; | |
| 884 | switch (ver.order(max_ver)) { | |
| 885 | .gt => max_ver = ver, | |
| 886 | .lt, .eq => continue, | |
| 887 | } | |
| 888 | } | |
| 889 | } | |
| 890 | return max_ver; | |
| 891 | } | |
| 892 | ||
| 893 | /// In the past, this function attempted to use the executable's own binary if it was dynamically | |
| 894 | /// linked to answer both the C ABI question and the dynamic linker question. However, this | |
| 895 | /// could be problematic on a system that uses a RUNPATH for the compiler binary, locking | |
| 896 | /// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc | |
| 897 | /// version. The problem is that libc.so.6 glibc version will match that of the system while | |
| 898 | /// the dynamic linker will match that of the compiler binary. Executables with these versions | |
| 899 | /// mismatching will fail to run. | |
| 900 | /// | |
| 901 | /// Therefore, this function works the same regardless of whether the compiler binary is | |
| 902 | /// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the | |
| 903 | /// answer to these questions, or if there is a shebang line, then it chases the referenced | |
| 904 | /// file recursively. If that does not provide the answer, then the function falls back to | |
| 905 | /// defaults. | |
| 906 | fn detectAbiAndDynamicLinker( | |
| 907 | cpu: Target.Cpu, | |
| 908 | os: Target.Os, | |
| 909 | query: Target.Query, | |
| 910 | ) DetectError!Target { | |
| 911 | const native_target_has_ld = comptime builtin.target.hasDynamicLinker(); | |
| 912 | const is_linux = builtin.target.os.tag == .linux; | |
| 913 | const is_solarish = builtin.target.os.tag.isSolarish(); | |
| 914 | const have_all_info = query.dynamic_linker.get() != null and | |
| 915 | query.abi != null and (!is_linux or query.abi.?.isGnu()); | |
| 916 | const os_is_non_native = query.os_tag != null; | |
| 917 | // The Solaris/illumos environment is always the same. | |
| 918 | if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) { | |
| 919 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 920 | } | |
| 921 | if (query.abi) |abi| { | |
| 922 | if (abi.isMusl()) { | |
| 923 | // musl implies static linking. | |
| 924 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 925 | } | |
| 926 | } | |
| 927 | // The current target's ABI cannot be relied on for this. For example, we may build the zig | |
| 928 | // compiler for target riscv64-linux-musl and provide a tarball for users to download. | |
| 929 | // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined | |
| 930 | // and supported by Zig. But that means that we must detect the system ABI here rather than | |
| 931 | // relying on `builtin.target`. | |
| 932 | const all_abis = comptime blk: { | |
| 933 | assert(@intFromEnum(Target.Abi.none) == 0); | |
| 934 | const fields = std.meta.fields(Target.Abi)[1..]; | |
| 935 | var array: [fields.len]Target.Abi = undefined; | |
| 936 | for (fields, 0..) |field, i| { | |
| 937 | array[i] = @field(Target.Abi, field.name); | |
| 938 | } | |
| 939 | break :blk array; | |
| 940 | }; | |
| 941 | var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; | |
| 942 | var ld_info_list_len: usize = 0; | |
| 943 | const ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); | |
| 944 | ||
| 945 | for (all_abis) |abi| { | |
| 946 | // This may be a nonsensical parameter. We detect this with | |
| 947 | // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`. | |
| 948 | const target: Target = .{ | |
| 949 | .cpu = cpu, | |
| 950 | .os = os, | |
| 951 | .abi = abi, | |
| 952 | .ofmt = ofmt, | |
| 953 | }; | |
| 954 | const ld = target.standardDynamicLinkerPath(); | |
| 955 | if (ld.get() == null) continue; | |
| 956 | ||
| 957 | ld_info_list_buffer[ld_info_list_len] = .{ | |
| 958 | .ld = ld, | |
| 959 | .abi = abi, | |
| 960 | }; | |
| 961 | ld_info_list_len += 1; | |
| 962 | } | |
| 963 | const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; | |
| 964 | ||
| 965 | // Best case scenario: the executable is dynamically linked, and we can iterate | |
| 966 | // over our own shared objects and find a dynamic linker. | |
| 967 | const elf_file = blk: { | |
| 968 | // This block looks for a shebang line in /usr/bin/env, | |
| 969 | // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, | |
| 970 | // doing the same logic recursively in case it finds another shebang line. | |
| 971 | ||
| 972 | // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a | |
| 973 | // reasonably reliable path to start with. | |
| 974 | var file_name: []const u8 = "/usr/bin/env"; | |
| 975 | // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) | |
| 976 | var buffer: [258]u8 = undefined; | |
| 977 | while (true) { | |
| 978 | const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { | |
| 979 | error.NoSpaceLeft => unreachable, | |
| 980 | error.NameTooLong => unreachable, | |
| 981 | error.PathAlreadyExists => unreachable, | |
| 982 | error.SharingViolation => unreachable, | |
| 983 | error.InvalidUtf8 => unreachable, | |
| 984 | error.BadPathName => unreachable, | |
| 985 | error.PipeBusy => unreachable, | |
| 986 | error.FileLocksNotSupported => unreachable, | |
| 987 | error.WouldBlock => unreachable, | |
| 988 | error.FileBusy => unreachable, // opened without write permissions | |
| 989 | ||
| 990 | error.IsDir, | |
| 991 | error.NotDir, | |
| 992 | error.InvalidHandle, | |
| 993 | error.AccessDenied, | |
| 994 | error.NoDevice, | |
| 995 | error.FileNotFound, | |
| 996 | error.NetworkNotFound, | |
| 997 | error.FileTooBig, | |
| 998 | error.Unexpected, | |
| 999 | => |e| { | |
| 1000 | std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); | |
| 1001 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 1002 | }, | |
| 1003 | ||
| 1004 | else => |e| return e, | |
| 1005 | }; | |
| 1006 | errdefer file.close(); | |
| 1007 | ||
| 1008 | const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) { | |
| 1009 | error.UnexpectedEndOfFile, | |
| 1010 | error.UnableToReadElfFile, | |
| 1011 | => break :blk file, | |
| 1012 | ||
| 1013 | else => |e| return e, | |
| 1014 | }; | |
| 1015 | const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; | |
| 1016 | const line = buffer[0..newline]; | |
| 1017 | if (!mem.startsWith(u8, line, "#!")) break :blk file; | |
| 1018 | var it = mem.tokenizeScalar(u8, line[2..], ' '); | |
| 1019 | file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 1020 | file.close(); | |
| 1021 | } | |
| 1022 | }; | |
| 1023 | defer elf_file.close(); | |
| 1024 | ||
| 1025 | // If Zig is statically linked, such as via distributed binary static builds, the above | |
| 1026 | // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. | |
| 1027 | // TODO: inline this function and combine the buffer we already read above to find | |
| 1028 | // the possible shebang line with the buffer we use for the ELF header. | |
| 1029 | return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) { | |
| 1030 | error.FileSystem, | |
| 1031 | error.SystemResources, | |
| 1032 | error.SymLinkLoop, | |
| 1033 | error.ProcessFdQuotaExceeded, | |
| 1034 | error.SystemFdQuotaExceeded, | |
| 1035 | => |e| return e, | |
| 1036 | ||
| 1037 | error.UnableToReadElfFile, | |
| 1038 | error.InvalidElfClass, | |
| 1039 | error.InvalidElfVersion, | |
| 1040 | error.InvalidElfEndian, | |
| 1041 | error.InvalidElfFile, | |
| 1042 | error.InvalidElfMagic, | |
| 1043 | error.Unexpected, | |
| 1044 | error.UnexpectedEndOfFile, | |
| 1045 | error.NameTooLong, | |
| 1046 | // Finally, we fall back on the standard path. | |
| 1047 | => |e| { | |
| 1048 | std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); | |
| 1049 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 1050 | }, | |
| 1051 | }; | |
| 1052 | } | |
| 1053 | ||
| 1054 | fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target { | |
| 1055 | const abi = query.abi orelse Target.Abi.default(cpu.arch, os); | |
| 1056 | return .{ | |
| 1057 | .cpu = cpu, | |
| 1058 | .os = os, | |
| 1059 | .abi = abi, | |
| 1060 | .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), | |
| 1061 | .dynamic_linker = if (query.dynamic_linker.get() == null) | |
| 1062 | Target.standardDynamicLinkerPath_cpu_os_abi(cpu, os.tag, abi) | |
| 1063 | else | |
| 1064 | query.dynamic_linker, | |
| 1065 | }; | |
| 1066 | } | |
| 1067 | ||
| 1068 | const LdInfo = struct { | |
| 1069 | ld: Target.DynamicLinker, | |
| 1070 | abi: Target.Abi, | |
| 1071 | }; | |
| 1072 | ||
| 1073 | fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { | |
| 1074 | var i: usize = 0; | |
| 1075 | while (i < min_read_len) { | |
| 1076 | const len = file.pread(buf[i..], offset + i) catch |err| switch (err) { | |
| 1077 | error.OperationAborted => unreachable, // Windows-only | |
| 1078 | error.WouldBlock => unreachable, // Did not request blocking mode | |
| 1079 | error.NotOpenForReading => unreachable, | |
| 1080 | error.SystemResources => return error.SystemResources, | |
| 1081 | error.IsDir => return error.UnableToReadElfFile, | |
| 1082 | error.BrokenPipe => return error.UnableToReadElfFile, | |
| 1083 | error.Unseekable => return error.UnableToReadElfFile, | |
| 1084 | error.ConnectionResetByPeer => return error.UnableToReadElfFile, | |
| 1085 | error.ConnectionTimedOut => return error.UnableToReadElfFile, | |
| 1086 | error.SocketNotConnected => return error.UnableToReadElfFile, | |
| 1087 | error.NetNameDeleted => return error.UnableToReadElfFile, | |
| 1088 | error.Unexpected => return error.Unexpected, | |
| 1089 | error.InputOutput => return error.FileSystem, | |
| 1090 | error.AccessDenied => return error.Unexpected, | |
| 1091 | }; | |
| 1092 | if (len == 0) return error.UnexpectedEndOfFile; | |
| 1093 | i += len; | |
| 1094 | } | |
| 1095 | return i; | |
| 1096 | } | |
| 1097 | ||
| 1098 | fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { | |
| 1099 | if (is_64) { | |
| 1100 | if (need_bswap) { | |
| 1101 | return @byteSwap(int_64); | |
| 1102 | } else { | |
| 1103 | return int_64; | |
| 1104 | } | |
| 1105 | } else { | |
| 1106 | if (need_bswap) { | |
| 1107 | return @byteSwap(int_32); | |
| 1108 | } else { | |
| 1109 | return int_32; | |
| 1110 | } | |
| 1111 | } | |
| 1112 | } | |
| 1113 | ||
| 1114 | const builtin = @import("builtin"); | |
| 1115 | const std = @import("../std.zig"); | |
| 1116 | const mem = std.mem; | |
| 1117 | const elf = std.elf; | |
| 1118 | const fs = std.fs; | |
| 1119 | const assert = std.debug.assert; | |
| 1120 | const Target = std.Target; | |
| 1121 | const native_endian = builtin.cpu.arch.endian(); | |
| 1122 | ||
| 8 | 1123 | test { |
| 9 | 1124 | _ = NativePaths; |
| 10 | _ = NativeTargetInfo; | |
| 11 | 1125 | |
| 12 | 1126 | _ = darwin; |
| 13 | 1127 | _ = linux; |
lib/std/zig/system/NativePaths.zig+1-3| ... | ... | @@ -5,7 +5,6 @@ const process = std.process; |
| 5 | 5 | const mem = std.mem; |
| 6 | 6 | |
| 7 | 7 | const NativePaths = @This(); |
| 8 | const NativeTargetInfo = std.zig.system.NativeTargetInfo; | |
| 9 | 8 | |
| 10 | 9 | arena: Allocator, |
| 11 | 10 | include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, |
| ... | ... | @@ -14,8 +13,7 @@ framework_dirs: std.ArrayListUnmanaged([]const u8) = .{}, |
| 14 | 13 | rpaths: std.ArrayListUnmanaged([]const u8) = .{}, |
| 15 | 14 | warnings: std.ArrayListUnmanaged([]const u8) = .{}, |
| 16 | 15 | |
| 17 | pub fn detect(arena: Allocator, native_info: NativeTargetInfo) !NativePaths { | |
| 18 | const native_target = native_info.target; | |
| 16 | pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths { | |
| 19 | 17 | var self: NativePaths = .{ .arena = arena }; |
| 20 | 18 | var is_nix = false; |
| 21 | 19 | if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { |
lib/std/zig/system/NativeTargetInfo.zig deleted-1130| ... | ... | @@ -1,1130 +0,0 @@ |
| 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 windows = std.zig.system.windows; | |
| 13 | const darwin = std.zig.system.darwin; | |
| 14 | const linux = std.zig.system.linux; | |
| 15 | ||
| 16 | target: Target, | |
| 17 | dynamic_linker: DynamicLinker = DynamicLinker{}, | |
| 18 | ||
| 19 | pub const DynamicLinker = Target.DynamicLinker; | |
| 20 | ||
| 21 | pub const DetectError = error{ | |
| 22 | FileSystem, | |
| 23 | SystemResources, | |
| 24 | SymLinkLoop, | |
| 25 | ProcessFdQuotaExceeded, | |
| 26 | SystemFdQuotaExceeded, | |
| 27 | DeviceBusy, | |
| 28 | OSVersionDetectionFail, | |
| 29 | Unexpected, | |
| 30 | }; | |
| 31 | ||
| 32 | /// Given a `Target.Query`, which specifies in detail which parts of the | |
| 33 | /// target should be detected natively, which should be standard or default, | |
| 34 | /// and which are provided explicitly, this function resolves the native | |
| 35 | /// components by detecting the native system, and then resolves | |
| 36 | /// standard/default parts relative to that. | |
| 37 | pub fn detect(query: Target.Query) DetectError!NativeTargetInfo { | |
| 38 | var os = query.getOsTag().defaultVersionRange(query.getCpuArch()); | |
| 39 | if (query.os_tag == null) { | |
| 40 | switch (builtin.target.os.tag) { | |
| 41 | .linux => { | |
| 42 | const uts = std.os.uname(); | |
| 43 | const release = mem.sliceTo(&uts.release, 0); | |
| 44 | // The release field sometimes has a weird format, | |
| 45 | // `Version.parse` will attempt to find some meaningful interpretation. | |
| 46 | if (std.SemanticVersion.parse(release)) |ver| { | |
| 47 | os.version_range.linux.range.min = ver; | |
| 48 | os.version_range.linux.range.max = ver; | |
| 49 | } else |err| switch (err) { | |
| 50 | error.Overflow => {}, | |
| 51 | error.InvalidVersion => {}, | |
| 52 | } | |
| 53 | }, | |
| 54 | .solaris, .illumos => { | |
| 55 | const uts = std.os.uname(); | |
| 56 | const release = mem.sliceTo(&uts.release, 0); | |
| 57 | if (std.SemanticVersion.parse(release)) |ver| { | |
| 58 | os.version_range.semver.min = ver; | |
| 59 | os.version_range.semver.max = ver; | |
| 60 | } else |err| switch (err) { | |
| 61 | error.Overflow => {}, | |
| 62 | error.InvalidVersion => {}, | |
| 63 | } | |
| 64 | }, | |
| 65 | .windows => { | |
| 66 | const detected_version = windows.detectRuntimeVersion(); | |
| 67 | os.version_range.windows.min = detected_version; | |
| 68 | os.version_range.windows.max = detected_version; | |
| 69 | }, | |
| 70 | .macos => try darwin.macos.detect(&os), | |
| 71 | .freebsd, .netbsd, .dragonfly => { | |
| 72 | const key = switch (builtin.target.os.tag) { | |
| 73 | .freebsd => "kern.osreldate", | |
| 74 | .netbsd, .dragonfly => "kern.osrevision", | |
| 75 | else => unreachable, | |
| 76 | }; | |
| 77 | var value: u32 = undefined; | |
| 78 | var len: usize = @sizeOf(@TypeOf(value)); | |
| 79 | ||
| 80 | std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { | |
| 81 | error.NameTooLong => unreachable, // constant, known good value | |
| 82 | error.PermissionDenied => unreachable, // only when setting values, | |
| 83 | error.SystemResources => unreachable, // memory already on the stack | |
| 84 | error.UnknownName => unreachable, // constant, known good value | |
| 85 | error.Unexpected => return error.OSVersionDetectionFail, | |
| 86 | }; | |
| 87 | ||
| 88 | switch (builtin.target.os.tag) { | |
| 89 | .freebsd => { | |
| 90 | // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html | |
| 91 | // Major * 100,000 has been convention since FreeBSD 2.2 (1997) | |
| 92 | // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997) | |
| 93 | // e.g. 492101 = 4.11-STABLE = 4.(9+2) | |
| 94 | const major = value / 100_000; | |
| 95 | const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1 | |
| 96 | const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since | |
| 97 | const patch = value % 1_000; | |
| 98 | os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch }; | |
| 99 | os.version_range.semver.max = os.version_range.semver.min; | |
| 100 | }, | |
| 101 | .netbsd => { | |
| 102 | // #define __NetBSD_Version__ MMmmrrpp00 | |
| 103 | // | |
| 104 | // M = major version | |
| 105 | // m = minor version; a minor number of 99 indicates current. | |
| 106 | // r = 0 (*) | |
| 107 | // p = patchlevel | |
| 108 | const major = value / 100_000_000; | |
| 109 | const minor = value % 100_000_000 / 1_000_000; | |
| 110 | const patch = value % 10_000 / 100; | |
| 111 | os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; | |
| 112 | os.version_range.semver.max = os.version_range.semver.min; | |
| 113 | }, | |
| 114 | .dragonfly => { | |
| 115 | // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17 | |
| 116 | // flat base10 format: Mmmmpp | |
| 117 | // M = major | |
| 118 | // m = minor; odd-numbers indicate current dev branch | |
| 119 | // p = patch | |
| 120 | const major = value / 100_000; | |
| 121 | const minor = value % 100_000 / 100; | |
| 122 | const patch = value % 100; | |
| 123 | os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; | |
| 124 | os.version_range.semver.max = os.version_range.semver.min; | |
| 125 | }, | |
| 126 | else => unreachable, | |
| 127 | } | |
| 128 | }, | |
| 129 | .openbsd => { | |
| 130 | const mib: [2]c_int = [_]c_int{ | |
| 131 | std.os.CTL.KERN, | |
| 132 | std.os.KERN.OSRELEASE, | |
| 133 | }; | |
| 134 | var buf: [64]u8 = undefined; | |
| 135 | // consider that sysctl result includes null-termination | |
| 136 | // reserve 1 byte to ensure we never overflow when appending ".0" | |
| 137 | var len: usize = buf.len - 1; | |
| 138 | ||
| 139 | std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { | |
| 140 | error.NameTooLong => unreachable, // constant, known good value | |
| 141 | error.PermissionDenied => unreachable, // only when setting values, | |
| 142 | error.SystemResources => unreachable, // memory already on the stack | |
| 143 | error.UnknownName => unreachable, // constant, known good value | |
| 144 | error.Unexpected => return error.OSVersionDetectionFail, | |
| 145 | }; | |
| 146 | ||
| 147 | // append ".0" to satisfy semver | |
| 148 | buf[len - 1] = '.'; | |
| 149 | buf[len] = '0'; | |
| 150 | len += 1; | |
| 151 | ||
| 152 | if (std.SemanticVersion.parse(buf[0..len])) |ver| { | |
| 153 | os.version_range.semver.min = ver; | |
| 154 | os.version_range.semver.max = ver; | |
| 155 | } else |_| { | |
| 156 | return error.OSVersionDetectionFail; | |
| 157 | } | |
| 158 | }, | |
| 159 | else => { | |
| 160 | // Unimplemented, fall back to default version range. | |
| 161 | }, | |
| 162 | } | |
| 163 | } | |
| 164 | ||
| 165 | if (query.os_version_min) |min| switch (min) { | |
| 166 | .none => {}, | |
| 167 | .semver => |semver| switch (query.getOsTag()) { | |
| 168 | .linux => os.version_range.linux.range.min = semver, | |
| 169 | else => os.version_range.semver.min = semver, | |
| 170 | }, | |
| 171 | .windows => |win_ver| os.version_range.windows.min = win_ver, | |
| 172 | }; | |
| 173 | ||
| 174 | if (query.os_version_max) |max| switch (max) { | |
| 175 | .none => {}, | |
| 176 | .semver => |semver| switch (query.getOsTag()) { | |
| 177 | .linux => os.version_range.linux.range.max = semver, | |
| 178 | else => os.version_range.semver.max = semver, | |
| 179 | }, | |
| 180 | .windows => |win_ver| os.version_range.windows.max = win_ver, | |
| 181 | }; | |
| 182 | ||
| 183 | if (query.glibc_version) |glibc| { | |
| 184 | assert(query.isGnuLibC()); | |
| 185 | os.version_range.linux.glibc = glibc; | |
| 186 | } | |
| 187 | ||
| 188 | // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the | |
| 189 | // native CPU architecture as being different than the current target), we use this: | |
| 190 | const cpu_arch = query.getCpuArch(); | |
| 191 | ||
| 192 | const cpu = switch (query.cpu_model) { | |
| 193 | .native => detectNativeCpuAndFeatures(cpu_arch, os, query), | |
| 194 | .baseline => Target.Cpu.baseline(cpu_arch), | |
| 195 | .determined_by_cpu_arch => if (query.cpu_arch == null) | |
| 196 | detectNativeCpuAndFeatures(cpu_arch, os, query) | |
| 197 | else | |
| 198 | Target.Cpu.baseline(cpu_arch), | |
| 199 | .explicit => |model| model.toCpu(cpu_arch), | |
| 200 | } orelse backup_cpu_detection: { | |
| 201 | break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); | |
| 202 | }; | |
| 203 | var result = try detectAbiAndDynamicLinker(cpu, os, query); | |
| 204 | // For x86, we need to populate some CPU feature flags depending on architecture | |
| 205 | // and mode: | |
| 206 | // * 16bit_mode => if the abi is code16 | |
| 207 | // * 32bit_mode => if the arch is x86 | |
| 208 | // However, the "mode" flags can be used as overrides, so if the user explicitly | |
| 209 | // sets one of them, that takes precedence. | |
| 210 | switch (cpu_arch) { | |
| 211 | .x86 => { | |
| 212 | if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{ | |
| 213 | .@"16bit_mode", .@"32bit_mode", | |
| 214 | })) { | |
| 215 | switch (result.target.abi) { | |
| 216 | .code16 => result.target.cpu.features.addFeature( | |
| 217 | @intFromEnum(Target.x86.Feature.@"16bit_mode"), | |
| 218 | ), | |
| 219 | else => result.target.cpu.features.addFeature( | |
| 220 | @intFromEnum(Target.x86.Feature.@"32bit_mode"), | |
| 221 | ), | |
| 222 | } | |
| 223 | } | |
| 224 | }, | |
| 225 | .arm, .armeb => { | |
| 226 | // XXX What do we do if the target has the noarm feature? | |
| 227 | // What do we do if the user specifies +thumb_mode? | |
| 228 | }, | |
| 229 | .thumb, .thumbeb => { | |
| 230 | result.target.cpu.features.addFeature( | |
| 231 | @intFromEnum(Target.arm.Feature.thumb_mode), | |
| 232 | ); | |
| 233 | }, | |
| 234 | else => {}, | |
| 235 | } | |
| 236 | query.updateCpuFeatures(&result.target.cpu.features); | |
| 237 | return result; | |
| 238 | } | |
| 239 | ||
| 240 | /// In the past, this function attempted to use the executable's own binary if it was dynamically | |
| 241 | /// linked to answer both the C ABI question and the dynamic linker question. However, this | |
| 242 | /// could be problematic on a system that uses a RUNPATH for the compiler binary, locking | |
| 243 | /// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc | |
| 244 | /// version. The problem is that libc.so.6 glibc version will match that of the system while | |
| 245 | /// the dynamic linker will match that of the compiler binary. Executables with these versions | |
| 246 | /// mismatching will fail to run. | |
| 247 | /// | |
| 248 | /// Therefore, this function works the same regardless of whether the compiler binary is | |
| 249 | /// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the | |
| 250 | /// answer to these questions, or if there is a shebang line, then it chases the referenced | |
| 251 | /// file recursively. If that does not provide the answer, then the function falls back to | |
| 252 | /// defaults. | |
| 253 | fn detectAbiAndDynamicLinker( | |
| 254 | cpu: Target.Cpu, | |
| 255 | os: Target.Os, | |
| 256 | query: Target.Query, | |
| 257 | ) DetectError!NativeTargetInfo { | |
| 258 | const native_target_has_ld = comptime builtin.target.hasDynamicLinker(); | |
| 259 | const is_linux = builtin.target.os.tag == .linux; | |
| 260 | const is_solarish = builtin.target.os.tag.isSolarish(); | |
| 261 | const have_all_info = query.dynamic_linker.get() != null and | |
| 262 | query.abi != null and (!is_linux or query.abi.?.isGnu()); | |
| 263 | const os_is_non_native = query.os_tag != null; | |
| 264 | // The Solaris/illumos environment is always the same. | |
| 265 | if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) { | |
| 266 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 267 | } | |
| 268 | if (query.abi) |abi| { | |
| 269 | if (abi.isMusl()) { | |
| 270 | // musl implies static linking. | |
| 271 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 272 | } | |
| 273 | } | |
| 274 | // The current target's ABI cannot be relied on for this. For example, we may build the zig | |
| 275 | // compiler for target riscv64-linux-musl and provide a tarball for users to download. | |
| 276 | // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined | |
| 277 | // and supported by Zig. But that means that we must detect the system ABI here rather than | |
| 278 | // relying on `builtin.target`. | |
| 279 | const all_abis = comptime blk: { | |
| 280 | assert(@intFromEnum(Target.Abi.none) == 0); | |
| 281 | const fields = std.meta.fields(Target.Abi)[1..]; | |
| 282 | var array: [fields.len]Target.Abi = undefined; | |
| 283 | for (fields, 0..) |field, i| { | |
| 284 | array[i] = @field(Target.Abi, field.name); | |
| 285 | } | |
| 286 | break :blk array; | |
| 287 | }; | |
| 288 | var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; | |
| 289 | var ld_info_list_len: usize = 0; | |
| 290 | const ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); | |
| 291 | ||
| 292 | for (all_abis) |abi| { | |
| 293 | // This may be a nonsensical parameter. We detect this with | |
| 294 | // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`. | |
| 295 | const target: Target = .{ | |
| 296 | .cpu = cpu, | |
| 297 | .os = os, | |
| 298 | .abi = abi, | |
| 299 | .ofmt = ofmt, | |
| 300 | }; | |
| 301 | const ld = target.standardDynamicLinkerPath(); | |
| 302 | if (ld.get() == null) continue; | |
| 303 | ||
| 304 | ld_info_list_buffer[ld_info_list_len] = .{ | |
| 305 | .ld = ld, | |
| 306 | .abi = abi, | |
| 307 | }; | |
| 308 | ld_info_list_len += 1; | |
| 309 | } | |
| 310 | const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; | |
| 311 | ||
| 312 | // Best case scenario: the executable is dynamically linked, and we can iterate | |
| 313 | // over our own shared objects and find a dynamic linker. | |
| 314 | const elf_file = blk: { | |
| 315 | // This block looks for a shebang line in /usr/bin/env, | |
| 316 | // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, | |
| 317 | // doing the same logic recursively in case it finds another shebang line. | |
| 318 | ||
| 319 | // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a | |
| 320 | // reasonably reliable path to start with. | |
| 321 | var file_name: []const u8 = "/usr/bin/env"; | |
| 322 | // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) | |
| 323 | var buffer: [258]u8 = undefined; | |
| 324 | while (true) { | |
| 325 | const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { | |
| 326 | error.NoSpaceLeft => unreachable, | |
| 327 | error.NameTooLong => unreachable, | |
| 328 | error.PathAlreadyExists => unreachable, | |
| 329 | error.SharingViolation => unreachable, | |
| 330 | error.InvalidUtf8 => unreachable, | |
| 331 | error.BadPathName => unreachable, | |
| 332 | error.PipeBusy => unreachable, | |
| 333 | error.FileLocksNotSupported => unreachable, | |
| 334 | error.WouldBlock => unreachable, | |
| 335 | error.FileBusy => unreachable, // opened without write permissions | |
| 336 | ||
| 337 | error.IsDir, | |
| 338 | error.NotDir, | |
| 339 | error.InvalidHandle, | |
| 340 | error.AccessDenied, | |
| 341 | error.NoDevice, | |
| 342 | error.FileNotFound, | |
| 343 | error.NetworkNotFound, | |
| 344 | error.FileTooBig, | |
| 345 | error.Unexpected, | |
| 346 | => |e| { | |
| 347 | std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); | |
| 348 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 349 | }, | |
| 350 | ||
| 351 | else => |e| return e, | |
| 352 | }; | |
| 353 | errdefer file.close(); | |
| 354 | ||
| 355 | const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) { | |
| 356 | error.UnexpectedEndOfFile, | |
| 357 | error.UnableToReadElfFile, | |
| 358 | => break :blk file, | |
| 359 | ||
| 360 | else => |e| return e, | |
| 361 | }; | |
| 362 | const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; | |
| 363 | const line = buffer[0..newline]; | |
| 364 | if (!mem.startsWith(u8, line, "#!")) break :blk file; | |
| 365 | var it = mem.tokenizeScalar(u8, line[2..], ' '); | |
| 366 | file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 367 | file.close(); | |
| 368 | } | |
| 369 | }; | |
| 370 | defer elf_file.close(); | |
| 371 | ||
| 372 | // If Zig is statically linked, such as via distributed binary static builds, the above | |
| 373 | // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. | |
| 374 | // TODO: inline this function and combine the buffer we already read above to find | |
| 375 | // the possible shebang line with the buffer we use for the ELF header. | |
| 376 | return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) { | |
| 377 | error.FileSystem, | |
| 378 | error.SystemResources, | |
| 379 | error.SymLinkLoop, | |
| 380 | error.ProcessFdQuotaExceeded, | |
| 381 | error.SystemFdQuotaExceeded, | |
| 382 | => |e| return e, | |
| 383 | ||
| 384 | error.UnableToReadElfFile, | |
| 385 | error.InvalidElfClass, | |
| 386 | error.InvalidElfVersion, | |
| 387 | error.InvalidElfEndian, | |
| 388 | error.InvalidElfFile, | |
| 389 | error.InvalidElfMagic, | |
| 390 | error.Unexpected, | |
| 391 | error.UnexpectedEndOfFile, | |
| 392 | error.NameTooLong, | |
| 393 | // Finally, we fall back on the standard path. | |
| 394 | => |e| { | |
| 395 | std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); | |
| 396 | return defaultAbiAndDynamicLinker(cpu, os, query); | |
| 397 | }, | |
| 398 | }; | |
| 399 | } | |
| 400 | ||
| 401 | fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { | |
| 402 | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { | |
| 403 | error.NameTooLong => unreachable, | |
| 404 | error.InvalidUtf8 => unreachable, | |
| 405 | error.BadPathName => unreachable, | |
| 406 | error.DeviceBusy => unreachable, | |
| 407 | error.NetworkNotFound => unreachable, // Windows-only | |
| 408 | ||
| 409 | error.FileNotFound, | |
| 410 | error.NotDir, | |
| 411 | error.InvalidHandle, | |
| 412 | error.AccessDenied, | |
| 413 | error.NoDevice, | |
| 414 | => return error.GLibCNotFound, | |
| 415 | ||
| 416 | error.ProcessFdQuotaExceeded, | |
| 417 | error.SystemFdQuotaExceeded, | |
| 418 | error.SystemResources, | |
| 419 | error.SymLinkLoop, | |
| 420 | error.Unexpected, | |
| 421 | => |e| return e, | |
| 422 | }; | |
| 423 | defer dir.close(); | |
| 424 | ||
| 425 | // Now we have a candidate for the path to libc shared object. In | |
| 426 | // the past, we used readlink() here because the link name would | |
| 427 | // reveal the glibc version. However, in more recent GNU/Linux | |
| 428 | // installations, there is no symlink. Thus we instead use a more | |
| 429 | // robust check of opening the libc shared object and looking at the | |
| 430 | // .dynstr section, and finding the max version number of symbols | |
| 431 | // that start with "GLIBC_2.". | |
| 432 | const glibc_so_basename = "libc.so.6"; | |
| 433 | var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { | |
| 434 | error.NameTooLong => unreachable, | |
| 435 | error.InvalidUtf8 => unreachable, // Windows only | |
| 436 | error.BadPathName => unreachable, // Windows only | |
| 437 | error.PipeBusy => unreachable, // Windows-only | |
| 438 | error.SharingViolation => unreachable, // Windows-only | |
| 439 | error.NetworkNotFound => unreachable, // Windows-only | |
| 440 | error.FileLocksNotSupported => unreachable, // No lock requested. | |
| 441 | error.NoSpaceLeft => unreachable, // read-only | |
| 442 | error.PathAlreadyExists => unreachable, // read-only | |
| 443 | error.DeviceBusy => unreachable, // read-only | |
| 444 | error.FileBusy => unreachable, // read-only | |
| 445 | error.InvalidHandle => unreachable, // should not be in the error set | |
| 446 | error.WouldBlock => unreachable, // not using O_NONBLOCK | |
| 447 | error.NoDevice => unreachable, // not asking for a special device | |
| 448 | ||
| 449 | error.AccessDenied, | |
| 450 | error.FileNotFound, | |
| 451 | error.NotDir, | |
| 452 | error.IsDir, | |
| 453 | => return error.GLibCNotFound, | |
| 454 | ||
| 455 | error.FileTooBig => return error.Unexpected, | |
| 456 | ||
| 457 | error.ProcessFdQuotaExceeded, | |
| 458 | error.SystemFdQuotaExceeded, | |
| 459 | error.SystemResources, | |
| 460 | error.SymLinkLoop, | |
| 461 | error.Unexpected, | |
| 462 | => |e| return e, | |
| 463 | }; | |
| 464 | defer f.close(); | |
| 465 | ||
| 466 | return glibcVerFromSoFile(f) catch |err| switch (err) { | |
| 467 | error.InvalidElfMagic, | |
| 468 | error.InvalidElfEndian, | |
| 469 | error.InvalidElfClass, | |
| 470 | error.InvalidElfFile, | |
| 471 | error.InvalidElfVersion, | |
| 472 | error.InvalidGnuLibCVersion, | |
| 473 | error.UnexpectedEndOfFile, | |
| 474 | => return error.GLibCNotFound, | |
| 475 | ||
| 476 | error.SystemResources, | |
| 477 | error.UnableToReadElfFile, | |
| 478 | error.Unexpected, | |
| 479 | error.FileSystem, | |
| 480 | => |e| return e, | |
| 481 | }; | |
| 482 | } | |
| 483 | ||
| 484 | fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { | |
| 485 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; | |
| 486 | _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); | |
| 487 | const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); | |
| 488 | const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); | |
| 489 | if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 490 | const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { | |
| 491 | elf.ELFDATA2LSB => .little, | |
| 492 | elf.ELFDATA2MSB => .big, | |
| 493 | else => return error.InvalidElfEndian, | |
| 494 | }; | |
| 495 | const need_bswap = elf_endian != native_endian; | |
| 496 | if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 497 | ||
| 498 | const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { | |
| 499 | elf.ELFCLASS32 => false, | |
| 500 | elf.ELFCLASS64 => true, | |
| 501 | else => return error.InvalidElfClass, | |
| 502 | }; | |
| 503 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); | |
| 504 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); | |
| 505 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); | |
| 506 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); | |
| 507 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; | |
| 508 | if (sh_buf.len < shentsize) return error.InvalidElfFile; | |
| 509 | ||
| 510 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); | |
| 511 | const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 512 | const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 513 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); | |
| 514 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); | |
| 515 | var strtab_buf: [4096:0]u8 = undefined; | |
| 516 | const shstrtab_len = @min(shstrtab_size, strtab_buf.len); | |
| 517 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); | |
| 518 | const shstrtab = strtab_buf[0..shstrtab_read_len]; | |
| 519 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); | |
| 520 | var sh_i: u16 = 0; | |
| 521 | const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { | |
| 522 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 523 | // even when the ELF file is 32-bits. | |
| 524 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); | |
| 525 | const sh_read_byte_len = try preadMin( | |
| 526 | file, | |
| 527 | sh_buf[0 .. sh_buf.len - sh_reserve], | |
| 528 | shoff, | |
| 529 | shentsize, | |
| 530 | ); | |
| 531 | var sh_buf_i: usize = 0; | |
| 532 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ | |
| 533 | sh_i += 1; | |
| 534 | shoff += shentsize; | |
| 535 | sh_buf_i += shentsize; | |
| 536 | }) { | |
| 537 | const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 538 | const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 539 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); | |
| 540 | const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); | |
| 541 | if (mem.eql(u8, sh_name, ".dynstr")) { | |
| 542 | break :find_dyn_str .{ | |
| 543 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), | |
| 544 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), | |
| 545 | }; | |
| 546 | } | |
| 547 | } | |
| 548 | } else return error.InvalidGnuLibCVersion; | |
| 549 | ||
| 550 | // Here we loop over all the strings in the dynstr string table, assuming that any | |
| 551 | // strings that start with "GLIBC_2." indicate the existence of such a glibc version, | |
| 552 | // and furthermore, that the system-installed glibc is at minimum that version. | |
| 553 | ||
| 554 | // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. | |
| 555 | // Here I use double this value plus some headroom. This makes it only need | |
| 556 | // a single read syscall here. | |
| 557 | var buf: [80000]u8 = undefined; | |
| 558 | if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion; | |
| 559 | ||
| 560 | const dynstr_size: usize = @intCast(dynstr.size); | |
| 561 | const dynstr_bytes = buf[0..dynstr_size]; | |
| 562 | _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len); | |
| 563 | var it = mem.splitScalar(u8, dynstr_bytes, 0); | |
| 564 | var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 }; | |
| 565 | while (it.next()) |s| { | |
| 566 | if (mem.startsWith(u8, s, "GLIBC_2.")) { | |
| 567 | const chopped = s["GLIBC_".len..]; | |
| 568 | const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) { | |
| 569 | error.Overflow => return error.InvalidGnuLibCVersion, | |
| 570 | error.InvalidVersion => return error.InvalidGnuLibCVersion, | |
| 571 | }; | |
| 572 | switch (ver.order(max_ver)) { | |
| 573 | .gt => max_ver = ver, | |
| 574 | .lt, .eq => continue, | |
| 575 | } | |
| 576 | } | |
| 577 | } | |
| 578 | return max_ver; | |
| 579 | } | |
| 580 | ||
| 581 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion { | |
| 582 | // example: "libc-2.3.4.so" | |
| 583 | // example: "libc-2.27.so" | |
| 584 | // example: "ld-2.33.so" | |
| 585 | const suffix = ".so"; | |
| 586 | if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { | |
| 587 | return error.UnrecognizedGnuLibCFileName; | |
| 588 | } | |
| 589 | // chop off "libc-" and ".so" | |
| 590 | const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; | |
| 591 | return Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) { | |
| 592 | error.Overflow => return error.InvalidGnuLibCVersion, | |
| 593 | error.InvalidVersion => return error.InvalidGnuLibCVersion, | |
| 594 | }; | |
| 595 | } | |
| 596 | ||
| 597 | test glibcVerFromLinkName { | |
| 598 | try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist")); | |
| 599 | try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-")); | |
| 600 | ||
| 601 | try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-")); | |
| 602 | try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-")); | |
| 603 | try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-")); | |
| 604 | try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-")); | |
| 605 | try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-")); | |
| 606 | } | |
| 607 | ||
| 608 | pub const AbiAndDynamicLinkerFromFileError = error{ | |
| 609 | FileSystem, | |
| 610 | SystemResources, | |
| 611 | SymLinkLoop, | |
| 612 | ProcessFdQuotaExceeded, | |
| 613 | SystemFdQuotaExceeded, | |
| 614 | UnableToReadElfFile, | |
| 615 | InvalidElfClass, | |
| 616 | InvalidElfVersion, | |
| 617 | InvalidElfEndian, | |
| 618 | InvalidElfFile, | |
| 619 | InvalidElfMagic, | |
| 620 | Unexpected, | |
| 621 | UnexpectedEndOfFile, | |
| 622 | NameTooLong, | |
| 623 | }; | |
| 624 | ||
| 625 | pub fn abiAndDynamicLinkerFromFile( | |
| 626 | file: fs.File, | |
| 627 | cpu: Target.Cpu, | |
| 628 | os: Target.Os, | |
| 629 | ld_info_list: []const LdInfo, | |
| 630 | query: Target.Query, | |
| 631 | ) AbiAndDynamicLinkerFromFileError!NativeTargetInfo { | |
| 632 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; | |
| 633 | _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); | |
| 634 | const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf)); | |
| 635 | const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf)); | |
| 636 | if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 637 | const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { | |
| 638 | elf.ELFDATA2LSB => .little, | |
| 639 | elf.ELFDATA2MSB => .big, | |
| 640 | else => return error.InvalidElfEndian, | |
| 641 | }; | |
| 642 | const need_bswap = elf_endian != native_endian; | |
| 643 | if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 644 | ||
| 645 | const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { | |
| 646 | elf.ELFCLASS32 => false, | |
| 647 | elf.ELFCLASS64 => true, | |
| 648 | else => return error.InvalidElfClass, | |
| 649 | }; | |
| 650 | var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); | |
| 651 | const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); | |
| 652 | const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); | |
| 653 | ||
| 654 | var result: NativeTargetInfo = .{ | |
| 655 | .target = .{ | |
| 656 | .cpu = cpu, | |
| 657 | .os = os, | |
| 658 | .abi = query.abi orelse Target.Abi.default(cpu.arch, os), | |
| 659 | .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), | |
| 660 | }, | |
| 661 | .dynamic_linker = query.dynamic_linker, | |
| 662 | }; | |
| 663 | var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC | |
| 664 | const look_for_ld = query.dynamic_linker.get() == null; | |
| 665 | ||
| 666 | var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; | |
| 667 | if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; | |
| 668 | ||
| 669 | var ph_i: u16 = 0; | |
| 670 | while (ph_i < phnum) { | |
| 671 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 672 | // even when the ELF file is 32-bits. | |
| 673 | const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); | |
| 674 | const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); | |
| 675 | var ph_buf_i: usize = 0; | |
| 676 | while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ | |
| 677 | ph_i += 1; | |
| 678 | phoff += phentsize; | |
| 679 | ph_buf_i += phentsize; | |
| 680 | }) { | |
| 681 | const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); | |
| 682 | const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); | |
| 683 | const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); | |
| 684 | switch (p_type) { | |
| 685 | elf.PT_INTERP => if (look_for_ld) { | |
| 686 | const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); | |
| 687 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); | |
| 688 | if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; | |
| 689 | const filesz = @as(usize, @intCast(p_filesz)); | |
| 690 | _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz); | |
| 691 | // PT_INTERP includes a null byte in filesz. | |
| 692 | const len = filesz - 1; | |
| 693 | // dynamic_linker.max_byte is "max", not "len". | |
| 694 | // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. | |
| 695 | result.dynamic_linker.max_byte = @as(u8, @intCast(len - 1)); | |
| 696 | ||
| 697 | // Use it to determine ABI. | |
| 698 | const full_ld_path = result.dynamic_linker.buffer[0..len]; | |
| 699 | for (ld_info_list) |ld_info| { | |
| 700 | const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); | |
| 701 | if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { | |
| 702 | result.target.abi = ld_info.abi; | |
| 703 | break; | |
| 704 | } | |
| 705 | } | |
| 706 | }, | |
| 707 | // We only need this for detecting glibc version. | |
| 708 | elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and | |
| 709 | query.glibc_version == null) | |
| 710 | { | |
| 711 | var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); | |
| 712 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); | |
| 713 | const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); | |
| 714 | const dyn_num = p_filesz / dyn_size; | |
| 715 | var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; | |
| 716 | var dyn_i: usize = 0; | |
| 717 | dyn: while (dyn_i < dyn_num) { | |
| 718 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 719 | // even when the ELF file is 32-bits. | |
| 720 | const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); | |
| 721 | const dyn_read_byte_len = try preadMin( | |
| 722 | file, | |
| 723 | dyn_buf[0 .. dyn_buf.len - dyn_reserve], | |
| 724 | dyn_off, | |
| 725 | dyn_size, | |
| 726 | ); | |
| 727 | var dyn_buf_i: usize = 0; | |
| 728 | while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ | |
| 729 | dyn_i += 1; | |
| 730 | dyn_off += dyn_size; | |
| 731 | dyn_buf_i += dyn_size; | |
| 732 | }) { | |
| 733 | const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); | |
| 734 | const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); | |
| 735 | const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); | |
| 736 | const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); | |
| 737 | if (tag == elf.DT_RUNPATH) { | |
| 738 | rpath_offset = val; | |
| 739 | break :dyn; | |
| 740 | } | |
| 741 | } | |
| 742 | } | |
| 743 | }, | |
| 744 | else => continue, | |
| 745 | } | |
| 746 | } | |
| 747 | } | |
| 748 | ||
| 749 | if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and | |
| 750 | query.glibc_version == null) | |
| 751 | { | |
| 752 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); | |
| 753 | ||
| 754 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); | |
| 755 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); | |
| 756 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); | |
| 757 | ||
| 758 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; | |
| 759 | if (sh_buf.len < shentsize) return error.InvalidElfFile; | |
| 760 | ||
| 761 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); | |
| 762 | const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 763 | const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); | |
| 764 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); | |
| 765 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); | |
| 766 | var strtab_buf: [4096:0]u8 = undefined; | |
| 767 | const shstrtab_len = @min(shstrtab_size, strtab_buf.len); | |
| 768 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); | |
| 769 | const shstrtab = strtab_buf[0..shstrtab_read_len]; | |
| 770 | ||
| 771 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); | |
| 772 | var sh_i: u16 = 0; | |
| 773 | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { | |
| 774 | // Reserve some bytes so that we can deref the 64-bit struct fields | |
| 775 | // even when the ELF file is 32-bits. | |
| 776 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); | |
| 777 | const sh_read_byte_len = try preadMin( | |
| 778 | file, | |
| 779 | sh_buf[0 .. sh_buf.len - sh_reserve], | |
| 780 | shoff, | |
| 781 | shentsize, | |
| 782 | ); | |
| 783 | var sh_buf_i: usize = 0; | |
| 784 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ | |
| 785 | sh_i += 1; | |
| 786 | shoff += shentsize; | |
| 787 | sh_buf_i += shentsize; | |
| 788 | }) { | |
| 789 | const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 790 | const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); | |
| 791 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); | |
| 792 | const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); | |
| 793 | if (mem.eql(u8, sh_name, ".dynstr")) { | |
| 794 | break :find_dyn_str .{ | |
| 795 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), | |
| 796 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), | |
| 797 | }; | |
| 798 | } | |
| 799 | } | |
| 800 | } else null; | |
| 801 | ||
| 802 | if (dynstr) |ds| { | |
| 803 | if (rpath_offset) |rpoff| { | |
| 804 | if (rpoff > ds.size) return error.InvalidElfFile; | |
| 805 | const rpoff_file = ds.offset + rpoff; | |
| 806 | const rp_max_size = ds.size - rpoff; | |
| 807 | ||
| 808 | const strtab_len = @min(rp_max_size, strtab_buf.len); | |
| 809 | const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len); | |
| 810 | const strtab = strtab_buf[0..strtab_read_len]; | |
| 811 | ||
| 812 | const rpath_list = mem.sliceTo(strtab, 0); | |
| 813 | var it = mem.tokenizeScalar(u8, rpath_list, ':'); | |
| 814 | while (it.next()) |rpath| { | |
| 815 | if (glibcVerFromRPath(rpath)) |ver| { | |
| 816 | result.target.os.version_range.linux.glibc = ver; | |
| 817 | return result; | |
| 818 | } else |err| switch (err) { | |
| 819 | error.GLibCNotFound => continue, | |
| 820 | else => |e| return e, | |
| 821 | } | |
| 822 | } | |
| 823 | } | |
| 824 | } | |
| 825 | ||
| 826 | if (result.dynamic_linker.get()) |dl_path| glibc_ver: { | |
| 827 | // There is no DT_RUNPATH so we try to find libc.so.6 inside the same | |
| 828 | // directory as the dynamic linker. | |
| 829 | if (fs.path.dirname(dl_path)) |rpath| { | |
| 830 | if (glibcVerFromRPath(rpath)) |ver| { | |
| 831 | result.target.os.version_range.linux.glibc = ver; | |
| 832 | return result; | |
| 833 | } else |err| switch (err) { | |
| 834 | error.GLibCNotFound => {}, | |
| 835 | else => |e| return e, | |
| 836 | } | |
| 837 | } | |
| 838 | ||
| 839 | // So far, no luck. Next we try to see if the information is | |
| 840 | // present in the symlink data for the dynamic linker path. | |
| 841 | var link_buf: [std.os.PATH_MAX]u8 = undefined; | |
| 842 | const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { | |
| 843 | error.NameTooLong => unreachable, | |
| 844 | error.InvalidUtf8 => unreachable, // Windows only | |
| 845 | error.BadPathName => unreachable, // Windows only | |
| 846 | error.UnsupportedReparsePointType => unreachable, // Windows only | |
| 847 | error.NetworkNotFound => unreachable, // Windows only | |
| 848 | ||
| 849 | error.AccessDenied, | |
| 850 | error.FileNotFound, | |
| 851 | error.NotLink, | |
| 852 | error.NotDir, | |
| 853 | => break :glibc_ver, | |
| 854 | ||
| 855 | error.SystemResources, | |
| 856 | error.FileSystem, | |
| 857 | error.SymLinkLoop, | |
| 858 | error.Unexpected, | |
| 859 | => |e| return e, | |
| 860 | }; | |
| 861 | result.target.os.version_range.linux.glibc = glibcVerFromLinkName( | |
| 862 | fs.path.basename(link_name), | |
| 863 | "ld-", | |
| 864 | ) catch |err| switch (err) { | |
| 865 | error.UnrecognizedGnuLibCFileName, | |
| 866 | error.InvalidGnuLibCVersion, | |
| 867 | => break :glibc_ver, | |
| 868 | }; | |
| 869 | return result; | |
| 870 | } | |
| 871 | ||
| 872 | // Nothing worked so far. Finally we fall back to hard-coded search paths. | |
| 873 | // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`. | |
| 874 | var path_buf: [std.os.PATH_MAX]u8 = undefined; | |
| 875 | var index: usize = 0; | |
| 876 | const prefix = "/lib/"; | |
| 877 | const cpu_arch = @tagName(result.target.cpu.arch); | |
| 878 | const os_tag = @tagName(result.target.os.tag); | |
| 879 | const abi = @tagName(result.target.abi); | |
| 880 | @memcpy(path_buf[index..][0..prefix.len], prefix); | |
| 881 | index += prefix.len; | |
| 882 | @memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch); | |
| 883 | index += cpu_arch.len; | |
| 884 | path_buf[index] = '-'; | |
| 885 | index += 1; | |
| 886 | @memcpy(path_buf[index..][0..os_tag.len], os_tag); | |
| 887 | index += os_tag.len; | |
| 888 | path_buf[index] = '-'; | |
| 889 | index += 1; | |
| 890 | @memcpy(path_buf[index..][0..abi.len], abi); | |
| 891 | index += abi.len; | |
| 892 | const rpath = path_buf[0..index]; | |
| 893 | if (glibcVerFromRPath(rpath)) |ver| { | |
| 894 | result.target.os.version_range.linux.glibc = ver; | |
| 895 | return result; | |
| 896 | } else |err| switch (err) { | |
| 897 | error.GLibCNotFound => {}, | |
| 898 | else => |e| return e, | |
| 899 | } | |
| 900 | } | |
| 901 | ||
| 902 | return result; | |
| 903 | } | |
| 904 | ||
| 905 | fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { | |
| 906 | var i: usize = 0; | |
| 907 | while (i < min_read_len) { | |
| 908 | const len = file.pread(buf[i..], offset + i) catch |err| switch (err) { | |
| 909 | error.OperationAborted => unreachable, // Windows-only | |
| 910 | error.WouldBlock => unreachable, // Did not request blocking mode | |
| 911 | error.NotOpenForReading => unreachable, | |
| 912 | error.SystemResources => return error.SystemResources, | |
| 913 | error.IsDir => return error.UnableToReadElfFile, | |
| 914 | error.BrokenPipe => return error.UnableToReadElfFile, | |
| 915 | error.Unseekable => return error.UnableToReadElfFile, | |
| 916 | error.ConnectionResetByPeer => return error.UnableToReadElfFile, | |
| 917 | error.ConnectionTimedOut => return error.UnableToReadElfFile, | |
| 918 | error.SocketNotConnected => return error.UnableToReadElfFile, | |
| 919 | error.NetNameDeleted => return error.UnableToReadElfFile, | |
| 920 | error.Unexpected => return error.Unexpected, | |
| 921 | error.InputOutput => return error.FileSystem, | |
| 922 | error.AccessDenied => return error.Unexpected, | |
| 923 | }; | |
| 924 | if (len == 0) return error.UnexpectedEndOfFile; | |
| 925 | i += len; | |
| 926 | } | |
| 927 | return i; | |
| 928 | } | |
| 929 | ||
| 930 | fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !NativeTargetInfo { | |
| 931 | const target: Target = .{ | |
| 932 | .cpu = cpu, | |
| 933 | .os = os, | |
| 934 | .abi = query.abi orelse Target.Abi.default(cpu.arch, os), | |
| 935 | .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), | |
| 936 | }; | |
| 937 | return NativeTargetInfo{ | |
| 938 | .target = target, | |
| 939 | .dynamic_linker = if (query.dynamic_linker.get() == null) | |
| 940 | target.standardDynamicLinkerPath() | |
| 941 | else | |
| 942 | query.dynamic_linker, | |
| 943 | }; | |
| 944 | } | |
| 945 | ||
| 946 | pub const LdInfo = struct { | |
| 947 | ld: DynamicLinker, | |
| 948 | abi: Target.Abi, | |
| 949 | }; | |
| 950 | ||
| 951 | pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { | |
| 952 | if (is_64) { | |
| 953 | if (need_bswap) { | |
| 954 | return @byteSwap(int_64); | |
| 955 | } else { | |
| 956 | return int_64; | |
| 957 | } | |
| 958 | } else { | |
| 959 | if (need_bswap) { | |
| 960 | return @byteSwap(int_32); | |
| 961 | } else { | |
| 962 | return int_32; | |
| 963 | } | |
| 964 | } | |
| 965 | } | |
| 966 | ||
| 967 | fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu { | |
| 968 | // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`, | |
| 969 | // although it is a runtime value, is guaranteed to be one of the architectures in the set | |
| 970 | // of the respective switch prong. | |
| 971 | switch (builtin.cpu.arch) { | |
| 972 | .x86_64, .x86 => { | |
| 973 | return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query); | |
| 974 | }, | |
| 975 | else => {}, | |
| 976 | } | |
| 977 | ||
| 978 | switch (builtin.os.tag) { | |
| 979 | .linux => return linux.detectNativeCpuAndFeatures(), | |
| 980 | .macos => return darwin.macos.detectNativeCpuAndFeatures(), | |
| 981 | .windows => return windows.detectNativeCpuAndFeatures(), | |
| 982 | else => {}, | |
| 983 | } | |
| 984 | ||
| 985 | // This architecture does not have CPU model & feature detection yet. | |
| 986 | // See https://github.com/ziglang/zig/issues/4591 | |
| 987 | return null; | |
| 988 | } | |
| 989 | ||
| 990 | pub const Executor = union(enum) { | |
| 991 | native, | |
| 992 | rosetta, | |
| 993 | qemu: []const u8, | |
| 994 | wine: []const u8, | |
| 995 | wasmtime: []const u8, | |
| 996 | darling: []const u8, | |
| 997 | bad_dl: []const u8, | |
| 998 | bad_os_or_cpu, | |
| 999 | }; | |
| 1000 | ||
| 1001 | pub const GetExternalExecutorOptions = struct { | |
| 1002 | allow_darling: bool = true, | |
| 1003 | allow_qemu: bool = true, | |
| 1004 | allow_rosetta: bool = true, | |
| 1005 | allow_wasmtime: bool = true, | |
| 1006 | allow_wine: bool = true, | |
| 1007 | qemu_fixes_dl: bool = false, | |
| 1008 | link_libc: bool = false, | |
| 1009 | }; | |
| 1010 | ||
| 1011 | /// Return whether or not the given host is capable of running executables of | |
| 1012 | /// the other target. | |
| 1013 | pub fn getExternalExecutor( | |
| 1014 | host: NativeTargetInfo, | |
| 1015 | candidate: *const NativeTargetInfo, | |
| 1016 | options: GetExternalExecutorOptions, | |
| 1017 | ) Executor { | |
| 1018 | const os_match = host.target.os.tag == candidate.target.os.tag; | |
| 1019 | const cpu_ok = cpu_ok: { | |
| 1020 | if (host.target.cpu.arch == candidate.target.cpu.arch) | |
| 1021 | break :cpu_ok true; | |
| 1022 | ||
| 1023 | if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .x86) | |
| 1024 | break :cpu_ok true; | |
| 1025 | ||
| 1026 | if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm) | |
| 1027 | break :cpu_ok true; | |
| 1028 | ||
| 1029 | if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb) | |
| 1030 | break :cpu_ok true; | |
| 1031 | ||
| 1032 | // TODO additionally detect incompatible CPU features. | |
| 1033 | // Note that in some cases the OS kernel will emulate missing CPU features | |
| 1034 | // when an illegal instruction is encountered. | |
| 1035 | ||
| 1036 | break :cpu_ok false; | |
| 1037 | }; | |
| 1038 | ||
| 1039 | var bad_result: Executor = .bad_os_or_cpu; | |
| 1040 | ||
| 1041 | if (os_match and cpu_ok) native: { | |
| 1042 | if (options.link_libc) { | |
| 1043 | if (candidate.dynamic_linker.get()) |candidate_dl| { | |
| 1044 | fs.cwd().access(candidate_dl, .{}) catch { | |
| 1045 | bad_result = .{ .bad_dl = candidate_dl }; | |
| 1046 | break :native; | |
| 1047 | }; | |
| 1048 | } | |
| 1049 | } | |
| 1050 | return .native; | |
| 1051 | } | |
| 1052 | ||
| 1053 | // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 | |
| 1054 | // to emulate the foreign architecture. | |
| 1055 | if (options.allow_rosetta and os_match and | |
| 1056 | host.target.os.tag == .macos and host.target.cpu.arch == .aarch64) | |
| 1057 | { | |
| 1058 | switch (candidate.target.cpu.arch) { | |
| 1059 | .x86_64 => return .rosetta, | |
| 1060 | else => return bad_result, | |
| 1061 | } | |
| 1062 | } | |
| 1063 | ||
| 1064 | // If the OS matches, we can use QEMU to emulate a foreign architecture. | |
| 1065 | if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { | |
| 1066 | return switch (candidate.target.cpu.arch) { | |
| 1067 | .aarch64 => Executor{ .qemu = "qemu-aarch64" }, | |
| 1068 | .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, | |
| 1069 | .arm => Executor{ .qemu = "qemu-arm" }, | |
| 1070 | .armeb => Executor{ .qemu = "qemu-armeb" }, | |
| 1071 | .hexagon => Executor{ .qemu = "qemu-hexagon" }, | |
| 1072 | .x86 => Executor{ .qemu = "qemu-i386" }, | |
| 1073 | .m68k => Executor{ .qemu = "qemu-m68k" }, | |
| 1074 | .mips => Executor{ .qemu = "qemu-mips" }, | |
| 1075 | .mipsel => Executor{ .qemu = "qemu-mipsel" }, | |
| 1076 | .mips64 => Executor{ .qemu = "qemu-mips64" }, | |
| 1077 | .mips64el => Executor{ .qemu = "qemu-mips64el" }, | |
| 1078 | .powerpc => Executor{ .qemu = "qemu-ppc" }, | |
| 1079 | .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, | |
| 1080 | .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, | |
| 1081 | .riscv32 => Executor{ .qemu = "qemu-riscv32" }, | |
| 1082 | .riscv64 => Executor{ .qemu = "qemu-riscv64" }, | |
| 1083 | .s390x => Executor{ .qemu = "qemu-s390x" }, | |
| 1084 | .sparc => Executor{ .qemu = "qemu-sparc" }, | |
| 1085 | .sparc64 => Executor{ .qemu = "qemu-sparc64" }, | |
| 1086 | .x86_64 => Executor{ .qemu = "qemu-x86_64" }, | |
| 1087 | else => return bad_result, | |
| 1088 | }; | |
| 1089 | } | |
| 1090 | ||
| 1091 | switch (candidate.target.os.tag) { | |
| 1092 | .windows => { | |
| 1093 | if (options.allow_wine) { | |
| 1094 | // x86_64 wine does not support emulating aarch64-windows and | |
| 1095 | // vice versa. | |
| 1096 | if (candidate.target.cpu.arch != builtin.cpu.arch) { | |
| 1097 | return bad_result; | |
| 1098 | } | |
| 1099 | switch (candidate.target.ptrBitWidth()) { | |
| 1100 | 32 => return Executor{ .wine = "wine" }, | |
| 1101 | 64 => return Executor{ .wine = "wine64" }, | |
| 1102 | else => return bad_result, | |
| 1103 | } | |
| 1104 | } | |
| 1105 | return bad_result; | |
| 1106 | }, | |
| 1107 | .wasi => { | |
| 1108 | if (options.allow_wasmtime) { | |
| 1109 | switch (candidate.target.ptrBitWidth()) { | |
| 1110 | 32 => return Executor{ .wasmtime = "wasmtime" }, | |
| 1111 | else => return bad_result, | |
| 1112 | } | |
| 1113 | } | |
| 1114 | return bad_result; | |
| 1115 | }, | |
| 1116 | .macos => { | |
| 1117 | if (options.allow_darling) { | |
| 1118 | // This check can be loosened once darling adds a QEMU-based emulation | |
| 1119 | // layer for non-host architectures: | |
| 1120 | // https://github.com/darlinghq/darling/issues/863 | |
| 1121 | if (candidate.target.cpu.arch != builtin.cpu.arch) { | |
| 1122 | return bad_result; | |
| 1123 | } | |
| 1124 | return Executor{ .darling = "darling" }; | |
| 1125 | } | |
| 1126 | return bad_result; | |
| 1127 | }, | |
| 1128 | else => return bad_result, | |
| 1129 | } | |
| 1130 | } |
src/Compilation.zig+21-6| ... | ... | @@ -6527,7 +6527,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca |
| 6527 | 6527 | try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)}); |
| 6528 | 6528 | } |
| 6529 | 6529 | } |
| 6530 | ||
| 6531 | 6530 | try buffer.writer().print( |
| 6532 | 6531 | \\ }}), |
| 6533 | 6532 | \\}}; |
| ... | ... | @@ -6607,15 +6606,31 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca |
| 6607 | 6606 | .{ windows.min, windows.max }, |
| 6608 | 6607 | ), |
| 6609 | 6608 | } |
| 6610 | try buffer.appendSlice("};\n"); | |
| 6611 | ||
| 6612 | try buffer.writer().print( | |
| 6613 | \\pub const target = std.Target{{ | |
| 6609 | try buffer.appendSlice( | |
| 6610 | \\}; | |
| 6611 | \\pub const target: std.Target = .{ | |
| 6614 | 6612 | \\ .cpu = cpu, |
| 6615 | 6613 | \\ .os = os, |
| 6616 | 6614 | \\ .abi = abi, |
| 6617 | 6615 | \\ .ofmt = object_format, |
| 6618 | \\}}; | |
| 6616 | \\ | |
| 6617 | ); | |
| 6618 | ||
| 6619 | if (target.dynamic_linker.get()) |dl| { | |
| 6620 | try buffer.writer().print( | |
| 6621 | \\ .dynamic_linker = std.Target.DynamicLinker.init("{s}"), | |
| 6622 | \\}}; | |
| 6623 | \\ | |
| 6624 | , .{dl}); | |
| 6625 | } else { | |
| 6626 | try buffer.appendSlice( | |
| 6627 | \\ .dynamic_linker = std.Target.DynamicLinker.none, | |
| 6628 | \\}; | |
| 6629 | \\ | |
| 6630 | ); | |
| 6631 | } | |
| 6632 | ||
| 6633 | try buffer.writer().print( | |
| 6619 | 6634 | \\pub const object_format = std.Target.ObjectFormat.{}; |
| 6620 | 6635 | \\pub const mode = std.builtin.OptimizeMode.{}; |
| 6621 | 6636 | \\pub const link_libc = {}; |
src/main.zig+62-70| ... | ... | @@ -321,13 +321,14 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi |
| 321 | 321 | } else if (mem.eql(u8, cmd, "init")) { |
| 322 | 322 | return cmdInit(gpa, arena, cmd_args); |
| 323 | 323 | } else if (mem.eql(u8, cmd, "targets")) { |
| 324 | const info = try detectNativeTargetInfo(.{}); | |
| 324 | const host = try std.zig.system.resolveTargetQuery(.{}); | |
| 325 | 325 | const stdout = io.getStdOut().writer(); |
| 326 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); | |
| 326 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, host); | |
| 327 | 327 | } else if (mem.eql(u8, cmd, "version")) { |
| 328 | 328 | try std.io.getStdOut().writeAll(build_options.version ++ "\n"); |
| 329 | // Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version" | |
| 330 | // to avoid affecting the startup time for build-critical commands (check takes about ~10 μs) | |
| 329 | // Check libc++ linkage to make sure Zig was built correctly, but only | |
| 330 | // for "env" and "version" to avoid affecting the startup time for | |
| 331 | // build-critical commands (check takes about ~10 μs) | |
| 331 | 332 | return verifyLibcxxCorrectlyLinked(); |
| 332 | 333 | } else if (mem.eql(u8, cmd, "env")) { |
| 333 | 334 | verifyLibcxxCorrectlyLinked(); |
| ... | ... | @@ -2608,9 +2609,9 @@ fn buildOutputType( |
| 2608 | 2609 | } |
| 2609 | 2610 | |
| 2610 | 2611 | const target_query = try parseTargetQueryOrReportFatalError(arena, target_parse_options); |
| 2611 | const target_info = try detectNativeTargetInfo(target_query); | |
| 2612 | const target = try std.zig.system.resolveTargetQuery(target_query); | |
| 2612 | 2613 | |
| 2613 | if (target_info.target.os.tag != .freestanding) { | |
| 2614 | if (target.os.tag != .freestanding) { | |
| 2614 | 2615 | if (ensure_libc_on_non_freestanding) |
| 2615 | 2616 | link_libc = true; |
| 2616 | 2617 | if (ensure_libcpp_on_non_freestanding) |
| ... | ... | @@ -2621,7 +2622,7 @@ fn buildOutputType( |
| 2621 | 2622 | if (!force) { |
| 2622 | 2623 | entry = null; |
| 2623 | 2624 | } else if (entry == null and output_mode == .Exe) { |
| 2624 | entry = switch (target_info.target.ofmt) { | |
| 2625 | entry = switch (target.ofmt) { | |
| 2625 | 2626 | .coff => "wWinMainCRTStartup", |
| 2626 | 2627 | .macho => "_main", |
| 2627 | 2628 | .elf, .plan9 => "_start", |
| ... | ... | @@ -2629,12 +2630,12 @@ fn buildOutputType( |
| 2629 | 2630 | else => |tag| fatal("No default entry point available for output format {s}", .{@tagName(tag)}), |
| 2630 | 2631 | }; |
| 2631 | 2632 | } |
| 2632 | } else if (entry == null and target_info.target.isWasm() and output_mode == .Exe) { | |
| 2633 | } else if (entry == null and target.isWasm() and output_mode == .Exe) { | |
| 2633 | 2634 | // For WebAssembly the compiler defaults to setting the entry name when no flags are set. |
| 2634 | 2635 | entry = defaultWasmEntryName(wasi_exec_model); |
| 2635 | 2636 | } |
| 2636 | 2637 | |
| 2637 | if (target_info.target.ofmt == .coff) { | |
| 2638 | if (target.ofmt == .coff) { | |
| 2638 | 2639 | // Now that we know the target supports resources, |
| 2639 | 2640 | // we can add the res files as link objects. |
| 2640 | 2641 | for (res_files.items) |res_file| { |
| ... | ... | @@ -2652,7 +2653,7 @@ fn buildOutputType( |
| 2652 | 2653 | } |
| 2653 | 2654 | } |
| 2654 | 2655 | |
| 2655 | if (target_info.target.cpu.arch.isWasm()) blk: { | |
| 2656 | if (target.cpu.arch.isWasm()) blk: { | |
| 2656 | 2657 | if (single_threaded == null) { |
| 2657 | 2658 | single_threaded = true; |
| 2658 | 2659 | } |
| ... | ... | @@ -2678,8 +2679,8 @@ fn buildOutputType( |
| 2678 | 2679 | fatal("shared memory is not allowed in object files", .{}); |
| 2679 | 2680 | } |
| 2680 | 2681 | |
| 2681 | if (!target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or | |
| 2682 | !target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory))) | |
| 2682 | if (!target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or | |
| 2683 | !target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory))) | |
| 2683 | 2684 | { |
| 2684 | 2685 | fatal("'atomics' and 'bulk-memory' features must be enabled to use shared memory", .{}); |
| 2685 | 2686 | } |
| ... | ... | @@ -2777,15 +2778,15 @@ fn buildOutputType( |
| 2777 | 2778 | } |
| 2778 | 2779 | |
| 2779 | 2780 | for (system_libs.keys(), system_libs.values()) |lib_name, info| { |
| 2780 | if (target_info.target.is_libc_lib_name(lib_name)) { | |
| 2781 | if (target.is_libc_lib_name(lib_name)) { | |
| 2781 | 2782 | link_libc = true; |
| 2782 | 2783 | continue; |
| 2783 | 2784 | } |
| 2784 | if (target_info.target.is_libcpp_lib_name(lib_name)) { | |
| 2785 | if (target.is_libcpp_lib_name(lib_name)) { | |
| 2785 | 2786 | link_libcpp = true; |
| 2786 | 2787 | continue; |
| 2787 | 2788 | } |
| 2788 | switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) { | |
| 2789 | switch (target_util.classifyCompilerRtLibName(target, lib_name)) { | |
| 2789 | 2790 | .none => {}, |
| 2790 | 2791 | .only_libunwind, .both => { |
| 2791 | 2792 | link_libunwind = true; |
| ... | ... | @@ -2797,8 +2798,8 @@ fn buildOutputType( |
| 2797 | 2798 | }, |
| 2798 | 2799 | } |
| 2799 | 2800 | |
| 2800 | if (target_info.target.isMinGW()) { | |
| 2801 | const exists = mingw.libExists(arena, target_info.target, zig_lib_directory, lib_name) catch |err| { | |
| 2801 | if (target.isMinGW()) { | |
| 2802 | const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| { | |
| 2802 | 2803 | fatal("failed to check zig installation for DLL import libs: {s}", .{ |
| 2803 | 2804 | @errorName(err), |
| 2804 | 2805 | }); |
| ... | ... | @@ -2820,7 +2821,7 @@ fn buildOutputType( |
| 2820 | 2821 | fatal("cannot use absolute path as a system library: {s}", .{lib_name}); |
| 2821 | 2822 | } |
| 2822 | 2823 | |
| 2823 | if (target_info.target.os.tag == .wasi) { | |
| 2824 | if (target.os.tag == .wasi) { | |
| 2824 | 2825 | if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| { |
| 2825 | 2826 | try wasi_emulated_libs.append(crt_file); |
| 2826 | 2827 | continue; |
| ... | ... | @@ -2838,7 +2839,7 @@ fn buildOutputType( |
| 2838 | 2839 | if (sysroot == null and target_query.isNativeOs() and target_query.isNativeAbi() and |
| 2839 | 2840 | (external_system_libs.len != 0 or want_native_include_dirs)) |
| 2840 | 2841 | { |
| 2841 | const paths = std.zig.system.NativePaths.detect(arena, target_info) catch |err| { | |
| 2842 | const paths = std.zig.system.NativePaths.detect(arena, target) catch |err| { | |
| 2842 | 2843 | fatal("unable to detect native system paths: {s}", .{@errorName(err)}); |
| 2843 | 2844 | }; |
| 2844 | 2845 | for (paths.warnings.items) |warning| { |
| ... | ... | @@ -2857,7 +2858,7 @@ fn buildOutputType( |
| 2857 | 2858 | } |
| 2858 | 2859 | |
| 2859 | 2860 | if (builtin.target.os.tag == .windows and |
| 2860 | target_info.target.abi == .msvc and | |
| 2861 | target.abi == .msvc and | |
| 2861 | 2862 | external_system_libs.len != 0) |
| 2862 | 2863 | { |
| 2863 | 2864 | if (libc_installation == null) { |
| ... | ... | @@ -2902,7 +2903,7 @@ fn buildOutputType( |
| 2902 | 2903 | &checked_paths, |
| 2903 | 2904 | lib_dir_path, |
| 2904 | 2905 | lib_name, |
| 2905 | target_info.target, | |
| 2906 | target, | |
| 2906 | 2907 | info.preferred_mode, |
| 2907 | 2908 | )) { |
| 2908 | 2909 | const path = try arena.dupe(u8, test_path.items); |
| ... | ... | @@ -2936,7 +2937,7 @@ fn buildOutputType( |
| 2936 | 2937 | &checked_paths, |
| 2937 | 2938 | lib_dir_path, |
| 2938 | 2939 | lib_name, |
| 2939 | target_info.target, | |
| 2940 | target, | |
| 2940 | 2941 | info.fallbackMode(), |
| 2941 | 2942 | )) { |
| 2942 | 2943 | const path = try arena.dupe(u8, test_path.items); |
| ... | ... | @@ -2970,7 +2971,7 @@ fn buildOutputType( |
| 2970 | 2971 | &checked_paths, |
| 2971 | 2972 | lib_dir_path, |
| 2972 | 2973 | lib_name, |
| 2973 | target_info.target, | |
| 2974 | target, | |
| 2974 | 2975 | info.preferred_mode, |
| 2975 | 2976 | )) { |
| 2976 | 2977 | const path = try arena.dupe(u8, test_path.items); |
| ... | ... | @@ -2994,7 +2995,7 @@ fn buildOutputType( |
| 2994 | 2995 | &checked_paths, |
| 2995 | 2996 | lib_dir_path, |
| 2996 | 2997 | lib_name, |
| 2997 | target_info.target, | |
| 2998 | target, | |
| 2998 | 2999 | info.fallbackMode(), |
| 2999 | 3000 | )) { |
| 3000 | 3001 | const path = try arena.dupe(u8, test_path.items); |
| ... | ... | @@ -3089,15 +3090,13 @@ fn buildOutputType( |
| 3089 | 3090 | } |
| 3090 | 3091 | // After this point, resolved_frameworks is used instead of frameworks. |
| 3091 | 3092 | |
| 3092 | const object_format = target_info.target.ofmt; | |
| 3093 | ||
| 3094 | if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) { | |
| 3093 | if (output_mode == .Obj and (target.ofmt == .coff or target.ofmt == .macho)) { | |
| 3095 | 3094 | const total_obj_count = c_source_files.items.len + |
| 3096 | 3095 | @intFromBool(root_src_file != null) + |
| 3097 | 3096 | rc_source_files.items.len + |
| 3098 | 3097 | link_objects.items.len; |
| 3099 | 3098 | if (total_obj_count > 1) { |
| 3100 | fatal("{s} does not support linking multiple objects into one", .{@tagName(object_format)}); | |
| 3099 | fatal("{s} does not support linking multiple objects into one", .{@tagName(target.ofmt)}); | |
| 3101 | 3100 | } |
| 3102 | 3101 | } |
| 3103 | 3102 | |
| ... | ... | @@ -3110,7 +3109,7 @@ fn buildOutputType( |
| 3110 | 3109 | const resolved_soname: ?[]const u8 = switch (soname) { |
| 3111 | 3110 | .yes => |explicit| explicit, |
| 3112 | 3111 | .no => null, |
| 3113 | .yes_default_value => switch (object_format) { | |
| 3112 | .yes_default_value => switch (target.ofmt) { | |
| 3114 | 3113 | .elf => if (have_version) |
| 3115 | 3114 | try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ root_name, version.major }) |
| 3116 | 3115 | else |
| ... | ... | @@ -3119,7 +3118,7 @@ fn buildOutputType( |
| 3119 | 3118 | }, |
| 3120 | 3119 | }; |
| 3121 | 3120 | |
| 3122 | const a_out_basename = switch (object_format) { | |
| 3121 | const a_out_basename = switch (target.ofmt) { | |
| 3123 | 3122 | .coff => "a.exe", |
| 3124 | 3123 | else => "a.out", |
| 3125 | 3124 | }; |
| ... | ... | @@ -3141,7 +3140,7 @@ fn buildOutputType( |
| 3141 | 3140 | }, |
| 3142 | 3141 | .basename = try std.zig.binNameAlloc(arena, .{ |
| 3143 | 3142 | .root_name = root_name, |
| 3144 | .target = target_info.target, | |
| 3143 | .target = target, | |
| 3145 | 3144 | .output_mode = output_mode, |
| 3146 | 3145 | .link_mode = link_mode, |
| 3147 | 3146 | .version = optional_version, |
| ... | ... | @@ -3269,7 +3268,7 @@ fn buildOutputType( |
| 3269 | 3268 | // Note that cmake when targeting Windows will try to execute |
| 3270 | 3269 | // zig cc to make an executable and output an implib too. |
| 3271 | 3270 | const implib_eligible = is_exe_or_dyn_lib and |
| 3272 | emit_bin_loc != null and target_info.target.os.tag == .windows; | |
| 3271 | emit_bin_loc != null and target.os.tag == .windows; | |
| 3273 | 3272 | if (!implib_eligible) { |
| 3274 | 3273 | if (!emit_implib_arg_provided) { |
| 3275 | 3274 | emit_implib = .no; |
| ... | ... | @@ -3419,7 +3418,7 @@ fn buildOutputType( |
| 3419 | 3418 | // "-" is stdin. Dump it to a real file. |
| 3420 | 3419 | const sep = fs.path.sep_str; |
| 3421 | 3420 | const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{ |
| 3422 | std.crypto.random.int(u64), ext.canonicalName(target_info.target), | |
| 3421 | std.crypto.random.int(u64), ext.canonicalName(target), | |
| 3423 | 3422 | }); |
| 3424 | 3423 | try local_cache_directory.handle.makePath("tmp"); |
| 3425 | 3424 | // Note that in one of the happy paths, execve() is used to switch |
| ... | ... | @@ -3454,10 +3453,10 @@ fn buildOutputType( |
| 3454 | 3453 | .local_cache_directory = local_cache_directory, |
| 3455 | 3454 | .global_cache_directory = global_cache_directory, |
| 3456 | 3455 | .root_name = root_name, |
| 3457 | .target = target_info.target, | |
| 3456 | .target = target, | |
| 3458 | 3457 | .is_native_os = target_query.isNativeOs(), |
| 3459 | 3458 | .is_native_abi = target_query.isNativeAbi(), |
| 3460 | .dynamic_linker = target_info.dynamic_linker.get(), | |
| 3459 | .dynamic_linker = target.dynamic_linker.get(), | |
| 3461 | 3460 | .sysroot = sysroot, |
| 3462 | 3461 | .output_mode = output_mode, |
| 3463 | 3462 | .main_mod = main_mod, |
| ... | ... | @@ -3603,7 +3602,6 @@ fn buildOutputType( |
| 3603 | 3602 | .want_structured_cfg = want_structured_cfg, |
| 3604 | 3603 | }) catch |err| switch (err) { |
| 3605 | 3604 | error.LibCUnavailable => { |
| 3606 | const target = target_info.target; | |
| 3607 | 3605 | const triple_name = try target.zigTriple(arena); |
| 3608 | 3606 | std.log.err("unable to find or provide libc for target '{s}'", .{triple_name}); |
| 3609 | 3607 | |
| ... | ... | @@ -3692,7 +3690,7 @@ fn buildOutputType( |
| 3692 | 3690 | try comp.makeBinFileExecutable(); |
| 3693 | 3691 | saveState(comp, debug_incremental); |
| 3694 | 3692 | |
| 3695 | if (test_exec_args.items.len == 0 and object_format == .c) default_exec_args: { | |
| 3693 | if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: { | |
| 3696 | 3694 | // Default to using `zig run` to execute the produced .c code from `zig test`. |
| 3697 | 3695 | const c_code_loc = emit_bin_loc orelse break :default_exec_args; |
| 3698 | 3696 | const c_code_directory = c_code_loc.directory orelse comp.bin_file.options.emit.?.directory; |
| ... | ... | @@ -3707,7 +3705,7 @@ fn buildOutputType( |
| 3707 | 3705 | |
| 3708 | 3706 | if (link_libc) { |
| 3709 | 3707 | try test_exec_args.append("-lc"); |
| 3710 | } else if (target_info.target.os.tag == .windows) { | |
| 3708 | } else if (target.os.tag == .windows) { | |
| 3711 | 3709 | try test_exec_args.appendSlice(&.{ |
| 3712 | 3710 | "--subsystem", "console", |
| 3713 | 3711 | "-lkernel32", "-lntdll", |
| ... | ... | @@ -3741,7 +3739,7 @@ fn buildOutputType( |
| 3741 | 3739 | test_exec_args.items, |
| 3742 | 3740 | self_exe_path.?, |
| 3743 | 3741 | arg_mode, |
| 3744 | &target_info, | |
| 3742 | &target, | |
| 3745 | 3743 | &comp_destroyed, |
| 3746 | 3744 | all_args, |
| 3747 | 3745 | runtime_args_start, |
| ... | ... | @@ -3861,7 +3859,7 @@ fn serve( |
| 3861 | 3859 | // test_exec_args, |
| 3862 | 3860 | // self_exe_path.?, |
| 3863 | 3861 | // arg_mode, |
| 3864 | // target_info, | |
| 3862 | // target, | |
| 3865 | 3863 | // true, |
| 3866 | 3864 | // &comp_destroyed, |
| 3867 | 3865 | // all_args, |
| ... | ... | @@ -4071,7 +4069,7 @@ fn runOrTest( |
| 4071 | 4069 | test_exec_args: []const ?[]const u8, |
| 4072 | 4070 | self_exe_path: []const u8, |
| 4073 | 4071 | arg_mode: ArgMode, |
| 4074 | target_info: *const std.zig.system.NativeTargetInfo, | |
| 4072 | target: *const std.Target, | |
| 4075 | 4073 | comp_destroyed: *bool, |
| 4076 | 4074 | all_args: []const []const u8, |
| 4077 | 4075 | runtime_args_start: ?usize, |
| ... | ... | @@ -4105,7 +4103,7 @@ fn runOrTest( |
| 4105 | 4103 | if (process.can_execv and arg_mode == .run) { |
| 4106 | 4104 | // execv releases the locks; no need to destroy the Compilation here. |
| 4107 | 4105 | const err = process.execve(gpa, argv.items, &env_map); |
| 4108 | try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); | |
| 4106 | try warnAboutForeignBinaries(arena, arg_mode, target, link_libc); | |
| 4109 | 4107 | const cmd = try std.mem.join(arena, " ", argv.items); |
| 4110 | 4108 | fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd }); |
| 4111 | 4109 | } else if (process.can_spawn) { |
| ... | ... | @@ -4121,7 +4119,7 @@ fn runOrTest( |
| 4121 | 4119 | comp_destroyed.* = true; |
| 4122 | 4120 | |
| 4123 | 4121 | const term = child.spawnAndWait() catch |err| { |
| 4124 | try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); | |
| 4122 | try warnAboutForeignBinaries(arena, arg_mode, target, link_libc); | |
| 4125 | 4123 | const cmd = try std.mem.join(arena, " ", argv.items); |
| 4126 | 4124 | fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd }); |
| 4127 | 4125 | }; |
| ... | ... | @@ -4820,12 +4818,10 @@ pub fn cmdLibC(gpa: Allocator, args: []const []const u8) !void { |
| 4820 | 4818 | if (!target_query.isNative()) { |
| 4821 | 4819 | fatal("unable to detect libc for non-native target", .{}); |
| 4822 | 4820 | } |
| 4823 | const target_info = try detectNativeTargetInfo(target_query); | |
| 4824 | ||
| 4825 | 4821 | var libc = LibCInstallation.findNative(.{ |
| 4826 | 4822 | .allocator = gpa, |
| 4827 | 4823 | .verbose = true, |
| 4828 | .target = target_info.target, | |
| 4824 | .target = try std.zig.system.resolveTargetQuery(target_query), | |
| 4829 | 4825 | }) catch |err| { |
| 4830 | 4826 | fatal("unable to detect native libc: {s}", .{@errorName(err)}); |
| 4831 | 4827 | }; |
| ... | ... | @@ -5114,11 +5110,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi |
| 5114 | 5110 | gimmeMoreOfThoseSweetSweetFileDescriptors(); |
| 5115 | 5111 | |
| 5116 | 5112 | const target_query: std.Target.Query = .{}; |
| 5117 | const target_info = try detectNativeTargetInfo(target_query); | |
| 5113 | const target = try std.zig.system.resolveTargetQuery(target_query); | |
| 5118 | 5114 | |
| 5119 | 5115 | const exe_basename = try std.zig.binNameAlloc(arena, .{ |
| 5120 | 5116 | .root_name = "build", |
| 5121 | .target = target_info.target, | |
| 5117 | .target = target, | |
| 5122 | 5118 | .output_mode = .Exe, |
| 5123 | 5119 | }); |
| 5124 | 5120 | const emit_bin: Compilation.EmitLoc = .{ |
| ... | ... | @@ -5282,10 +5278,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi |
| 5282 | 5278 | .local_cache_directory = local_cache_directory, |
| 5283 | 5279 | .global_cache_directory = global_cache_directory, |
| 5284 | 5280 | .root_name = "build", |
| 5285 | .target = target_info.target, | |
| 5281 | .target = target, | |
| 5286 | 5282 | .is_native_os = target_query.isNativeOs(), |
| 5287 | 5283 | .is_native_abi = target_query.isNativeAbi(), |
| 5288 | .dynamic_linker = target_info.dynamic_linker.get(), | |
| 5284 | .dynamic_linker = target.dynamic_linker.get(), | |
| 5289 | 5285 | .output_mode = .Exe, |
| 5290 | 5286 | .main_mod = &main_mod, |
| 5291 | 5287 | .emit_bin = emit_bin, |
| ... | ... | @@ -6269,10 +6265,6 @@ test "fds" { |
| 6269 | 6265 | gimmeMoreOfThoseSweetSweetFileDescriptors(); |
| 6270 | 6266 | } |
| 6271 | 6267 | |
| 6272 | fn detectNativeTargetInfo(target_query: std.Target.Query) !std.zig.system.NativeTargetInfo { | |
| 6273 | return std.zig.system.NativeTargetInfo.detect(target_query); | |
| 6274 | } | |
| 6275 | ||
| 6276 | 6268 | const usage_ast_check = |
| 6277 | 6269 | \\Usage: zig ast-check [file] |
| 6278 | 6270 | \\ |
| ... | ... | @@ -6669,24 +6661,24 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 { |
| 6669 | 6661 | fn warnAboutForeignBinaries( |
| 6670 | 6662 | arena: Allocator, |
| 6671 | 6663 | arg_mode: ArgMode, |
| 6672 | target_info: *const std.zig.system.NativeTargetInfo, | |
| 6664 | target: *const std.Target, | |
| 6673 | 6665 | link_libc: bool, |
| 6674 | 6666 | ) !void { |
| 6675 | 6667 | const host_query: std.Target.Query = .{}; |
| 6676 | const host_target_info = try detectNativeTargetInfo(host_query); | |
| 6668 | const host_target = try std.zig.system.resolveTargetQuery(host_query); | |
| 6677 | 6669 | |
| 6678 | switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) { | |
| 6670 | switch (std.zig.system.getExternalExecutor(host_target, target, .{ .link_libc = link_libc })) { | |
| 6679 | 6671 | .native => return, |
| 6680 | 6672 | .rosetta => { |
| 6681 | const host_name = try host_target_info.target.zigTriple(arena); | |
| 6682 | const foreign_name = try target_info.target.zigTriple(arena); | |
| 6673 | const host_name = try host_target.zigTriple(arena); | |
| 6674 | const foreign_name = try target.zigTriple(arena); | |
| 6683 | 6675 | warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{ |
| 6684 | 6676 | host_name, foreign_name, |
| 6685 | 6677 | }); |
| 6686 | 6678 | }, |
| 6687 | 6679 | .qemu => |qemu| { |
| 6688 | const host_name = try host_target_info.target.zigTriple(arena); | |
| 6689 | const foreign_name = try target_info.target.zigTriple(arena); | |
| 6680 | const host_name = try host_target.zigTriple(arena); | |
| 6681 | const foreign_name = try target.zigTriple(arena); | |
| 6690 | 6682 | switch (arg_mode) { |
| 6691 | 6683 | .zig_test => warn( |
| 6692 | 6684 | "the host system ({s}) does not appear to be capable of executing binaries " ++ |
| ... | ... | @@ -6702,8 +6694,8 @@ fn warnAboutForeignBinaries( |
| 6702 | 6694 | } |
| 6703 | 6695 | }, |
| 6704 | 6696 | .wine => |wine| { |
| 6705 | const host_name = try host_target_info.target.zigTriple(arena); | |
| 6706 | const foreign_name = try target_info.target.zigTriple(arena); | |
| 6697 | const host_name = try host_target.zigTriple(arena); | |
| 6698 | const foreign_name = try target.zigTriple(arena); | |
| 6707 | 6699 | switch (arg_mode) { |
| 6708 | 6700 | .zig_test => warn( |
| 6709 | 6701 | "the host system ({s}) does not appear to be capable of executing binaries " ++ |
| ... | ... | @@ -6719,8 +6711,8 @@ fn warnAboutForeignBinaries( |
| 6719 | 6711 | } |
| 6720 | 6712 | }, |
| 6721 | 6713 | .wasmtime => |wasmtime| { |
| 6722 | const host_name = try host_target_info.target.zigTriple(arena); | |
| 6723 | const foreign_name = try target_info.target.zigTriple(arena); | |
| 6714 | const host_name = try host_target.zigTriple(arena); | |
| 6715 | const foreign_name = try target.zigTriple(arena); | |
| 6724 | 6716 | switch (arg_mode) { |
| 6725 | 6717 | .zig_test => warn( |
| 6726 | 6718 | "the host system ({s}) does not appear to be capable of executing binaries " ++ |
| ... | ... | @@ -6736,8 +6728,8 @@ fn warnAboutForeignBinaries( |
| 6736 | 6728 | } |
| 6737 | 6729 | }, |
| 6738 | 6730 | .darling => |darling| { |
| 6739 | const host_name = try host_target_info.target.zigTriple(arena); | |
| 6740 | const foreign_name = try target_info.target.zigTriple(arena); | |
| 6731 | const host_name = try host_target.zigTriple(arena); | |
| 6732 | const foreign_name = try target.zigTriple(arena); | |
| 6741 | 6733 | switch (arg_mode) { |
| 6742 | 6734 | .zig_test => warn( |
| 6743 | 6735 | "the host system ({s}) does not appear to be capable of executing binaries " ++ |
| ... | ... | @@ -6753,7 +6745,7 @@ fn warnAboutForeignBinaries( |
| 6753 | 6745 | } |
| 6754 | 6746 | }, |
| 6755 | 6747 | .bad_dl => |foreign_dl| { |
| 6756 | const host_dl = host_target_info.dynamic_linker.get() orelse "(none)"; | |
| 6748 | const host_dl = host_target.dynamic_linker.get() orelse "(none)"; | |
| 6757 | 6749 | const tip_suffix = switch (arg_mode) { |
| 6758 | 6750 | .zig_test => ", '--test-no-exec', or '--test-cmd'", |
| 6759 | 6751 | else => "", |
| ... | ... | @@ -6763,8 +6755,8 @@ fn warnAboutForeignBinaries( |
| 6763 | 6755 | }); |
| 6764 | 6756 | }, |
| 6765 | 6757 | .bad_os_or_cpu => { |
| 6766 | const host_name = try host_target_info.target.zigTriple(arena); | |
| 6767 | const foreign_name = try target_info.target.zigTriple(arena); | |
| 6758 | const host_name = try host_target.zigTriple(arena); | |
| 6759 | const foreign_name = try target.zigTriple(arena); | |
| 6768 | 6760 | const tip_suffix = switch (arg_mode) { |
| 6769 | 6761 | .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'", |
| 6770 | 6762 | else => "", |
src/print_env.zig+2-2| ... | ... | @@ -17,8 +17,8 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8, stdout: std.fs.File.Wr |
| 17 | 17 | |
| 18 | 18 | const global_cache_dir = try introspect.resolveGlobalCacheDir(arena); |
| 19 | 19 | |
| 20 | const info = try std.zig.system.NativeTargetInfo.detect(.{}); | |
| 21 | const triple = try info.target.zigTriple(arena); | |
| 20 | const host = try std.zig.system.resolveTargetQuery(.{}); | |
| 21 | const triple = try host.zigTriple(arena); | |
| 22 | 22 | |
| 23 | 23 | var bw = std.io.bufferedWriter(stdout); |
| 24 | 24 | const w = bw.writer(); |
test/src/Cases.zig+13-17| ... | ... | @@ -541,7 +541,7 @@ pub fn lowerToBuildSteps( |
| 541 | 541 | cases_dir_path: []const u8, |
| 542 | 542 | incremental_exe: *std.Build.Step.Compile, |
| 543 | 543 | ) void { |
| 544 | const host = std.zig.system.NativeTargetInfo.detect(.{}) catch |err| | |
| 544 | const host = std.zig.system.resolveTargetQuery(.{}) catch |err| | |
| 545 | 545 | std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)}); |
| 546 | 546 | |
| 547 | 547 | for (self.incremental_cases.items) |incr_case| { |
| ... | ... | @@ -648,8 +648,7 @@ pub fn lowerToBuildSteps( |
| 648 | 648 | }, |
| 649 | 649 | .Execution => |expected_stdout| no_exec: { |
| 650 | 650 | const run = if (case.target.target.ofmt == .c) run_step: { |
| 651 | const target_info = case.target.toNativeTargetInfo(); | |
| 652 | if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) { | |
| 651 | if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) { | |
| 653 | 652 | // We wouldn't be able to run the compiled C code. |
| 654 | 653 | break :no_exec; |
| 655 | 654 | } |
| ... | ... | @@ -694,8 +693,7 @@ pub fn lowerToBuildSteps( |
| 694 | 693 | continue; // Pass test. |
| 695 | 694 | } |
| 696 | 695 | |
| 697 | const target_info = case.target.toNativeTargetInfo(); | |
| 698 | if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) { | |
| 696 | if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) { | |
| 699 | 697 | // We wouldn't be able to run the compiled C code. |
| 700 | 698 | continue; // Pass test. |
| 701 | 699 | } |
| ... | ... | @@ -1199,6 +1197,8 @@ const builtin = @import("builtin"); |
| 1199 | 1197 | const std = @import("std"); |
| 1200 | 1198 | const assert = std.debug.assert; |
| 1201 | 1199 | const Allocator = std.mem.Allocator; |
| 1200 | const getExternalExecutor = std.zig.system.getExternalExecutor; | |
| 1201 | ||
| 1202 | 1202 | const Compilation = @import("../../src/Compilation.zig"); |
| 1203 | 1203 | const zig_h = @import("../../src/link.zig").File.C.zig_h; |
| 1204 | 1204 | const introspect = @import("../../src/introspect.zig"); |
| ... | ... | @@ -1386,18 +1386,15 @@ pub fn main() !void { |
| 1386 | 1386 | } |
| 1387 | 1387 | |
| 1388 | 1388 | fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget { |
| 1389 | const result = std.zig.system.NativeTargetInfo.detect(query) catch | |
| 1390 | @panic("unable to resolve target query"); | |
| 1391 | ||
| 1392 | 1389 | return .{ |
| 1393 | 1390 | .query = query, |
| 1394 | .target = result.target, | |
| 1395 | .dynamic_linker = result.dynamic_linker, | |
| 1391 | .target = std.zig.system.resolveTargetQuery(query) catch | |
| 1392 | @panic("unable to resolve target query"), | |
| 1396 | 1393 | }; |
| 1397 | 1394 | } |
| 1398 | 1395 | |
| 1399 | 1396 | fn runCases(self: *Cases, zig_exe_path: []const u8) !void { |
| 1400 | const host = try std.zig.system.NativeTargetInfo.detect(.{}); | |
| 1397 | const host = try std.zig.system.resolveTargetQuery(.{}); | |
| 1401 | 1398 | |
| 1402 | 1399 | var progress = std.Progress{}; |
| 1403 | 1400 | const root_node = progress.start("compiler", self.cases.items.len); |
| ... | ... | @@ -1478,7 +1475,7 @@ fn runOneCase( |
| 1478 | 1475 | zig_exe_path: []const u8, |
| 1479 | 1476 | thread_pool: *ThreadPool, |
| 1480 | 1477 | global_cache_directory: Compilation.Directory, |
| 1481 | host: std.zig.system.NativeTargetInfo, | |
| 1478 | host: std.Target, | |
| 1482 | 1479 | ) !void { |
| 1483 | 1480 | const tmp_src_path = "tmp.zig"; |
| 1484 | 1481 | const enable_rosetta = build_options.enable_rosetta; |
| ... | ... | @@ -1488,8 +1485,7 @@ fn runOneCase( |
| 1488 | 1485 | const enable_darling = build_options.enable_darling; |
| 1489 | 1486 | const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; |
| 1490 | 1487 | |
| 1491 | const target_info = try std.zig.system.NativeTargetInfo.detect(case.target); | |
| 1492 | const target = target_info.target; | |
| 1488 | const target = try std.zig.system.resolveTargetQuery(case.target); | |
| 1493 | 1489 | |
| 1494 | 1490 | var arena_allocator = std.heap.ArenaAllocator.init(allocator); |
| 1495 | 1491 | defer arena_allocator.deinit(); |
| ... | ... | @@ -1579,7 +1575,7 @@ fn runOneCase( |
| 1579 | 1575 | .keep_source_files_loaded = true, |
| 1580 | 1576 | .is_native_os = case.target.isNativeOs(), |
| 1581 | 1577 | .is_native_abi = case.target.isNativeAbi(), |
| 1582 | .dynamic_linker = target_info.dynamic_linker.get(), | |
| 1578 | .dynamic_linker = target.dynamic_linker.get(), | |
| 1583 | 1579 | .link_libc = case.link_libc, |
| 1584 | 1580 | .use_llvm = use_llvm, |
| 1585 | 1581 | .self_exe_path = zig_exe_path, |
| ... | ... | @@ -1715,7 +1711,7 @@ fn runOneCase( |
| 1715 | 1711 | .{ &tmp.sub_path, bin_name }, |
| 1716 | 1712 | ); |
| 1717 | 1713 | if (case.target.ofmt != null and case.target.ofmt.? == .c) { |
| 1718 | if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) { | |
| 1714 | if (getExternalExecutor(host, &target, .{ .link_libc = true }) != .native) { | |
| 1719 | 1715 | // We wouldn't be able to run the compiled C code. |
| 1720 | 1716 | continue :update; // Pass test. |
| 1721 | 1717 | } |
| ... | ... | @@ -1734,7 +1730,7 @@ fn runOneCase( |
| 1734 | 1730 | if (zig_lib_directory.path) |p| { |
| 1735 | 1731 | try argv.appendSlice(&.{ "-I", p }); |
| 1736 | 1732 | } |
| 1737 | } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) { | |
| 1733 | } else switch (getExternalExecutor(host, &target, .{ .link_libc = case.link_libc })) { | |
| 1738 | 1734 | .native => { |
| 1739 | 1735 | if (case.backend == .stage2 and case.target.getCpuArch().isArmOrThumb()) { |
| 1740 | 1736 | // https://github.com/ziglang/zig/issues/13623 |