| ... | @@ -7,6 +7,7 @@ const ArrayList = std.ArrayList; | ... | @@ -7,6 +7,7 @@ const ArrayList = std.ArrayList; |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | const process = std.process; | 8 | const process = std.process; |
| 9 | const Target = std.Target; | 9 | const Target = std.Target; |
| | 10 | const CrossTarget = std.zig.CrossTarget; |
| 10 | | 11 | |
| 11 | const is_windows = Target.current.os.tag == .windows; | 12 | const is_windows = Target.current.os.tag == .windows; |
| 12 | | 13 | |
| ... | @@ -182,37 +183,87 @@ pub const NativeTargetInfo = struct { | ... | @@ -182,37 +183,87 @@ pub const NativeTargetInfo = struct { |
| 182 | DeviceBusy, | 183 | DeviceBusy, |
| 183 | }; | 184 | }; |
| 184 | | 185 | |
| 185 | /// Detects the native CPU model & features, operating system & version, and C ABI & dynamic linker. | 186 | /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected |
| 186 | /// On Linux, this is additionally responsible for detecting the native glibc version when applicable. | 187 | /// natively, which should be standard or default, and which are provided explicitly, this function |
| | 188 | /// resolves the native components by detecting the native system, and then resolves standard/default parts |
| | 189 | /// relative to that. |
| 187 | /// Any resources this function allocates are released before returning, and so there is no | 190 | /// Any resources this function allocates are released before returning, and so there is no |
| 188 | /// deinitialization method. | 191 | /// deinitialization method. |
| 189 | /// TODO Remove the Allocator requirement from this function. | 192 | /// TODO Remove the Allocator requirement from this function. |
| 190 | pub fn detect(allocator: *Allocator) DetectError!NativeTargetInfo { | 193 | pub fn detect(allocator: *Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo { |
| 191 | const arch = Target.current.cpu.arch; | 194 | const cpu = blk: { |
| 192 | const os_tag = Target.current.os.tag; | 195 | if (cross_target.cpu_arch) |arch| { |
| 193 | | 196 | if (cross_target.cpu_model) |model| { |
| 194 | // TODO Detect native CPU model & features. Until that is implemented we hard code baseline. | 197 | var adjusted_model = model.toCpu(arch); |
| 195 | const cpu = Target.Cpu.baseline(arch); | 198 | cross_target.updateCpuFeatures(&adjusted_model.features); |
| 196 | | 199 | break :blk adjusted_model; |
| 197 | // TODO Detect native operating system version. Until that is implemented we use the default range. | 200 | } else { |
| 198 | var os = Target.Os.defaultVersionRange(os_tag); | 201 | // TODO Detect native CPU model. Until that is implemented we use baseline. |
| 199 | switch (Target.current.os.tag) { | 202 | var adjusted_baseline = Target.Cpu.baseline(arch); |
| 200 | .linux => { | 203 | cross_target.updateCpuFeatures(&adjusted_baseline.features); |
| 201 | const uts = std.os.uname(); | 204 | break :blk adjusted_baseline; |
| 202 | const release = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.release)); | | |
| 203 | if (std.builtin.Version.parse(release)) |ver| { | | |
| 204 | os.version_range.linux.range.min = ver; | | |
| 205 | os.version_range.linux.range.max = ver; | | |
| 206 | } else |err| switch (err) { | | |
| 207 | error.Overflow => {}, | | |
| 208 | error.InvalidCharacter => {}, | | |
| 209 | error.InvalidVersion => {}, | | |
| 210 | } | 205 | } |
| | 206 | } else { |
| | 207 | assert(cross_target.cpu_model == null); |
| | 208 | // TODO Detect native CPU model & features. Until that is implemented we use baseline. |
| | 209 | var adjusted_baseline = Target.Cpu.baseline(Target.current.cpu.arch); |
| | 210 | cross_target.updateCpuFeatures(&adjusted_baseline.features); |
| | 211 | break :blk adjusted_baseline; |
| | 212 | } |
| | 213 | }; |
| | 214 | |
| | 215 | var os = Target.Os.defaultVersionRange(cross_target.getOsTag()); |
| | 216 | if (cross_target.os_tag == null) { |
| | 217 | switch (Target.current.os.tag) { |
| | 218 | .linux => { |
| | 219 | const uts = std.os.uname(); |
| | 220 | const release = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.release)); |
| | 221 | if (std.builtin.Version.parse(release)) |ver| { |
| | 222 | os.version_range.linux.range.min = ver; |
| | 223 | os.version_range.linux.range.max = ver; |
| | 224 | } else |err| switch (err) { |
| | 225 | error.Overflow => {}, |
| | 226 | error.InvalidCharacter => {}, |
| | 227 | error.InvalidVersion => {}, |
| | 228 | } |
| | 229 | }, |
| | 230 | .windows => { |
| | 231 | // TODO Detect native operating system version. |
| | 232 | }, |
| | 233 | .macosx => { |
| | 234 | // TODO Detect native operating system version. |
| | 235 | }, |
| | 236 | .freebsd => { |
| | 237 | // TODO Detect native operating system version. |
| | 238 | }, |
| | 239 | else => {}, |
| | 240 | } |
| | 241 | } |
| | 242 | |
| | 243 | if (cross_target.os_version_min) |min| switch (min) { |
| | 244 | .none => {}, |
| | 245 | .semver => |semver| switch (cross_target.getOsTag()) { |
| | 246 | .linux => os.version_range.linux.range.min = semver, |
| | 247 | else => os.version_range.semver.min = semver, |
| | 248 | }, |
| | 249 | .windows => |win_ver| os.version_range.windows.min = win_ver, |
| | 250 | }; |
| | 251 | |
| | 252 | if (cross_target.os_version_max) |max| switch (max) { |
| | 253 | .none => {}, |
| | 254 | .semver => |semver| switch (cross_target.getOsTag()) { |
| | 255 | .linux => os.version_range.linux.range.max = semver, |
| | 256 | else => os.version_range.semver.max = semver, |
| 211 | }, | 257 | }, |
| 212 | else => {}, | 258 | .windows => |win_ver| os.version_range.windows.max = win_ver, |
| | 259 | }; |
| | 260 | |
| | 261 | if (cross_target.glibc_version) |glibc| { |
| | 262 | assert(cross_target.isGnuLibC()); |
| | 263 | os.version_range.linux.glibc = glibc; |
| 213 | } | 264 | } |
| 214 | | 265 | |
| 215 | return detectAbiAndDynamicLinker(allocator, cpu, os); | 266 | return detectAbiAndDynamicLinker(allocator, cpu, os, cross_target); |
| 216 | } | 267 | } |
| 217 | | 268 | |
| 218 | /// First we attempt to use the executable's own binary. If it is dynamically | 269 | /// First we attempt to use the executable's own binary. If it is dynamically |
| ... | @@ -224,9 +275,14 @@ pub const NativeTargetInfo = struct { | ... | @@ -224,9 +275,14 @@ pub const NativeTargetInfo = struct { |
| 224 | allocator: *Allocator, | 275 | allocator: *Allocator, |
| 225 | cpu: Target.Cpu, | 276 | cpu: Target.Cpu, |
| 226 | os: Target.Os, | 277 | os: Target.Os, |
| | 278 | cross_target: CrossTarget, |
| 227 | ) DetectError!NativeTargetInfo { | 279 | ) DetectError!NativeTargetInfo { |
| 228 | if (!comptime Target.current.hasDynamicLinker()) { | 280 | const native_target_has_ld = comptime Target.current.hasDynamicLinker(); |
| 229 | return defaultAbiAndDynamicLinker(cpu, os); | 281 | const is_linux = Target.current.os.tag == .linux; |
| | 282 | const have_all_info = cross_target.dynamic_linker.get() != null and |
| | 283 | cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu()); |
| | 284 | if (!native_target_has_ld or have_all_info) { |
| | 285 | return defaultAbiAndDynamicLinker(cpu, os, cross_target); |
| 230 | } | 286 | } |
| 231 | // The current target's ABI cannot be relied on for this. For example, we may build the zig | 287 | // The current target's ABI cannot be relied on for this. For example, we may build the zig |
| 232 | // compiler for target riscv64-linux-musl and provide a tarball for users to download. | 288 | // compiler for target riscv64-linux-musl and provide a tarball for users to download. |
| ... | @@ -264,6 +320,13 @@ pub const NativeTargetInfo = struct { | ... | @@ -264,6 +320,13 @@ pub const NativeTargetInfo = struct { |
| 264 | } | 320 | } |
| 265 | const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; | 321 | const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; |
| 266 | | 322 | |
| | 323 | if (cross_target.dynamic_linker.get()) |explicit_ld| { |
| | 324 | const explicit_ld_basename = fs.path.basename(explicit_ld); |
| | 325 | for (ld_info_list) |ld_info| { |
| | 326 | const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); |
| | 327 | } |
| | 328 | } |
| | 329 | |
| 267 | // Best case scenario: the executable is dynamically linked, and we can iterate | 330 | // Best case scenario: the executable is dynamically linked, and we can iterate |
| 268 | // over our own shared objects and find a dynamic linker. | 331 | // over our own shared objects and find a dynamic linker. |
| 269 | self_exe: { | 332 | self_exe: { |
| ... | @@ -288,7 +351,9 @@ pub const NativeTargetInfo = struct { | ... | @@ -288,7 +351,9 @@ pub const NativeTargetInfo = struct { |
| 288 | | 351 | |
| 289 | // Look for glibc version. | 352 | // Look for glibc version. |
| 290 | var os_adjusted = os; | 353 | var os_adjusted = os; |
| 291 | if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu()) { | 354 | if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu() and |
| | 355 | cross_target.glibc_version == null) |
| | 356 | { |
| 292 | for (lib_paths) |lib_path| { | 357 | for (lib_paths) |lib_path| { |
| 293 | if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { | 358 | if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { |
| 294 | os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { | 359 | os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { |
| ... | @@ -306,9 +371,12 @@ pub const NativeTargetInfo = struct { | ... | @@ -306,9 +371,12 @@ pub const NativeTargetInfo = struct { |
| 306 | .target = .{ | 371 | .target = .{ |
| 307 | .cpu = cpu, | 372 | .cpu = cpu, |
| 308 | .os = os_adjusted, | 373 | .os = os_adjusted, |
| 309 | .abi = found_ld_info.abi, | 374 | .abi = cross_target.abi orelse found_ld_info.abi, |
| 310 | }, | 375 | }, |
| 311 | .dynamic_linker = DynamicLinker.init(found_ld_path), | 376 | .dynamic_linker = if (cross_target.dynamic_linker.get() == null) |
| | 377 | DynamicLinker.init(found_ld_path) |
| | 378 | else |
| | 379 | cross_target.dynamic_linker, |
| 312 | }; | 380 | }; |
| 313 | return result; | 381 | return result; |
| 314 | } | 382 | } |
| ... | @@ -317,7 +385,7 @@ pub const NativeTargetInfo = struct { | ... | @@ -317,7 +385,7 @@ pub const NativeTargetInfo = struct { |
| 317 | // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. | 385 | // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. |
| 318 | // Since that path is hard-coded into the shebang line of many portable scripts, it's a | 386 | // Since that path is hard-coded into the shebang line of many portable scripts, it's a |
| 319 | // reasonably reliable path to check for. | 387 | // reasonably reliable path to check for. |
| 320 | return abiAndDynamicLinkerFromUsrBinEnv(cpu, os, ld_info_list) catch |err| switch (err) { | 388 | return abiAndDynamicLinkerFromUsrBinEnv(cpu, os, ld_info_list, cross_target) catch |err| switch (err) { |
| 321 | error.FileSystem, | 389 | error.FileSystem, |
| 322 | error.SystemResources, | 390 | error.SystemResources, |
| 323 | error.SymLinkLoop, | 391 | error.SymLinkLoop, |
| ... | @@ -337,7 +405,7 @@ pub const NativeTargetInfo = struct { | ... | @@ -337,7 +405,7 @@ pub const NativeTargetInfo = struct { |
| 337 | error.UnexpectedEndOfFile, | 405 | error.UnexpectedEndOfFile, |
| 338 | error.NameTooLong, | 406 | error.NameTooLong, |
| 339 | // Finally, we fall back on the standard path. | 407 | // Finally, we fall back on the standard path. |
| 340 | => defaultAbiAndDynamicLinker(cpu, os), | 408 | => defaultAbiAndDynamicLinker(cpu, os, cross_target), |
| 341 | }; | 409 | }; |
| 342 | } | 410 | } |
| 343 | | 411 | |
| ... | @@ -379,6 +447,7 @@ pub const NativeTargetInfo = struct { | ... | @@ -379,6 +447,7 @@ pub const NativeTargetInfo = struct { |
| 379 | cpu: Target.Cpu, | 447 | cpu: Target.Cpu, |
| 380 | os: Target.Os, | 448 | os: Target.Os, |
| 381 | ld_info_list: []const LdInfo, | 449 | ld_info_list: []const LdInfo, |
| | 450 | cross_target: CrossTarget, |
| 382 | ) !NativeTargetInfo { | 451 | ) !NativeTargetInfo { |
| 383 | const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) { | 452 | const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) { |
| 384 | error.NoSpaceLeft => unreachable, | 453 | error.NoSpaceLeft => unreachable, |
| ... | @@ -424,10 +493,12 @@ pub const NativeTargetInfo = struct { | ... | @@ -424,10 +493,12 @@ pub const NativeTargetInfo = struct { |
| 424 | .target = .{ | 493 | .target = .{ |
| 425 | .cpu = cpu, | 494 | .cpu = cpu, |
| 426 | .os = os, | 495 | .os = os, |
| 427 | .abi = Target.Abi.default(cpu.arch, os), | 496 | .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), |
| 428 | }, | 497 | }, |
| | 498 | .dynamic_linker = cross_target.dynamic_linker, |
| 429 | }; | 499 | }; |
| 430 | var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC | 500 | var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC |
| | 501 | const look_for_ld = cross_target.dynamic_linker.get() == null; |
| 431 | | 502 | |
| 432 | var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; | 503 | var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; |
| 433 | if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; | 504 | if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; |
| ... | @@ -448,7 +519,7 @@ pub const NativeTargetInfo = struct { | ... | @@ -448,7 +519,7 @@ pub const NativeTargetInfo = struct { |
| 448 | const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i])); | 519 | const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i])); |
| 449 | const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); | 520 | const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); |
| 450 | switch (p_type) { | 521 | switch (p_type) { |
| 451 | elf.PT_INTERP => { | 522 | elf.PT_INTERP => if (look_for_ld) { |
| 452 | const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); | 523 | const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); |
| 453 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); | 524 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); |
| 454 | if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; | 525 | if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; |
| ... | @@ -470,7 +541,9 @@ pub const NativeTargetInfo = struct { | ... | @@ -470,7 +541,9 @@ pub const NativeTargetInfo = struct { |
| 470 | } | 541 | } |
| 471 | }, | 542 | }, |
| 472 | // We only need this for detecting glibc version. | 543 | // We only need this for detecting glibc version. |
| 473 | elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC()) { | 544 | elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC() and |
| | 545 | cross_target.glibc_version == null) |
| | 546 | { |
| 474 | var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); | 547 | var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); |
| 475 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); | 548 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); |
| 476 | const dyn_size: u64 = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); | 549 | const dyn_size: u64 = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); |
| ... | @@ -515,7 +588,7 @@ pub const NativeTargetInfo = struct { | ... | @@ -515,7 +588,7 @@ pub const NativeTargetInfo = struct { |
| 515 | } | 588 | } |
| 516 | } | 589 | } |
| 517 | | 590 | |
| 518 | if (Target.current.os.tag == .linux and result.target.isGnuLibC()) { | 591 | if (Target.current.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) { |
| 519 | if (rpath_offset) |rpoff| { | 592 | if (rpath_offset) |rpoff| { |
| 520 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); | 593 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 521 | | 594 | |
| ... | @@ -657,15 +730,18 @@ pub const NativeTargetInfo = struct { | ... | @@ -657,15 +730,18 @@ pub const NativeTargetInfo = struct { |
| 657 | return i; | 730 | return i; |
| 658 | } | 731 | } |
| 659 | | 732 | |
| 660 | fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo { | 733 | fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo { |
| 661 | const target: Target = .{ | 734 | const target: Target = .{ |
| 662 | .cpu = cpu, | 735 | .cpu = cpu, |
| 663 | .os = os, | 736 | .os = os, |
| 664 | .abi = Target.Abi.default(cpu.arch, os), | 737 | .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), |
| 665 | }; | 738 | }; |
| 666 | return NativeTargetInfo{ | 739 | return NativeTargetInfo{ |
| 667 | .target = target, | 740 | .target = target, |
| 668 | .dynamic_linker = target.standardDynamicLinkerPath(), | 741 | .dynamic_linker = if (cross_target.dynamic_linker.get() == null) |
| | 742 | target.standardDynamicLinkerPath() |
| | 743 | else |
| | 744 | cross_target.dynamic_linker, |
| 669 | }; | 745 | }; |
| 670 | } | 746 | } |
| 671 | | 747 | |