| ... | ... | @@ -7,6 +7,7 @@ const ArrayList = std.ArrayList; |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const process = std.process; |
| 9 | 9 | const Target = std.Target; |
| 10 | const CrossTarget = std.zig.CrossTarget; |
| 10 | 11 | |
| 11 | 12 | const is_windows = Target.current.os.tag == .windows; |
| 12 | 13 | |
| ... | ... | @@ -182,37 +183,87 @@ pub const NativeTargetInfo = struct { |
| 182 | 183 | DeviceBusy, |
| 183 | 184 | }; |
| 184 | 185 | |
| 185 | | /// Detects the native CPU model & features, operating system & version, and C ABI & dynamic linker. |
| 186 | | /// On Linux, this is additionally responsible for detecting the native glibc version when applicable. |
| 186 | /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected |
| 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 | 190 | /// Any resources this function allocates are released before returning, and so there is no |
| 188 | 191 | /// deinitialization method. |
| 189 | 192 | /// TODO Remove the Allocator requirement from this function. |
| 190 | | pub fn detect(allocator: *Allocator) DetectError!NativeTargetInfo { |
| 191 | | const arch = Target.current.cpu.arch; |
| 192 | | const os_tag = Target.current.os.tag; |
| 193 | | |
| 194 | | // TODO Detect native CPU model & features. Until that is implemented we hard code baseline. |
| 195 | | const cpu = Target.Cpu.baseline(arch); |
| 196 | | |
| 197 | | // TODO Detect native operating system version. Until that is implemented we use the default range. |
| 198 | | var os = Target.Os.defaultVersionRange(os_tag); |
| 199 | | switch (Target.current.os.tag) { |
| 200 | | .linux => { |
| 201 | | const uts = std.os.uname(); |
| 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 => {}, |
| 193 | pub fn detect(allocator: *Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo { |
| 194 | const cpu = blk: { |
| 195 | if (cross_target.cpu_arch) |arch| { |
| 196 | if (cross_target.cpu_model) |model| { |
| 197 | var adjusted_model = model.toCpu(arch); |
| 198 | cross_target.updateCpuFeatures(&adjusted_model.features); |
| 199 | break :blk adjusted_model; |
| 200 | } else { |
| 201 | // TODO Detect native CPU model. Until that is implemented we use baseline. |
| 202 | var adjusted_baseline = Target.Cpu.baseline(arch); |
| 203 | cross_target.updateCpuFeatures(&adjusted_baseline.features); |
| 204 | break :blk adjusted_baseline; |
| 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 | 269 | /// First we attempt to use the executable's own binary. If it is dynamically |
| ... | ... | @@ -224,9 +275,14 @@ pub const NativeTargetInfo = struct { |
| 224 | 275 | allocator: *Allocator, |
| 225 | 276 | cpu: Target.Cpu, |
| 226 | 277 | os: Target.Os, |
| 278 | cross_target: CrossTarget, |
| 227 | 279 | ) DetectError!NativeTargetInfo { |
| 228 | | if (!comptime Target.current.hasDynamicLinker()) { |
| 229 | | return defaultAbiAndDynamicLinker(cpu, os); |
| 280 | const native_target_has_ld = comptime Target.current.hasDynamicLinker(); |
| 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 | 287 | // The current target's ABI cannot be relied on for this. For example, we may build the zig |
| 232 | 288 | // compiler for target riscv64-linux-musl and provide a tarball for users to download. |
| ... | ... | @@ -264,6 +320,13 @@ pub const NativeTargetInfo = struct { |
| 264 | 320 | } |
| 265 | 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 | 330 | // Best case scenario: the executable is dynamically linked, and we can iterate |
| 268 | 331 | // over our own shared objects and find a dynamic linker. |
| 269 | 332 | self_exe: { |
| ... | ... | @@ -288,7 +351,9 @@ pub const NativeTargetInfo = struct { |
| 288 | 351 | |
| 289 | 352 | // Look for glibc version. |
| 290 | 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 | 357 | for (lib_paths) |lib_path| { |
| 293 | 358 | if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { |
| 294 | 359 | os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { |
| ... | ... | @@ -306,9 +371,12 @@ pub const NativeTargetInfo = struct { |
| 306 | 371 | .target = .{ |
| 307 | 372 | .cpu = cpu, |
| 308 | 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 | 381 | return result; |
| 314 | 382 | } |
| ... | ... | @@ -317,7 +385,7 @@ pub const NativeTargetInfo = struct { |
| 317 | 385 | // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. |
| 318 | 386 | // Since that path is hard-coded into the shebang line of many portable scripts, it's a |
| 319 | 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 | 389 | error.FileSystem, |
| 322 | 390 | error.SystemResources, |
| 323 | 391 | error.SymLinkLoop, |
| ... | ... | @@ -337,7 +405,7 @@ pub const NativeTargetInfo = struct { |
| 337 | 405 | error.UnexpectedEndOfFile, |
| 338 | 406 | error.NameTooLong, |
| 339 | 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 | 447 | cpu: Target.Cpu, |
| 380 | 448 | os: Target.Os, |
| 381 | 449 | ld_info_list: []const LdInfo, |
| 450 | cross_target: CrossTarget, |
| 382 | 451 | ) !NativeTargetInfo { |
| 383 | 452 | const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) { |
| 384 | 453 | error.NoSpaceLeft => unreachable, |
| ... | ... | @@ -424,10 +493,12 @@ pub const NativeTargetInfo = struct { |
| 424 | 493 | .target = .{ |
| 425 | 494 | .cpu = cpu, |
| 426 | 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 | 500 | var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC |
| 501 | const look_for_ld = cross_target.dynamic_linker.get() == null; |
| 431 | 502 | |
| 432 | 503 | var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; |
| 433 | 504 | if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; |
| ... | ... | @@ -448,7 +519,7 @@ pub const NativeTargetInfo = struct { |
| 448 | 519 | const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i])); |
| 449 | 520 | const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); |
| 450 | 521 | switch (p_type) { |
| 451 | | elf.PT_INTERP => { |
| 522 | elf.PT_INTERP => if (look_for_ld) { |
| 452 | 523 | const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); |
| 453 | 524 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); |
| 454 | 525 | if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; |
| ... | ... | @@ -470,7 +541,9 @@ pub const NativeTargetInfo = struct { |
| 470 | 541 | } |
| 471 | 542 | }, |
| 472 | 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 | 547 | var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); |
| 475 | 548 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); |
| 476 | 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 | 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 | 592 | if (rpath_offset) |rpoff| { |
| 520 | 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 | 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 | 734 | const target: Target = .{ |
| 662 | 735 | .cpu = cpu, |
| 663 | 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 | 739 | return NativeTargetInfo{ |
| 667 | 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 | |