@@ -40,7 +40,7 @@ use rustc_target::spec::crt_objects::CrtObjects;
4040use rustc_target:: spec:: {
4141 Cc , LinkOutputKind , LinkSelfContainedComponents , LinkSelfContainedDefault , LinkerFeatures ,
4242 LinkerFlavor , LinkerFlavorCli , Lld , PanicStrategy , RelocModel , RelroLevel , SanitizerSet ,
43- SplitDebuginfo ,
43+ SplitDebuginfo , current_apple_deployment_target ,
4444} ;
4545use tempfile:: Builder as TempFileBuilder ;
4646use tracing:: { debug, info, warn} ;
@@ -2405,6 +2405,8 @@ fn add_order_independent_options(
24052405 // Take care of the flavors and CLI options requesting the `lld` linker.
24062406 add_lld_args ( cmd, sess, flavor, self_contained_components) ;
24072407
2408+ add_apple_link_args ( cmd, sess, flavor) ;
2409+
24082410 let apple_sdk_root = add_apple_sdk ( cmd, sess, flavor) ;
24092411
24102412 add_link_script ( cmd, sess, tmpdir, crate_type) ;
@@ -2957,6 +2959,135 @@ pub(crate) fn are_upstream_rust_objects_already_included(sess: &Session) -> bool
29572959 }
29582960}
29592961
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+
29603091fn add_apple_sdk ( cmd : & mut dyn Linker , sess : & Session , flavor : LinkerFlavor ) -> Option < PathBuf > {
29613092 let arch = & sess. target . arch ;
29623093 let os = & sess. target . os ;
0 commit comments