@@ -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