Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebuild on changes to the deployment target when compiling Apple targets #129342

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,7 @@ fn escape_dep_filename(filename: &str) -> String {

// Makefile comments only need escaping newlines and `\`.
// The result can be unescaped by anything that can unescape `escape_default` and friends.
fn escape_dep_env(symbol: Symbol) -> String {
let s = symbol.as_str();
fn escape_dep_env(s: &str) -> String {
let mut escaped = String::with_capacity(s.len());
for c in s.chars() {
match c {
Expand Down Expand Up @@ -492,16 +491,22 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P

// Emit special comments with information about accessed environment variables.
let env_depinfo = sess.psess.env_depinfo.borrow();

// We will soon sort, so the initial order does not matter.
#[allow(rustc::potential_query_instability)]
jieyouxu marked this conversation as resolved.
Show resolved Hide resolved
let mut env_depinfo: Vec<_> = env_depinfo
.iter()
.map(|(k, v)| (escape_dep_env(k.as_str()), v.map(|v| escape_dep_env(v.as_str()))))
.chain(tcx.sess.target.is_like_osx.then(|| {
// On Apple targets, we also depend on the deployment target environment variable.
let name = rustc_target::spec::apple_deployment_target_env(&tcx.sess.target.os);
(name.into(), std::env::var(name).ok().map(|var| escape_dep_env(&var)))
}))
.collect();
env_depinfo.sort_unstable();
jieyouxu marked this conversation as resolved.
Show resolved Hide resolved
if !env_depinfo.is_empty() {
// We will soon sort, so the initial order does not matter.
#[allow(rustc::potential_query_instability)]
let mut envs: Vec<_> = env_depinfo
.iter()
.map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env)))
.collect();
envs.sort_unstable();
writeln!(file)?;
for (k, v) in envs {
for (k, v) in env_depinfo {
write!(file, "# env-dep:{k}")?;
if let Some(v) = v {
write!(file, "={v}")?;
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,7 @@ impl Default for Options {
color: ColorConfig::Auto,
logical_env: FxIndexMap::default(),
verbose: false,
apple_deployment_target_env: None,
}
}
}
Expand Down Expand Up @@ -2734,6 +2735,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
color,
logical_env,
verbose,
apple_deployment_target_env: None,
}
}

Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ top_level_options!(
color: ColorConfig [UNTRACKED],

verbose: bool [TRACKED_NO_CRATE_HASH],

/// The raw value of the `*_DEPLOYMENT_TARGET` environment variable
/// for the selected target OS.
///
/// The exact environment variable to use depends on the target that
/// the user has chosen, and we do not want to re-compile if an
/// unrelated deployment target environment variable changed, so we
/// defer the initialization of this to `build_session`.
apple_deployment_target_env: Option<String> [TRACKED],
}
);

Expand Down
9 changes: 8 additions & 1 deletion compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -995,7 +995,7 @@ fn default_emitter(
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
pub fn build_session(
early_dcx: EarlyDiagCtxt,
sopts: config::Options,
mut sopts: config::Options,
io: CompilerIO,
bundle: Option<Lrc<rustc_errors::FluentBundle>>,
registry: rustc_errors::registry::Registry,
Expand Down Expand Up @@ -1099,6 +1099,13 @@ pub fn build_session(

let asm_arch = if target.allow_asm { InlineAsmArch::from_str(&target.arch).ok() } else { None };

// Configure the deployment target for change-tracking, now that target
// details are available.
if target.is_like_osx {
let name = rustc_target::spec::apple_deployment_target_env(&target.os);
sopts.apple_deployment_target_env = std::env::var(name).ok();
}

let sess = Session {
target,
host,
Expand Down
31 changes: 20 additions & 11 deletions compiler/rustc_target/src/spec/base/apple/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ pub fn platform(target: &Target) -> Option<u32> {
})
}

/// Name of the environment variable used to fetch the deployment target on
/// the given OS.
pub fn deployment_target_env(os: &str) -> &'static str {
match os {
"macos" => "MACOSX_DEPLOYMENT_TARGET",
"ios" => "IPHONEOS_DEPLOYMENT_TARGET",
"watchos" => "WATCHOS_DEPLOYMENT_TARGET",
"tvos" => "TVOS_DEPLOYMENT_TARGET",
"visionos" => "XROS_DEPLOYMENT_TARGET",
_ => unreachable!("tried to get deployment target env var for non-Apple platform"),
}
}

/// Hack for calling `deployment_target` outside of this module.
pub fn deployment_target_for_target(target: &Target) -> (u16, u8, u8) {
let arch = if target.llvm_target.starts_with("arm64e") {
Expand Down Expand Up @@ -332,17 +345,13 @@ fn deployment_target(os: &str, arch: Arch, abi: TargetAbi) -> (u16, u8, u8) {
_ => os_min,
};

// The environment variable used to fetch the deployment target.
let env_var = match os {
"macos" => "MACOSX_DEPLOYMENT_TARGET",
"ios" => "IPHONEOS_DEPLOYMENT_TARGET",
"watchos" => "WATCHOS_DEPLOYMENT_TARGET",
"tvos" => "TVOS_DEPLOYMENT_TARGET",
"visionos" => "XROS_DEPLOYMENT_TARGET",
_ => unreachable!("tried to get deployment target env var for non-Apple platform"),
};

if let Ok(deployment_target) = env::var(env_var) {
// NOTE: We access the deployment target environment variable here, which
// makes the variable an **implicit** input which affects compilation.
//
// We make sure to rebuild when the variable changes, both by busting the
// incremental cache, and by telling Cargo that it is a dependency.
// Search for usages of `deployment_target_env` to see how.
if let Ok(deployment_target) = env::var(deployment_target_env(os)) {
match parse_version(&deployment_target) {
// It is common that the deployment target is set too low, e.g. on
// macOS Aarch64 to also target older x86_64, the user may set a
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub mod crt_objects;

mod base;
pub use base::apple::{
deployment_target_env as apple_deployment_target_env,
deployment_target_for_target as current_apple_deployment_target,
platform as current_apple_platform, sdk_version as current_apple_sdk_version,
};
Expand Down
2 changes: 1 addition & 1 deletion src/tools/run-make-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub use env::{env_var, env_var_os, set_current_dir};
pub use run::{cmd, run, run_fail, run_with_args};

/// Helpers for checking target information.
pub use targets::{is_darwin, is_msvc, is_windows, llvm_components_contain, target, uname};
pub use targets::{is_darwin, is_msvc, is_windows, llvm_components_contain, target, uname, apple_os};

/// Helpers for building names of output artifacts that are potentially target-specific.
pub use artifact_names::{
Expand Down
18 changes: 18 additions & 0 deletions src/tools/run-make-support/src/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ pub fn is_darwin() -> bool {
target().contains("darwin")
}

/// Get the target OS on Apple operating systems.
#[must_use]
pub fn apple_os() -> &'static str {
if target().contains("darwin") {
"macos"
} else if target().contains("ios") {
"ios"
} else if target().contains("tvos") {
"tvos"
} else if target().contains("watchos") {
"watchos"
} else if target().contains("visionos") {
"visionos"
} else {
panic!("not an Apple OS")
}
}

/// Check if `component` is within `LLVM_COMPONENTS`
#[must_use]
pub fn llvm_components_contain(component: &str) -> bool {
Expand Down
1 change: 0 additions & 1 deletion src/tools/tidy/src/allowed_run_make_makefiles.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ run-make/incr-add-rust-src-component/Makefile
run-make/issue-84395-lto-embed-bitcode/Makefile
run-make/jobserver-error/Makefile
run-make/libs-through-symlinks/Makefile
run-make/macos-deployment-target/Makefile
run-make/split-debuginfo/Makefile
run-make/symbol-mangling-hashed/Makefile
run-make/translation/Makefile
1 change: 1 addition & 0 deletions tests/run-make/apple-deployment-target/foo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
152 changes: 152 additions & 0 deletions tests/run-make/apple-deployment-target/rmake.rs
madsmtm marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//! Test codegen when setting deployment targets 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/pull/105123.

//@ only-apple

use run_make_support::{apple_os, cmd, run_in_tmpdir, rustc, target};

/// Run vtool to check the `minos` 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 - these have a `version` field, so also check that.
#[track_caller]
fn minos(file: &str, version: &str) {
cmd("vtool")
.arg("-show-build")
.arg(file)
.run()
.assert_stdout_contains_regex(format!("(minos|version) {version}"));
}

fn main() {
// These versions should generally be higher than the default versions
let (env_var, example_version, higher_example_version) = match apple_os() {
"macos" => ("MACOSX_DEPLOYMENT_TARGET", "12.0", "13.0"),
// armv7s-apple-ios and i386-apple-ios only supports iOS 10.0
"ios" if target() == "armv7s-apple-ios" || target() == "i386-apple-ios" => {
("IPHONEOS_DEPLOYMENT_TARGET", "10.0", "10.0")
}
"ios" => ("IPHONEOS_DEPLOYMENT_TARGET", "15.0", "16.0"),
"watchos" => ("WATCHOS_DEPLOYMENT_TARGET", "7.0", "9.0"),
"tvos" => ("TVOS_DEPLOYMENT_TARGET", "14.0", "15.0"),
"visionos" => ("XROS_DEPLOYMENT_TARGET", "1.1", "1.2"),
_ => unreachable!(),
};
let default_version =
rustc().target(target()).env_remove(env_var).print("deployment-target").run().stdout_utf8();
let default_version = default_version.strip_prefix("deployment_target=").unwrap().trim();

// Test that version makes it to the object file.
run_in_tmpdir(|| {
let rustc = || {
let mut rustc = rustc();
rustc.target(target());
rustc.crate_type("lib");
rustc.emit("obj");
rustc.input("foo.rs");
rustc.output("foo.o");
rustc
};

rustc().env(env_var, example_version).run();
minos("foo.o", example_version);

// FIXME(madsmtm): Doesn't work on Mac Catalyst and the simulator.
if !target().contains("macabi") && !target().contains("sim") {
rustc().env_remove(env_var).run();
minos("foo.o", default_version);
}
});

// Test that version makes it to the linker when linking dylibs.
run_in_tmpdir(|| {
// Certain watchOS targets don't support dynamic linking, so we disable the test on those.
if apple_os() == "watchos" {
return;
}

let rustc = || {
let mut rustc = rustc();
rustc.target(target());
rustc.crate_type("dylib");
rustc.input("foo.rs");
rustc.output("libfoo.dylib");
rustc
};

rustc().env(env_var, example_version).run();
minos("libfoo.dylib", example_version);

// FIXME(madsmtm): Deployment target is not currently passed properly to linker
// rustc().env_remove(env_var).run();
// minos("libfoo.dylib", default_version);

// Test with ld64 instead

rustc().arg("-Clinker-flavor=ld").env(env_var, example_version).run();
minos("libfoo.dylib", example_version);

rustc().arg("-Clinker-flavor=ld").env_remove(env_var).run();
minos("libfoo.dylib", default_version);
});

// Test that version makes it to the linker when linking executables.
run_in_tmpdir(|| {
let rustc = || {
let mut rustc = rustc();
rustc.target(target());
rustc.crate_type("bin");
rustc.input("foo.rs");
rustc.output("foo");
rustc
};

// FIXME(madsmtm): Doesn't work on watchOS for some reason?
if !target().contains("watchos") {
rustc().env(env_var, example_version).run();
minos("foo", example_version);

// FIXME(madsmtm): Deployment target is not currently passed properly to linker
// rustc().env_remove(env_var).run();
// minos("foo", default_version);
}

// Test with ld64 instead

rustc().arg("-Clinker-flavor=ld").env(env_var, example_version).run();
minos("foo", example_version);

rustc().arg("-Clinker-flavor=ld").env_remove(env_var).run();
minos("foo", default_version);
});

// Test that changing the deployment target busts the incremental cache.
run_in_tmpdir(|| {
let rustc = || {
let mut rustc = rustc();
rustc.target(target());
rustc.incremental("incremental");
rustc.crate_type("lib");
rustc.emit("obj");
rustc.input("foo.rs");
rustc.output("foo.o");
rustc
};

rustc().env(env_var, example_version).run();
minos("foo.o", example_version);

rustc().env(env_var, higher_example_version).run();
minos("foo.o", higher_example_version);

// FIXME(madsmtm): Doesn't work on Mac Catalyst and the simulator.
if !target().contains("macabi") && !target().contains("sim") {
rustc().env_remove(env_var).run();
minos("foo.o", default_version);
}
});
}
21 changes: 0 additions & 21 deletions tests/run-make/macos-deployment-target/Makefile

This file was deleted.

This file was deleted.

Loading