Skip to content

Commit 8b90d49

Browse files
committed
Pass deployment target to cc linker with -m*-version-min=
Clang supports many ways of passing the deployment target, and prefers that you use the `-target` flag for doing so. This works poorly work with configuration files and Zig CC though, so we use the older `-mmacosx-version-min=`, `-miphoneos-version-min=`, `-mtargetos=` etc. flags to pass the deployment target instead.
1 parent 6c1d960 commit 8b90d49

File tree

3 files changed

+93
-35
lines changed

3 files changed

+93
-35
lines changed

compiler/rustc_codegen_ssa/src/back/apple.rs

+47-3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ pub fn pretty_version(version: OSVersion) -> impl Display {
5656
})
5757
}
5858

59+
fn full_version(version: OSVersion) -> impl Display {
60+
let (major, minor, patch) = version;
61+
from_fn(move |f| write!(f, "{major}.{minor}.{patch}"))
62+
}
63+
5964
/// Minimum operating system versions currently supported by `rustc`.
6065
fn os_minimum_deployment_target(os: &str) -> OSVersion {
6166
// When bumping a version in here, remember to update the platform-support docs too.
@@ -155,7 +160,7 @@ pub(super) fn add_version_to_llvm_target(
155160
let environment = components.next();
156161
assert_eq!(components.next(), None, "too many LLVM triple components");
157162

158-
let (major, minor, patch) = deployment_target;
163+
let version = full_version(deployment_target);
159164

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

165170
if let Some(env) = environment {
166171
// Insert version into OS, before environment
167-
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
172+
format!("{arch}-{vendor}-{os}{version}-{env}")
168173
} else {
169-
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
174+
format!("{arch}-{vendor}-{os}{version}")
175+
}
176+
}
177+
178+
/// The flag needed to specify the OS and deployment target to a C compiler.
179+
///
180+
/// There are many aliases for these, and `-mtargetos=` is preferred on Clang
181+
/// nowadays, but for compatibility with older Clang and other tooling, we try
182+
/// use the earliest supported name here.
183+
pub(super) fn cc_os_version_min_flag(os: &str, abi: &str, deployment_target: OSVersion) -> String {
184+
let version = full_version(deployment_target);
185+
// NOTE: GCC does not support `-miphoneos-version-min=` etc. (because it
186+
// does not support iOS in general), but we specify them there anyhow in
187+
// case GCC adds support for these in the future, and to force a
188+
// compilation error if GCC compiler is not configured for Darwin:
189+
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html
190+
//
191+
// See also:
192+
// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mmacos-version-min
193+
// https://clang.llvm.org/docs/AttributeReference.html#availability
194+
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html#index-mmacosx-version-min
195+
//
196+
// Note: These are intentionally passed as a single argument, Clang
197+
// doesn't seem to like it otherwise.
198+
match (os, abi) {
199+
("macos", "") => format!("-mmacosx-version-min={version}"),
200+
("ios", "") => format!("-miphoneos-version-min={version}"),
201+
("ios", "sim") => format!("-mios-simulator-version-min={version}"),
202+
// Mac Catalyst came after the introduction of `-mtargetos=`, so no
203+
// other flag exists for that.
204+
("ios", "macabi") => format!("-mtargetos=ios{version}-macabi"),
205+
("tvos", "") => format!("-mappletvos-version-min={version}"),
206+
("tvos", "sim") => format!("-mappletvsimulator-version-min={version}"),
207+
("watchos", "") => format!("-mwatchos-version-min={version}"),
208+
("watchos", "sim") => format!("-mwatchsimulator-version-min={version}"),
209+
// `-mxros-version-min=` does not exist, use `-mtargetos=`.
210+
// https://github.com/llvm/llvm-project/issues/88271
211+
("visionos", "") => format!("-mtargetos=xros{version}"),
212+
("visionos", "sim") => format!("-mtargetos=xros{version}-simulator"),
213+
_ => unreachable!("tried to get cc version flag for non-Apple platform"),
170214
}
171215
}

compiler/rustc_codegen_ssa/src/back/apple/tests.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{add_version_to_llvm_target, parse_version};
1+
use super::{add_version_to_llvm_target, cc_os_version_min_flag, parse_version};
22

33
#[test]
44
fn test_add_version_to_llvm_target() {
@@ -19,3 +19,13 @@ fn test_parse_version() {
1919
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
2020
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
2121
}
22+
23+
#[test]
24+
fn test_cc_os_version_min_flag() {
25+
assert_eq!(cc_os_version_min_flag("macos", "", (10, 14, 1)), "-mmacosx-version-min=10.14.1");
26+
assert_eq!(cc_os_version_min_flag("ios", "macabi", (13, 1, 0)), "-mtargetos=ios13.1.0-macabi");
27+
assert_eq!(
28+
cc_os_version_min_flag("visionos", "sim", (1, 0, 0)),
29+
"-mtargetos=xros1.0.0-simulator"
30+
);
31+
}

compiler/rustc_codegen_ssa/src/back/link.rs

+35-31
Original file line numberDiff line numberDiff line change
@@ -3163,39 +3163,43 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
31633163
} else {
31643164
// cc == Cc::Yes
31653165
//
3166-
// We'd _like_ to use `-target` everywhere, since that can uniquely
3166+
// We'd _like_ to pass `--target` everywhere, since that can uniquely
31673167
// communicate all the required details except for the SDK version
3168-
// (which is read by Clang itself from the SDKROOT), but that doesn't
3169-
// work on GCC, and since we don't know whether the `cc` compiler is
3170-
// Clang, GCC, or something else, we fall back to other options that
3171-
// also work on GCC when compiling for macOS.
3168+
// (which is read by Clang itself from the SDK root). But that has a
3169+
// few problems:
3170+
// - It doesn't work with GCC.
3171+
// - It doesn't work with Zig CC:
3172+
// <https://github.com/ziglang/zig/issues/4911>.
3173+
// - It worked poorly with Clang when using config files before:
3174+
// <https://github.com/llvm/llvm-project/pull/111387>
3175+
// - It worked poorly with certain versions of the `llvm` package in
3176+
// Homebrew that expected the older flags:
3177+
// <https://github.com/Homebrew/homebrew-core/issues/197532>
31723178
//
3173-
// Targets other than macOS are ill-supported by GCC (it doesn't even
3174-
// support e.g. `-miphoneos-version-min`), so in those cases we can
3175-
// fairly safely use `-target`. See also the following, where it is
3176-
// made explicit that the recommendation by LLVM developers is to use
3177-
// `-target`: <https://github.com/llvm/llvm-project/issues/88271>
3178-
if target_os == "macos" {
3179-
// `-arch` communicates the architecture.
3180-
//
3181-
// CC forwards the `-arch` to the linker, so we use the same value
3182-
// here intentionally.
3183-
cmd.cc_args(&["-arch", ld64_arch]);
3184-
3185-
// The presence of `-mmacosx-version-min` makes CC default to
3186-
// macOS, and it sets the deployment target.
3187-
let (major, minor, patch) = apple::deployment_target(sess);
3188-
// Intentionally pass this as a single argument, Clang doesn't
3189-
// seem to like it otherwise.
3190-
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
3191-
3192-
// macOS has no environment, so with these two, we've told CC the
3193-
// four desired parameters.
3194-
//
3195-
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
3196-
} else {
3197-
cmd.cc_args(&["-target", &versioned_llvm_target(sess)]);
3198-
}
3179+
// So instead, we fall back to to using a combination of `-arch` with
3180+
// `-mtargetos=` (and similar).
3181+
3182+
// cc usually forwards the `-arch` to the linker, so we use the same
3183+
// value here intentionally.
3184+
//
3185+
// NOTE: We avoid `-m32`/`-m64` as this is already encoded by `-arch`.
3186+
cmd.cc_args(&["-arch", ld64_arch]);
3187+
3188+
let version = apple::deployment_target(sess);
3189+
3190+
// The presence of the `-mmacosx-version-min=`, `-mtargetos=` etc.
3191+
// flag both allows Clang to figure out the desired target OS,
3192+
// environment / ABI, and the deployment target. See the logic in:
3193+
// <https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/clang/lib/Driver/ToolChains/Darwin.cpp#L2235-L2320>
3194+
//
3195+
// NOTE: These all (correctly) take precedence over the equivalent
3196+
// `*_DEPLOYMENT_TARGET` environment variables (which we read
3197+
// ourselves to figure out the desired deployment target), so the
3198+
// current state of the environment should not have an effect.
3199+
cmd.cc_arg(&apple::cc_os_version_min_flag(target_os, target_abi, version));
3200+
3201+
// With these two flags + the SDK root, we've told cc the five desired
3202+
// parameters.
31993203
}
32003204
}
32013205

0 commit comments

Comments
 (0)