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 = String;
// For clang "-fobjc-runtime" support
#[allow(clippy::upper_case_acronyms)]
enum AppleRuntime {
MacOS(Version),
IOS(Version),
TvOS(Version),
WatchOS(Version),
Unknown,
// BridgeOS,
}
use AppleRuntime::*;
enum Runtime {
Apple(AppleRuntime),
GNUStep(u8, u8),
WinObjC,
#[allow(dead_code)]
ObjFW(Option<String>),
}
use Runtime::*;
fn get_env(env: &str) -> Option<String> {
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) {
// Same logic as in https://github.com/rust-lang/rust/blob/1.63.0/compiler/rustc_target/src/spec/apple_base.rs
(true, false, false) => Apple(match &*target_os {
"macos" if target_arch == "aarch64" => {
MacOS(get_env("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|| "11.0".into()))
}
"macos" => MacOS(get_env("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|| "10.7".into())),
"ios" => IOS(get_env("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or_else(|| "7.0".into())),
"tvos" => TvOS(get_env("TVOS_DEPLOYMENT_TARGET").unwrap_or_else(|| "7.0".into())),
"watchos" => {
WatchOS(get_env("WATCHOS_DEPLOYMENT_TARGET").unwrap_or_else(|| "5.0".into()))
}
_ => Unknown,
}),
(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) => {
match (runtime, &*target_arch) {
// 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
(MacOS(version), "x86") => format!("macosx-fragile-{version}"),
(MacOS(version), _) => format!("macosx-{version}"),
(IOS(version), _) => format!("ios-{version}"),
(WatchOS(version), _) => format!("watchos-{version}"),
// tvOS doesn't have its own -fobjc-runtime string
(TvOS(version), _) => format!("ios-{version}"),
// Choose a sensible default for other platforms that
// specified `apple`; this is likely not going to work anyhow
(Unknown, _) => "macosx".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 <objc/objc.h>` 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");
}
}