Skip to content

Pass deployment target to cc linker with -m*-version-min= #136333

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

Open
wants to merge 1 commit 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
50 changes: 47 additions & 3 deletions compiler/rustc_codegen_ssa/src/back/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ pub fn pretty_version(version: OSVersion) -> impl Display {
})
}

fn full_version(version: OSVersion) -> impl Display {
let (major, minor, patch) = version;
from_fn(move |f| write!(f, "{major}.{minor}.{patch}"))
}

/// Minimum operating system versions currently supported by `rustc`.
fn os_minimum_deployment_target(os: &str) -> OSVersion {
// When bumping a version in here, remember to update the platform-support docs too.
Expand Down Expand Up @@ -155,7 +160,7 @@ pub(super) fn add_version_to_llvm_target(
let environment = components.next();
assert_eq!(components.next(), None, "too many LLVM triple components");

let (major, minor, patch) = deployment_target;
let version = full_version(deployment_target);

assert!(
!os.contains(|c: char| c.is_ascii_digit()),
Expand All @@ -164,8 +169,47 @@ pub(super) fn add_version_to_llvm_target(

if let Some(env) = environment {
// Insert version into OS, before environment
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
format!("{arch}-{vendor}-{os}{version}-{env}")
} else {
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
format!("{arch}-{vendor}-{os}{version}")
}
}

/// The flag needed to specify the OS and deployment target to a C compiler.
///
/// There are many aliases for these, and `-mtargetos=` is preferred on Clang
/// nowadays, but for compatibility with older Clang and other tooling, we try
/// use the earliest supported name here.
pub(super) fn cc_os_version_min_flag(os: &str, abi: &str, deployment_target: OSVersion) -> String {
let version = full_version(deployment_target);
// NOTE: GCC does not support `-miphoneos-version-min=` etc. (because it
// does not support iOS in general), but we specify them there anyhow in
// case GCC adds support for these in the future, and to force a
// compilation error if GCC compiler is not configured for Darwin:
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html
//
// See also:
// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mmacos-version-min
// https://clang.llvm.org/docs/AttributeReference.html#availability
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html#index-mmacosx-version-min
//
// Note: These are intentionally passed as a single argument, Clang
// doesn't seem to like it otherwise.
match (os, abi) {
("macos", "") => format!("-mmacosx-version-min={version}"),
("ios", "") => format!("-miphoneos-version-min={version}"),
("ios", "sim") => format!("-mios-simulator-version-min={version}"),
// Mac Catalyst came after the introduction of `-mtargetos=`, so no
// other flag exists for that.
("ios", "macabi") => format!("-mtargetos=ios{version}-macabi"),
("tvos", "") => format!("-mappletvos-version-min={version}"),
("tvos", "sim") => format!("-mappletvsimulator-version-min={version}"),
("watchos", "") => format!("-mwatchos-version-min={version}"),
("watchos", "sim") => format!("-mwatchsimulator-version-min={version}"),
// `-mxros-version-min=` does not exist, use `-mtargetos=`.
// https://github.com/llvm/llvm-project/issues/88271
("visionos", "") => format!("-mtargetos=xros{version}"),
("visionos", "sim") => format!("-mtargetos=xros{version}-simulator"),
_ => unreachable!("tried to get cc version flag for non-Apple platform"),
}
}
12 changes: 11 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/apple/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{add_version_to_llvm_target, parse_version};
use super::{add_version_to_llvm_target, cc_os_version_min_flag, parse_version};

#[test]
fn test_add_version_to_llvm_target() {
Expand All @@ -19,3 +19,13 @@ fn test_parse_version() {
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
}

#[test]
fn test_cc_os_version_min_flag() {
assert_eq!(cc_os_version_min_flag("macos", "", (10, 14, 1)), "-mmacosx-version-min=10.14.1");
assert_eq!(cc_os_version_min_flag("ios", "macabi", (13, 1, 0)), "-mtargetos=ios13.1.0-macabi");
assert_eq!(
cc_os_version_min_flag("visionos", "sim", (1, 0, 0)),
"-mtargetos=xros1.0.0-simulator"
);
}
66 changes: 35 additions & 31 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3163,39 +3163,43 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
} else {
// cc == Cc::Yes
//
// We'd _like_ to use `-target` everywhere, since that can uniquely
// We'd _like_ to pass `--target` everywhere, since that can uniquely
// communicate all the required details except for the SDK version
// (which is read by Clang itself from the SDKROOT), but that doesn't
// work on GCC, and since we don't know whether the `cc` compiler is
// Clang, GCC, or something else, we fall back to other options that
// also work on GCC when compiling for macOS.
// (which is read by Clang itself from the SDK root). But that has a
// few problems:
// - It doesn't work with GCC.
// - It doesn't work with Zig CC:
// <https://github.com/ziglang/zig/issues/4911>.
// - It worked poorly with Clang when using config files before:
// <https://github.com/llvm/llvm-project/pull/111387>
// - It worked poorly with certain versions of the `llvm` package in
// Homebrew that expected the older flags:
// <https://github.com/Homebrew/homebrew-core/issues/197532>
//
// Targets other than macOS are ill-supported by GCC (it doesn't even
// support e.g. `-miphoneos-version-min`), so in those cases we can
// fairly safely use `-target`. See also the following, where it is
// made explicit that the recommendation by LLVM developers is to use
// `-target`: <https://github.com/llvm/llvm-project/issues/88271>
if target_os == "macos" {
// `-arch` communicates the architecture.
//
// CC forwards the `-arch` to the linker, so we use the same value
// here intentionally.
cmd.cc_args(&["-arch", ld64_arch]);

// The presence of `-mmacosx-version-min` makes CC default to
// macOS, and it sets the deployment target.
let (major, minor, patch) = apple::deployment_target(sess);
// Intentionally pass this as a single argument, Clang doesn't
// seem to like it otherwise.
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));

// macOS has no environment, so with these two, we've told CC the
// four desired parameters.
//
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
} else {
cmd.cc_args(&["-target", &versioned_llvm_target(sess)]);
}
// So instead, we fall back to to using a combination of `-arch` with
// `-mtargetos=` (and similar).

// cc usually forwards the `-arch` to the linker, so we use the same
// value here intentionally.
//
// NOTE: We avoid `-m32`/`-m64` as this is already encoded by `-arch`.
cmd.cc_args(&["-arch", ld64_arch]);

let version = apple::deployment_target(sess);

// The presence of the `-mmacosx-version-min=`, `-mtargetos=` etc.
// flag both allows Clang to figure out the desired target OS,
// environment / ABI, and the deployment target. See the logic in:
// <https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/clang/lib/Driver/ToolChains/Darwin.cpp#L2235-L2320>
//
// NOTE: These all (correctly) take precedence over the equivalent
// `*_DEPLOYMENT_TARGET` environment variables (which we read
// ourselves to figure out the desired deployment target), so the
// current state of the environment should not have an effect.
cmd.cc_arg(&apple::cc_os_version_min_flag(target_os, target_abi, version));

// With these two flags + the SDK root, we've told cc the five desired
// parameters.
}
}

Expand Down
Loading