@@ -40,7 +40,7 @@ use rustc_target::spec::crt_objects::CrtObjects;
40
40
use rustc_target::spec::{
41
41
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
42
42
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
43
- SplitDebuginfo,
43
+ SplitDebuginfo, current_apple_deployment_target,
44
44
};
45
45
use tempfile::Builder as TempFileBuilder;
46
46
use tracing::{debug, info, warn};
@@ -2405,6 +2405,8 @@ fn add_order_independent_options(
2405
2405
// Take care of the flavors and CLI options requesting the `lld` linker.
2406
2406
add_lld_args(cmd, sess, flavor, self_contained_components);
2407
2407
2408
+ add_apple_link_args(cmd, sess, flavor);
2409
+
2408
2410
let apple_sdk_root = add_apple_sdk(cmd, sess, flavor);
2409
2411
2410
2412
add_link_script(cmd, sess, tmpdir, crate_type);
@@ -2957,6 +2959,135 @@ pub(crate) fn are_upstream_rust_objects_already_included(sess: &Session) -> bool
2957
2959
}
2958
2960
}
2959
2961
2962
+ /// We need to communicate four things to the linker on Apple/Darwin targets:
2963
+ /// - The architecture.
2964
+ /// - The operating system (and that it's an Apple platform).
2965
+ /// - The deployment target.
2966
+ /// - The environment / ABI.
2967
+ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
2968
+ if !sess.target.is_like_osx {
2969
+ return;
2970
+ }
2971
+ let LinkerFlavor::Darwin(cc, _) = flavor else {
2972
+ return;
2973
+ };
2974
+
2975
+ // `sess.target.arch` (`target_arch`) is not detailed enough.
2976
+ let llvm_arch = sess.target.llvm_target.split_once('-').expect("LLVM target must have arch").0;
2977
+ let target_os = &*sess.target.os;
2978
+ let target_abi = &*sess.target.abi;
2979
+
2980
+ // The architecture name to forward to the linker.
2981
+ //
2982
+ // Supported architecture names can be found in the source:
2983
+ // https://github.com/apple-oss-distributions/ld64/blob/ld64-951.9/src/abstraction/MachOFileAbstraction.hpp#L578-L648
2984
+ //
2985
+ // Intentially verbose to ensure that the list always matches correctly
2986
+ // with the list in the source above.
2987
+ let ld64_arch = match llvm_arch {
2988
+ "armv7k" => "armv7k",
2989
+ "armv7s" => "armv7s",
2990
+ "arm64" => "arm64",
2991
+ "arm64e" => "arm64e",
2992
+ "arm64_32" => "arm64_32",
2993
+ // ld64 doesn't understand i686, so fall back to i386 instead.
2994
+ //
2995
+ // Same story when linking with cc, since that ends up invoking ld64.
2996
+ "i386" | "i686" => "i386",
2997
+ "x86_64" => "x86_64",
2998
+ "x86_64h" => "x86_64h",
2999
+ _ => bug!("unsupported architecture in Apple target: {}", sess.target.llvm_target),
3000
+ };
3001
+
3002
+ if cc == Cc::No {
3003
+ // From the man page for ld64 (`man ld`):
3004
+ // > The linker accepts universal (multiple-architecture) input files,
3005
+ // > but always creates a "thin" (single-architecture), standard
3006
+ // > Mach-O output file. The architecture for the output file is
3007
+ // > specified using the -arch option.
3008
+ //
3009
+ // The linker has heuristics to determine the desired architecture,
3010
+ // but to be safe, and to avoid a warning, we set the architecture
3011
+ // explicitly.
3012
+ cmd.link_args(&["-arch", ld64_arch]);
3013
+
3014
+ // Man page says that ld64 supports the following platform names:
3015
+ // > - macos
3016
+ // > - ios
3017
+ // > - tvos
3018
+ // > - watchos
3019
+ // > - bridgeos
3020
+ // > - visionos
3021
+ // > - xros
3022
+ // > - mac-catalyst
3023
+ // > - ios-simulator
3024
+ // > - tvos-simulator
3025
+ // > - watchos-simulator
3026
+ // > - visionos-simulator
3027
+ // > - xros-simulator
3028
+ // > - driverkit
3029
+ let platform_name = match (target_os, target_abi) {
3030
+ (os, "") => os,
3031
+ ("ios", "macabi") => "mac-catalyst",
3032
+ ("ios", "sim") => "ios-simulator",
3033
+ ("tvos", "sim") => "tvos-simulator",
3034
+ ("watchos", "sim") => "watchos-simulator",
3035
+ ("visionos", "sim") => "visionos-simulator",
3036
+ _ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
3037
+ };
3038
+
3039
+ let (major, minor, patch) = current_apple_deployment_target(&sess.target);
3040
+ let min_version = format!("{major}.{minor}.{patch}");
3041
+
3042
+ // Lie about the SDK version, we don't know it here
3043
+ let sdk_version = &*min_version;
3044
+
3045
+ // From the man page for ld64 (`man ld`):
3046
+ // > This is set to indicate the platform, oldest supported version of
3047
+ // > that platform that output is to be used on, and the SDK that the
3048
+ // > output was built against.
3049
+ //
3050
+ // Like with `-arch`, the linker can figure out the platform versions
3051
+ // itself from the binaries being linked, but to be safe, we specify
3052
+ // the desired versions here explicitly.
3053
+ cmd.link_args(&["-platform_version", platform_name, &*min_version, sdk_version]);
3054
+ } else {
3055
+ // cc == Cc::Yes
3056
+ // We'd _like_ to use `-target` everywhere, since that can uniquely
3057
+ // communicate all the required details, but that doesn't work on GCC,
3058
+ // and since we don't know whether the `cc` compiler is Clang, GCC, or
3059
+ // something else, we fall back to other options that also work on GCC
3060
+ // when compiling for macOS.
3061
+ //
3062
+ // Targets other than macOS are ill-supported by GCC (it doesn't even
3063
+ // support e.g. `-miphoneos-version-min`), so in those cases we can
3064
+ // fairly safely use `-target`. See also the following, where it is
3065
+ // made explicit that the recommendation by LLVM developers is to use
3066
+ // `-target`: <https://github.com/llvm/llvm-project/issues/88271>
3067
+ if target_os == "macos" {
3068
+ // `-arch` communicates the architecture.
3069
+ //
3070
+ // CC forwards the `-arch` to the linker, so we use the same value
3071
+ // here intentionally.
3072
+ cmd.cc_args(&["-arch", ld64_arch]);
3073
+
3074
+ // The presence of `-mmacosx-version-min` makes CC default to
3075
+ // macOS, and it sets the deployment target.
3076
+ let (major, minor, patch) = current_apple_deployment_target(&sess.target);
3077
+ // Intentionally pass this as a single argument, Clang doesn't
3078
+ // seem to like it otherwise.
3079
+ cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
3080
+
3081
+ // macOS has no environment, so with these two, we've told CC the
3082
+ // four desired parameters.
3083
+ //
3084
+ // We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
3085
+ } else {
3086
+ cmd.cc_args(&["-target", &sess.target.llvm_target]);
3087
+ }
3088
+ }
3089
+ }
3090
+
2960
3091
fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) -> Option<PathBuf> {
2961
3092
let arch = &sess.target.arch;
2962
3093
let os = &sess.target.os;
0 commit comments