diff --git a/tests/run-make/apple-sdk-version/foo.rs b/tests/run-make/apple-sdk-version/foo.rs new file mode 100644 index 00000000000..f328e4d9d04 --- /dev/null +++ b/tests/run-make/apple-sdk-version/foo.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/apple-sdk-version/rmake.rs b/tests/run-make/apple-sdk-version/rmake.rs new file mode 100644 index 00000000000..a16c84ddc59 --- /dev/null +++ b/tests/run-make/apple-sdk-version/rmake.rs @@ -0,0 +1,86 @@ +//! Test codegen when setting SDK version on Apple platforms. +//! +//! This is important since its a compatibility hazard. The linker will +//! generate load commands differently based on what minimum OS it can assume. +//! +//! See https://github.com/rust-lang/rust/issues/129432. + +//@ only-apple + +use run_make_support::{apple_os, cmd, run_in_tmpdir, rustc, target}; + +/// Run vtool to check the `sdk` field in LC_BUILD_VERSION. +/// +/// On lower deployment targets, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS and similar +/// are used instead of LC_BUILD_VERSION, but both name the relevant variable `sdk`. +#[track_caller] +fn has_sdk_version(file: &str, version: &str) { + cmd("vtool") + .arg("-show-build") + .arg(file) + .run() + .assert_stdout_contains(format!("sdk {version}")); +} + +fn main() { + // Fetch rustc's inferred deployment target. + let current_deployment_target = + rustc().target(target()).print("deployment-target").run().stdout_utf8(); + let current_deployment_target = + current_deployment_target.strip_prefix("deployment_target=").unwrap().trim(); + + // Fetch current SDK version via. xcrun. + // + // Assumes a standard Xcode distribution, where e.g. the macOS SDK's Mac Catalyst + // and the iPhone Simulator version is the same as for the iPhone SDK. + let sdk_name = match apple_os() { + "macos" => "macosx", + "ios" => "iphoneos", + "watchos" => "watchos", + "tvos" => "appletvos", + "visionos" => "xros", + _ => unreachable!(), + }; + let current_sdk_version = + cmd("xcrun").arg("--show-sdk-version").arg("--sdk").arg(sdk_name).run().stdout_utf8(); + let current_sdk_version = current_sdk_version.trim(); + + // Check the SDK version in the object file produced by the codegen backend. + rustc().target(target()).crate_type("lib").emit("obj").input("foo.rs").output("foo.o").run(); + // Set to 0, which means not set or "n/a". + has_sdk_version("foo.o", "n/a"); + + // Test that version makes it to the linker. + for (crate_type, file_ext) in [("bin", ""), ("dylib", ".dylib")] { + // Non-simulator watchOS targets don't support dynamic linking, + // for simplicity we disable the test on all watchOS targets. + if crate_type == "dylib" && apple_os() == "watchos" { + continue; + } + + // Test with clang + let file_name = format!("foo_cc{file_ext}"); + rustc() + .target(target()) + .crate_type("bin") + .arg("-Clinker-flavor=gcc") + .input("foo.rs") + .output(&file_name) + .run(); + has_sdk_version(&file_name, current_sdk_version); + + // Test with ld64 + let file_name = format!("foo_ld{file_ext}"); + rustc() + .target(target()) + .crate_type("bin") + .arg("-Clinker-flavor=ld") + .input("foo.rs") + .output(&file_name) + .run(); + // FIXME(madsmtm): This uses the current deployment target + // instead of the current SDK version like Clang does. + // https://github.com/rust-lang/rust/issues/129432 + has_sdk_version(&file_name, current_deployment_target); + } +}