use std::{env, path::Path}; /// TODO: Better validation of this /// /// The version is used for providing different behaviour when: /// - CGException.cpp getObjCPersonality (GNUStep >= 1.7) /// - Clang.cpp Clang::AddObjCRuntimeArgs (GNUStep >= 2.0) /// - isLegacyDispatchDefaultForArch (macOS < 10.6, GNUStep < 1.6) /// - hasNativeARC (macOS < 10.7, iOS < 5) /// - shouldUseARCFunctionsForRetainRelease (macOS < 10.10, iOS < 8) /// - shouldUseRuntimeFunctionsForAlloc (macOS < 10.10, iOS < 8) /// - shouldUseRuntimeFunctionForCombinedAllocInit (macOS >= 10.14.4, iOS >= 12.2, watchOS >= 5.2) /// - hasOptimizedSetter (macOS >= 10.8, iOS >= 6, GNUStep >= 1.7) /// - hasSubscripting (macOS < 10.11, iOS < 9) /// - hasTerminate (macOS < 10.8, iOS < 5) /// - hasARCUnsafeClaimAutoreleasedReturnValue (macOS >= 10.11, iOS >= 9, watchOS >= 2) /// - hasEmptyCollections (macOS >= 10.11, iOS >= 9, watchOS >= 2) /// - ... (incomplete) /// /// `macosx-fragile` and `gcc` was not considered in this analysis, made on /// clang version 13's source code: /// https://github.com/llvm/llvm-project/blob/llvmorg-13.0.0/clang/include/clang/Basic/ObjCRuntime.h /// /// In short, it's not ultra important, but enables some optimizations if this /// is specified. type Version = Option; // For clang "-fobjc-runtime" support #[allow(clippy::upper_case_acronyms)] enum AppleRuntime { MacOS(Version), IOS(Version), TvOS(Version), WatchOS(Version), // BridgeOS, } use AppleRuntime::*; enum Runtime { Apple(AppleRuntime), GNUStep(u8, u8), WinObjC, #[allow(dead_code)] ObjFW(Option), } use Runtime::*; fn get_env(env: &str) -> Option { println!("cargo:rerun-if-env-changed={}", env); match env::var(env) { Ok(var) => Some(var), Err(env::VarError::NotPresent) => None, Err(env::VarError::NotUnicode(var)) => panic!("Invalid unicode for {}: {:?}", env, var), } } fn main() { // The script doesn't depend on our code println!("cargo:rerun-if-changed=build.rs"); let target = env::var("TARGET").unwrap(); // Used to figure out when BOOL should be i8 vs. bool // Matches: // aarch64-apple-ios-macabi // x86_64-apple-ios-macabi if target.ends_with("macabi") { println!("cargo:rustc-cfg=target_abi_macabi"); } // Used to set correct image info in `objc2` // Matches: // aarch64-apple-ios-sim // aarch64-apple-watchos-sim // x86_64-apple-watchos-sim // i386-apple-ios // x86_64-apple-ios if target.ends_with("sim") || target == "i386-apple-ios" || target == "x86_64-apple-ios" { println!("cargo:rustc-cfg=target_simulator"); } // TODO: Figure out when to enable this // println!("cargo:rustc-cfg=libobjc2_strict_apple_compat"); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let mut apple = env::var_os("CARGO_FEATURE_APPLE").is_some(); let mut gnustep = env::var_os("CARGO_FEATURE_GNUSTEP_1_7").is_some(); let objfw = env::var_os("CARGO_FEATURE_UNSTABLE_OBJFW").is_some(); // Choose defaults when generating docs // Only when the crate is being compiled directly if cfg!(feature = "unstable-docsrs") { if let "macos" | "ios" | "tvos" | "watchos" = &*target_os { apple = true; } else { gnustep = true; // Also winobjc } } let runtime = match (apple, gnustep, objfw) { (true, false, false) => { Apple(match &*target_os { "macos" => MacOS(Some( get_env("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|| "10.7".into()), )), "ios" => IOS(Some( get_env("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or_else(|| "7.0".into()), )), "tvos" => TvOS(get_env("TVOS_DEPLOYMENT_TARGET")), "watchos" => WatchOS(get_env("WATCHOS_DEPLOYMENT_TARGET")), // Choose a sensible default for other platforms that // specified `apple`; this is likely not going to work anyhow _ => MacOS(None), }) } (false, true, false) => { // Choose defaults when generating docs if cfg!(feature = "unstable-docsrs") { if "windows" == target_os { WinObjC } else { GNUStep(1, 7) } } else if env::var_os("CARGO_FEATURE_UNSTABLE_WINOBJC").is_some() { WinObjC } else if env::var_os("CARGO_FEATURE_GNUSTEP_2_1").is_some() { GNUStep(2, 1) } else if env::var_os("CARGO_FEATURE_GNUSTEP_2_0").is_some() { GNUStep(2, 0) } else if env::var_os("CARGO_FEATURE_GNUSTEP_1_9").is_some() { GNUStep(1, 9) } else if env::var_os("CARGO_FEATURE_GNUSTEP_1_8").is_some() { GNUStep(1, 8) } else { // CARGO_FEATURE_GNUSTEP_1_7 GNUStep(1, 7) } } (false, false, true) => { // For now unimplemented!("ObjFW is not yet supported") // ObjFW(None) } (false, false, false) => panic!("Must specify the desired runtime (using cargo features)."), _ => panic!("Invalid feature combination; only one runtime may be selected!"), }; // Add `#[cfg(RUNTIME)]` directive let runtime_cfg = match runtime { Apple(_) => "apple", // WinObjC can be treated like GNUStep 1.8 GNUStep(_, _) | WinObjC => "gnustep", ObjFW(_) => "objfw", }; println!("cargo:rustc-cfg={}", runtime_cfg); if let Apple(runtime) = &runtime { // A few things are defined differently depending on the __OBJC2__ // variable, which is set for all platforms except 32-bit macOS. if let (MacOS(_), "x86") = (runtime, &*target_arch) { println!("cargo:rustc-cfg=apple_old"); } else { println!("cargo:rustc-cfg=apple_new"); } } let clang_runtime = match &runtime { Apple(runtime) => { // The fragile runtime is expected on i686-apple-darwin, see: // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/ToolChains/Darwin.h#L228-L231 // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/ToolChains/Clang.cpp#L3639-L3640 let clang_runtime_str = match (runtime, &*target_arch) { (MacOS(_), "x86") => "macosx-fragile", (MacOS(_), _) => "macosx", (IOS(_), _) => "ios", (WatchOS(_), _) => "watchos", // tvOS doesn't have its own -fobjc-runtime string (TvOS(_), _) => "ios", }; match runtime { MacOS(version) | IOS(version) | WatchOS(version) | TvOS(version) => { if let Some(version) = version { format!("{}-{}", clang_runtime_str, version) } else { clang_runtime_str.into() } } } } // Default in clang is 1.6 // GNUStep's own default is 1.8 GNUStep(major, minor) => format!("gnustep-{}.{}", major, minor), // WinObjC's libobjc2 is just a fork of gnustep's from version 1.8 WinObjC => "gnustep-1.8".into(), ObjFW(version) => { // Default in clang let version = version.as_deref().unwrap_or("0.8"); format!("objfw-{}", version) } }; // let gcc_args = match &runtime { // Apple(_) => "-fnext-runtime -fobjc-abi-version=2", // _ => "-fgnu-runtime", // }; // Add CC arguments // Assume the compiler is clang; if it isn't, this is probably going to // fail anyways, since we're using newer runtimes than GCC supports. // // TODO: Should add we these, or is it someone else's responsibility? // - `-mios-simulator-version-min={}` // - `-miphoneos-version-min={}` // - `-mmacosx-version-min={}` // - ... // // TODO: -fobjc-weak ? let mut cc_args = format!( "-fobjc-arc -fobjc-arc-exceptions -fobjc-exceptions -fobjc-runtime={}", clang_runtime ); if let Runtime::ObjFW(_) = &runtime { // Add compability headers to make `#include ` work. let compat_headers = Path::new(env!("CARGO_MANIFEST_DIR")).join("compat-headers-objfw"); cc_args.push_str(" -I"); cc_args.push_str(compat_headers.to_str().unwrap()); } println!("cargo:cc_args={}", cc_args); // DEP_OBJC_[version]_CC_ARGS if let Runtime::ObjFW(_) = &runtime { // Link to libobjfw-rt println!("cargo:rustc-link-lib=dylib=objfw-rt"); } else { // Link to libobjc println!("cargo:rustc-link-lib=dylib=objc"); } // We do this compilation step here instead of in `objc2` to cut down on // the total number of build scripts required. #[cfg(feature = "unstable-exception")] { if std::env::var("DOCS_RS").is_ok() { // docs.rs doesn't have clang, so skip building this. The // documentation will still work since it doesn't need to link. // // This is independent of the `unstable-docsrs` feature flag; we // never want to try invoking clang on docs.rs, whether we're the // crate being documented currently, or a dependency of another // crate. return; } println!("cargo:rerun-if-changed=extern/exception.m"); let mut builder = cc::Build::new(); builder.file("extern/exception.m"); for flag in cc_args.split(' ') { builder.flag(flag); } builder.compile("librust_objc_sys_0_2_try_catch_exception.a"); } }