Skip to content

Commit 4b401e5

Browse files
committed
Rework objc2_sys' build script
- Add `OBJC_RUNTIME` environment variable as input to the build script; allows the user to choose the targeted runtime (defaults to auto-detection based on compilation target) - Expose `DEP_OBJC_RUNTIME`, `DEP_OBJC_CLANG_ARGS` and `DEP_OBJC_GCC_ARGS` to downstream build scripts - Add WinObjc support - Fix BOOL on Windows - Support running GNUStep (and in the future, ObjFW) under Apple systems
1 parent a680abd commit 4b401e5

File tree

9 files changed

+251
-43
lines changed

9 files changed

+251
-43
lines changed

objc2_sys/README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,33 @@ Raw Rust bindings to core Objective-C runtimes and ABIs.
99

1010
## Runtime Support
1111

12-
`objc2_sys` currently supports two runtimes (support for [`ObjFW`] and
13-
[`WinObjC`] may be added):
14-
- Apple's [`objc4`] on `cfg(target_vendor = "apple")` targets.
15-
- GNUStep's [`libobjc2`] on all other targets. See their [Objective-C Compiler
16-
and Runtime FAQ][gnustep-faq].
12+
`objc2_sys` currently supports three runtimes (support for [`ObjFW`] may be
13+
added):
14+
- Apple's [`objc4`] \(default on `target_vendor = "apple"`\).
15+
- GNUStep's [`libobjc2`] \(default on other systems\).
16+
- Window's [`WinObjC`] uses [a fork][ms-libobjc2] of GNUStep's `libobjc2`
17+
based on version 1.8 with very few user-facing changes \(default on
18+
`target_os = "windows"`\).
19+
20+
A default is chosen automatically from the compilation target as seen above,
21+
but it is recommended to set the `OBJC_RUNTIME` environment variable to the
22+
desired runtime when building (see below).
1723

1824
This library will probably only ever support ["Modern"][modern] Objective-C
1925
runtimes, since support for reference-counting primitives like `objc_retain`
2026
and `objc_autoreleasePoolPop` is a vital requirement for most applications.
2127

2228
Just so we're being clear, this rules out the GCC [`libobjc`][gcc-libobjc]
23-
runtime (see [this][gcc-objc-support]), and the [`mulle-objc`] runtime.
29+
runtime (see [this][gcc-objc-support]), and the [`mulle-objc`] runtime. More
30+
information on different runtimes can be found in GNUStep's [Objective-C
31+
Compiler and Runtime FAQ][gnustep-faq].
2432

2533
[`ObjFW`]: https://github.com/ObjFW/ObjFW
26-
[`WinObjC`]: https://github.com/microsoft/WinObjC
2734
[`objc4`]: https://opensource.apple.com/source/objc4/
2835
[`libobjc2`]: https://github.com/gnustep/libobjc2
2936
[gnustep-faq]: http://wiki.gnustep.org/index.php/Objective-C_Compiler_and_Runtime_FAQ
37+
[`WinObjC`]: https://github.com/microsoft/WinObjC
38+
[ms-libobjc2]: https://github.com/microsoft/libobjc2
3039
[modern]: https://en.wikipedia.org/wiki/Objective-C#Modern_Objective-C
3140
[gcc-libobjc]: https://github.com/gcc-mirror/gcc/tree/master/libobjc
3241
[gcc-objc-support]: https://gcc.gnu.org/onlinedocs/gcc/Standards.html#Objective-C-and-Objective-C_002b_002b-Languages

objc2_sys/build.rs

Lines changed: 170 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,179 @@
11
use std::env;
22

3+
/// TODO: Better validation of this
4+
///
5+
/// The version is used for providing different behaviour when:
6+
/// - CGException.cpp getObjCPersonality (GNUStep >= 1.7)
7+
/// - Clang.cpp Clang::AddObjCRuntimeArgs (GNUStep >= 2.0)
8+
/// - isLegacyDispatchDefaultForArch (macOS < 10.6, GNUStep < 1.6)
9+
/// - hasNativeARC (macOS < 10.7, iOS < 5)
10+
/// - shouldUseARCFunctionsForRetainRelease (macOS < 10.10, iOS < 8)
11+
/// - shouldUseRuntimeFunctionsForAlloc (macOS < 10.10, iOS < 8)
12+
/// - shouldUseRuntimeFunctionForCombinedAllocInit (macOS >= 10.14.4, iOS >= 12.2, watchOS >= 5.2)
13+
/// - hasOptimizedSetter (macOS >= 10.8, iOS >= 6, GNUStep >= 1.7)
14+
/// - hasSubscripting (macOS < 10.11, iOS < 9)
15+
/// - hasTerminate (macOS < 10.8, iOS < 5)
16+
/// - hasARCUnsafeClaimAutoreleasedReturnValue (macOS >= 10.11, iOS >= 9, watchOS >= 2)
17+
/// - hasEmptyCollections (macOS >= 10.11, iOS >= 9, watchOS >= 2)
18+
/// - ... (incomplete)
19+
///
20+
/// `macosx-fragile` and `gcc` was not considered in this analysis, made on
21+
/// clang version 13's source code:
22+
/// https://github.com/llvm/llvm-project/blob/llvmorg-13.0.0/clang/include/clang/Basic/ObjCRuntime.h
23+
///
24+
/// Anyhow, it's not ultra important, but enables some optimizations if this
25+
/// is specified. In the future, Rust will hopefully get something similar to
26+
/// clang's `-mmacosx-version-min`, and then we won't need this for the
27+
/// Apple runtimes.
28+
type Version = Option<String>;
29+
30+
// For clang "-fobjc-runtime" support
31+
enum AppleRuntime {
32+
MacOS(Version),
33+
IOS(Version),
34+
TvOS(Version),
35+
WatchOS(Version),
36+
// BridgeOS,
37+
}
38+
use AppleRuntime::*;
39+
40+
impl AppleRuntime {
41+
fn get_default(target_os: &str) -> Option<Self> {
42+
match target_os {
43+
"macos" => Some(Self::MacOS(None)),
44+
"ios" => Some(Self::IOS(None)),
45+
"watchos" => Some(Self::WatchOS(None)),
46+
"tvos" => Some(Self::TvOS(None)),
47+
_ => None,
48+
}
49+
}
50+
}
51+
52+
enum Runtime {
53+
Apple(AppleRuntime),
54+
GNUStep(Version),
55+
WinObjc(Version),
56+
ObjFW(Version),
57+
}
58+
use Runtime::*;
59+
360
fn main() {
4-
// Only rerun if this file changes; the script doesn't depend on our code
61+
// The script doesn't depend on our code
562
println!("cargo:rerun-if-changed=build.rs");
6-
// Link to libobjc
7-
println!("cargo:rustc-link-lib=dylib=objc");
63+
64+
println!("cargo:rerun-if-env-changed=OBJC_RUNTIME");
65+
66+
// Used to figure out when BOOL should be i8 vs. bool
67+
if env::var("TARGET").unwrap().ends_with("macabi") {
68+
println!("cargo:rustc-cfg=target_abi_macabi");
69+
}
70+
71+
// TODO: Figure out when to enable this
72+
// println!("cargo:rustc-cfg=libobjc2_strict_apple_compat");
873

974
let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap();
75+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
76+
77+
// OBJC_RUNTIME syntax: `RUNTIME ("-" VERSION)?`
78+
let runtime = if let Ok(runtime) = env::var("OBJC_RUNTIME") {
79+
let (runtime, version) = if let Some((runtime, version)) = runtime.split_once('-') {
80+
(runtime, Some(version.into()))
81+
} else {
82+
(&*runtime, None)
83+
};
1084

11-
// Adds useful #[cfg(apple)] and #[cfg(gnustep)] directives
12-
if target_vendor == "apple" {
13-
println!("cargo:rustc-cfg=apple");
85+
match runtime {
86+
"apple" => {
87+
if version.is_some() {
88+
panic!("Invalid OBJC_RUNTIME: Version doesn't make sense for the `apple` runtime; use `macos`, `ios`, `tvos` or `watchos`");
89+
}
90+
Apple(AppleRuntime::get_default(&target_os).expect("Invalid OBJC_RUNTIME: Target OS invalid for the `apple` runtime; specify manually with `macos`, `ios`, `tvos` or `watchos`"))
91+
}
92+
"gnustep" => GNUStep(version),
93+
"winobjc" => WinObjc(version),
94+
"objfw" => ObjFW(version),
95+
// Support clang "syntax" (`macosx`)
96+
"macos" | "macosx" => Apple(MacOS(version)),
97+
"ios" => Apple(IOS(version)),
98+
"tvos" => Apple(TvOS(version)),
99+
"watchos" => Apple(WatchOS(version)),
100+
_ => {
101+
panic!("Invalid OBJC_RUNTIME: {}", runtime)
102+
}
103+
}
14104
} else {
15-
// TODO: Are there other possibilities than GNUStep? Airyx? Is it
16-
// possible to use Apple's open source objc4 on other platforms?
17-
println!("cargo:rustc-cfg=gnustep");
18-
// TODO: Should we vendor GNUStep's libobjc2?
19-
// Using Cargo.toml:
20-
// [target.'cfg(not(target_vendor = "apple"))'.build-dependencies]
21-
// cc = "1.0"
22-
}
105+
if target_vendor == "apple" {
106+
Apple(AppleRuntime::get_default(&target_os).unwrap())
107+
} else if target_os == "windows" {
108+
WinObjc(None)
109+
} else {
110+
GNUStep(None)
111+
}
112+
};
113+
114+
// Add `#[cfg(RUNTIME)]` directive
115+
let runtime_cfg = match runtime {
116+
Apple(_) => "apple",
117+
GNUStep(_) => "gnustep",
118+
WinObjc(_) => "winobjc",
119+
ObjFW(_) => "objfw",
120+
};
121+
println!("cargo:rustc-cfg={}", runtime_cfg);
122+
// Allow downstream build scripts to do the same
123+
println!("cargo:runtime={}", runtime_cfg); // DEP_OBJC_RUNTIME
124+
125+
let clang_runtime = match &runtime {
126+
Apple(runtime) => {
127+
let clang_runtime_str = match runtime {
128+
MacOS(_) => "macosx",
129+
IOS(_) => "ios",
130+
WatchOS(_) => "watchos",
131+
TvOS(_) => "ios", // ??
132+
};
133+
match runtime {
134+
MacOS(version) | IOS(version) | WatchOS(version) | TvOS(version) => {
135+
if let Some(version) = version {
136+
format!("{}-{}", clang_runtime_str, version)
137+
} else {
138+
clang_runtime_str.into()
139+
}
140+
}
141+
}
142+
}
143+
GNUStep(version) => {
144+
// GNUStep default in clang is 1.6; we require at least 1.7
145+
let version = version.as_deref().unwrap_or("1.7");
146+
format!("gnustep-{}", version)
147+
}
148+
WinObjc(version) => {
149+
// WinObjc use a small fork of GNUStep version 1.8; so lower
150+
// versions doesn't make sense.
151+
let version = version.as_deref().unwrap_or("1.8");
152+
format!("gnustep-{}", version)
153+
}
154+
ObjFW(version) => {
155+
// Default in clang
156+
let _version = version.as_deref().unwrap_or("0.8");
157+
todo!()
158+
}
159+
};
160+
161+
// Add clang arguments
162+
println!(
163+
"cargo:clang_args=-fobjc-arc -fobjc-arc-exceptions -fobjc-exceptions -fobjc-weak -fobjc-runtime={}",
164+
// -fobjc-arc implies -fobjc-link-runtime, so people actually don't
165+
// even need to specify `-lobjc` (though they probably still should).
166+
clang_runtime
167+
); // DEP_OBJC_CLANG_ARGS
168+
169+
// Add GCC arguments. Not really supported
170+
match runtime {
171+
Apple(_) => {
172+
println!("cargo:gcc_args=-fnext-runtime -fobjc-exceptions -fobjc-abi-version=2")
173+
}
174+
_ => println!("cargo:gcc_args=-fgnu-runtime -fobjc-exceptions"),
175+
} // DEP_OBJC_GCC_ARGS
176+
177+
// Link to libobjc
178+
println!("cargo:rustc-link-lib=dylib=objc");
23179
}

objc2_sys/src/constants.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use std::os::raw::c_int;
66
use crate::{id, Class, BOOL};
77

88
/// The equivalent of `true` for Objective-C's [`BOOL`][`super::BOOL`] type.
9-
pub const YES: BOOL = true as BOOL;
9+
pub const YES: BOOL = true as BOOL; // true -> 1
1010

1111
/// The equivalent of `false` for Objective-C's [`BOOL`][`super::BOOL`] type.
12-
pub const NO: BOOL = false as BOOL;
12+
pub const NO: BOOL = false as BOOL; // false -> 0
1313

1414
/// A quick alias for a [`null_mut`][`core::ptr::null_mut`] object / instance.
1515
pub const nil: id = 0 as *mut _;

objc2_sys/src/exception.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! GNUStep: `eh_personality.c`, which is a bit brittle to rely on, but I
44
//! think it's fine...
55
use core::ffi::c_void;
6-
#[cfg(any(apple, gnustep))]
6+
#[cfg(any(apple, gnustep, winobjc))]
77
use std::os::raw::c_int;
88

99
#[cfg(apple)]
@@ -37,7 +37,7 @@ extern "C" {
3737
pub fn objc_exception_throw(exception: *mut objc_object) -> !;
3838
#[cfg(apple)]
3939
pub fn objc_exception_rethrow() -> !;
40-
#[cfg(gnustep)]
40+
#[cfg(any(gnustep, winobjc))]
4141
pub fn objc_exception_rethrow(exc_buf: *mut c_void) -> !;
4242

4343
#[cfg(apple)]
@@ -58,6 +58,6 @@ extern "C" {
5858
#[cfg(all(apple, target_os = "macos"))]
5959
pub fn objc_removeExceptionHandler(token: usize);
6060

61-
#[cfg(gnustep)]
61+
#[cfg(any(gnustep, winobjc))]
6262
pub fn objc_set_apple_compatible_objcxx_exceptions(newValue: c_int) -> c_int;
6363
}

objc2_sys/src/message.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//!
55
//! TODO: Some of these are only supported on _some_ GNUStep targets!
66
use crate::{objc_class, objc_object};
7-
#[cfg(gnustep)]
7+
#[cfg(any(gnustep, winobjc))]
88
use crate::{objc_selector, IMP};
99

1010
/// Specifies data used when sending messages to superclasses.
@@ -20,7 +20,7 @@ pub struct objc_super {
2020
pub super_class: *const objc_class,
2121
}
2222

23-
#[cfg(gnustep)]
23+
#[cfg(any(gnustep, winobjc))]
2424
extern "C" {
2525
pub fn objc_msg_lookup(receiver: *mut objc_object, sel: *const objc_selector) -> IMP;
2626
pub fn objc_msg_lookup_super(sup: *const objc_super, sel: *const objc_selector) -> IMP;

objc2_sys/src/protocol.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,6 @@ extern "C" {
7777
// #[cfg(macos >= 10.12)]
7878
// protocol_copyPropertyList2
7979

80-
// #[cfg(gnustep)]
80+
// #[cfg(any(gnustep, winobjc))]
8181
// _protocol_getMethodTypeEncoding
8282
}

objc2_sys/src/rc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ extern "C" {
4848
-> *mut objc_object;
4949

5050
// TODO: Decide about nonstandard extensions like these:
51-
// #[cfg(gnustep)]
51+
// #[cfg(any(gnustep, winobjc))]
5252
// pub fn objc_delete_weak_refs(obj: *mut objc_object) -> BOOL;
5353
}

objc2_sys/src/types.rs

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,68 @@ use crate::{
44
objc_class, objc_ivar, objc_method, objc_object, objc_property, objc_protocol, objc_selector,
55
};
66

7-
#[cfg(all(apple, not(target_arch = "aarch64")))]
8-
// C: (explicitly) signed char
9-
type BOOL_INNER = i8;
7+
/// The BOOL typedef for Apple's objc4.
8+
///
9+
/// Don't be fooled by the backup definition in `objc.h`; __OBJC_BOOL_IS_BOOL
10+
/// is always defined by `clang` when compiling Objective-C sources. The below
11+
/// cfgs are determined experimentally via. cross compiling.
12+
#[cfg(apple)]
13+
mod inner {
14+
// __OBJC_BOOL_IS_BOOL
15+
#[cfg(any(
16+
// aarch64-apple-*
17+
target_arch = "aarch64",
18+
// + x86_64-apple-ios (but not x86_64-apple-ios-macabi)
19+
all(target_os = "ios", target_pointer_width = "64", not(target_abi_macabi)),
20+
// + x86_64-apple-tvos
21+
all(target_os = "tvos", target_pointer_width = "64"),
22+
// + *-apple-watchos (no Rust targets with this yet)
23+
target_os = "watchos",
24+
))]
25+
// C: _Bool
26+
pub type BOOL = bool;
27+
28+
// Inverse of the above
29+
#[cfg(not(any(
30+
target_arch = "aarch64",
31+
all(target_os = "ios", target_pointer_width = "64", not(target_abi_macabi)),
32+
all(target_os = "tvos", target_pointer_width = "64"),
33+
target_os = "watchos",
34+
)))]
35+
// C: (explicitly) signed char
36+
pub type BOOL = i8;
37+
}
38+
39+
// GNUStep's and Microsoft's libobjc2
40+
#[cfg(all(any(gnustep, winobjc), libobjc2_strict_apple_compat))]
41+
mod inner {
42+
// C: (explicitly) signed char
43+
pub type BOOL = i8;
44+
}
45+
46+
#[cfg(all(any(gnustep, winobjc), not(libobjc2_strict_apple_compat)))]
47+
mod inner {
48+
// windows && !32bit-MinGW
49+
#[cfg(all(windows, not(all(target_pointer_width = "64", target_env = "gnu"))))]
50+
pub type BOOL = std::os::raw::c_int;
1051

11-
#[cfg(all(gnustep, not(target_arch = "aarch64")))]
12-
// TODO: Only if STRICT_APPLE_COMPATIBILITY is NOT defined.
13-
// TODO: (__vxworks || _WIN32) becomes BOOL = c_int.
14-
// C: unsigned char
15-
type BOOL_INNER = u8;
52+
// The inverse
53+
#[cfg(not(all(windows, not(all(target_pointer_width = "64", target_env = "gnu")))))]
54+
// C: unsigned char
55+
pub type BOOL = u8;
56+
}
1657

17-
#[cfg(target_arch = "aarch64")]
18-
// C: _Bool
19-
type BOOL_INNER = bool;
58+
// ObjFW???
59+
#[cfg(objfw)]
60+
mod inner {
61+
pub type BOOL = todo!();
62+
}
2063

2164
/// The Objective-C `BOOL` type.
2265
///
2366
/// The type of this varies across platforms, so to convert an it into a Rust
2467
/// [`bool`], always compare it with [`YES`][`crate::YES`] or [`NO`][`crate::NO`].
25-
pub type BOOL = BOOL_INNER;
68+
pub type BOOL = inner::BOOL;
2669

2770
/// An immutable pointer to a selector.
2871
///

objc2_sys/src/various.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,6 @@ extern "C" {
9898
// #[cfg(apple)]
9999
// pub fn _objc_flush_caches
100100

101-
// #[cfg(gnustep)]
101+
// #[cfg(any(gnustep, winobjc))]
102102
// objc_test_capability
103103
}

0 commit comments

Comments
 (0)