Skip to content

Commit 7a7da42

Browse files
committed
Support for the GNUstep Objective-C runtime (SSheldon#27).
Support for the GNUstep Objective-C runtime Restore accidentally lost attribute Cross-platform support for message sends with the GNUstep runtime. Add feature gates for the platform specific objc_msgSend implementation used by the GNUstep runtime (mips not currently supported because I'm unaware of the calling conventions). In all other cases, fall back to the cross-platform two-stage message sending. This now works and passes some of the tests (I had a buggy old gcc version yesterday, it seems). Every test that assumes NSObject to be present fails, though because that is not part of the GNUstep runtime. We'll either have to link gnustep-base for the tests to run properly, or provide stub implementation. Fix calling objc_slot_lookup_super(), which had the argument type wrong. Trick the linker into linking gnustep-base to pull in NSObject, eventhough we never reference the symbol for it. Also a bit of documentation. Fix libobjc2 repository location. Satisfy test dependencies using a stub NSObject implementation. Requires a patched gcc crate at the moment. Word Update to track proposed gcc-rs API Changes to the gcc crate were merged (cf. rust-lang/cc-rs#54) Experiment with travis builds Slim down a bit Shell script syntax is hard More shell script tweaks Tweak libobjc2 install location Stage libobjc2 into a local directory Conditionalize features, fix missing ‘;’ again. GNUstep base is no longer required for running tests. Fix gcc-rs revision Depend on a specific gcc-rs revision from github until a release includes the changes needed. Update dependencies to the released gcc-rs version. Exclude .travis.yml from publishing Restore original arch (aarch64 was incorrectly replaced with arm) Move NSObject dependency for tests with the GNUstep runtime into a sub-crate Rename ‘gnustep_runtime’ to ‘gnustep’ for brevity.
1 parent 9c5bec4 commit 7a7da42

File tree

12 files changed

+213
-11
lines changed

12 files changed

+213
-11
lines changed

.travis.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,31 @@
11
language: rust
2+
rust:
3+
- stable
4+
- beta
5+
- nightly
26
sudo: false
7+
install:
8+
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then export FEATURE="gnustep"; export CC="clang"; export CXX="clang++"; else export FEATURE=""; fi
9+
- if [[ "$FEATURE" == *"gnustep"* ]]; then git clone https://github.com/gnustep/libobjc2.git; fi
10+
- if [[ "$FEATURE" == *"gnustep"* ]]; then mkdir libobjc2/build; pushd libobjc2/build; fi
11+
- if [[ "$FEATURE" == *"gnustep"* ]]; then cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/libobjc2_staging ../; fi
12+
- if [[ "$FEATURE" == *"gnustep"* ]]; then make install; fi
13+
- if [[ "$FEATURE" == *"gnustep"* ]]; then export CPATH=$HOME/libobjc2_staging/include:$CPATH; export LIBRARY_PATH=$HOME/libobjc2_staging/lib:$LIBRARY_PATH; LD_LIBRARY_PATH=$HOME/libobjc2_staging/lib:$LD_LIBRARY_PATH; fi
14+
- if [[ "$FEATURE" == *"gnustep"* ]]; then popd; fi
15+
- if [ -n "$FEATURE" ]; then export FEATURES="--features $FEATURE"; else export FEATURES=""; fi;
16+
script:
17+
- cargo build $FEATURES
18+
- cargo test $FEATURES
19+
- cargo doc $FEATURES
20+
env:
21+
notifications:
22+
email:
23+
on_success: never
324
os:
25+
- linux
426
- osx
27+
addons:
28+
apt:
29+
packages:
30+
- clang-3.7
31+
- cmake

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@ repository = "http://github.com/SSheldon/rust-objc"
1010
documentation = "http://ssheldon.github.io/rust-objc/objc/"
1111
license = "MIT"
1212

13-
exclude = [".gitignore", ".travis.yml", "ios-tests/**", "xtests/**"]
13+
exclude = [".gitignore", "ios-tests/**", "xtests/**", ".travis.yml"]
1414

1515
[features]
1616
exception = ["objc_exception"]
1717
verify_message = []
18+
gnustep = [ "test_ns_object/gnustep" ]
1819

1920
[dependencies]
2021
malloc_buf = "0.0"
2122

2223
[dependencies.objc_exception]
2324
version = "0.1"
2425
optional = true
26+
27+
[dev-dependencies.test_ns_object]
28+
version = "0.0"
29+
path = "test_utils"

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,10 @@ will unwind into Rust resulting in unsafe, undefined behavior.
5050
However, this crate has an `"exception"` feature which, when enabled, wraps
5151
each `msg_send!` in a `@try`/`@catch` and panics if an exception is caught,
5252
preventing Objective-C from unwinding into Rust.
53+
54+
## Support for other Operating Systems
55+
56+
The bindings can be used on Linux or *BSD utilizing the
57+
[GNUstep Objective-C runtime](https://www.github.com/gnustep/libobjc2).
58+
To enable it, you need to pass the required feature to cargo:
59+
`cargo build --feature gnustep`.

src/declare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ methods can then be added before the class is ultimately registered.
99
The following example demonstrates declaring a class named `MyNumber` that has
1010
one ivar, a `u32` named `_number` and a `number` method that returns it:
1111
12-
```
12+
```no_run
1313
# #[macro_use] extern crate objc;
1414
# use objc::declare::ClassDecl;
1515
# use objc::runtime::{Class, Object, Sel};

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Objective-C Runtime bindings and wrapper for Rust.
55
66
Objective-C objects can be messaged using the [`msg_send!`](macro.msg_send!.html) macro:
77
8-
```
8+
```no_run
99
# #[macro_use] extern crate objc;
1010
# use objc::runtime::{BOOL, Class, Object};
1111
# fn main() {

src/message.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::any::Any;
22
use std::mem;
3-
43
use runtime::{Class, Object, Sel, Super, self};
54

65
/// Types that may be sent Objective-C messages.
@@ -31,7 +30,7 @@ fn msg_send_fn<R: Any>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
3130
}
3231
}
3332

34-
#[cfg(target_arch = "x86")]
33+
#[cfg(all(target_arch = "x86", not(feature = "gnustep")))]
3534
fn msg_send_super_fn<R: Any>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
3635
let size = mem::size_of::<R>();
3736
if size == 0 || size == 1 || size == 2 || size == 4 || size == 8 {
@@ -55,7 +54,7 @@ fn msg_send_fn<R>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
5554
}
5655
}
5756

58-
#[cfg(target_arch = "x86_64")]
57+
#[cfg(all(target_arch = "x86_64", not(feature = "gnustep")))]
5958
fn msg_send_super_fn<R>() -> unsafe extern fn(*const Super, Sel, ...) -> R {
6059
if mem::size_of::<R>() <= 16 {
6160
unsafe { mem::transmute(runtime::objc_msgSendSuper) }
@@ -83,7 +82,7 @@ fn msg_send_fn<R: Any>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
8382
}
8483
}
8584

86-
#[cfg(target_arch = "arm")]
85+
#[cfg(all(target_arch = "arm", not(feature = "gnustep")))]
8786
fn msg_send_super_fn<R: Any>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
8887
use std::any::TypeId;
8988

@@ -98,15 +97,15 @@ fn msg_send_super_fn<R: Any>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
9897
}
9998
}
10099

101-
#[cfg(target_arch = "aarch64")]
100+
#[cfg(all(target_arch = "aarch64", not(feature = "gnustep")))]
102101
fn msg_send_fn<R>() -> unsafe extern fn(*mut Object, Sel, ...) -> R {
103102
// stret is not even available in arm64.
104103
// https://twitter.com/gparker/status/378079715824660480
105104

106105
unsafe { mem::transmute(runtime::objc_msgSend) }
107106
}
108107

109-
#[cfg(target_arch = "aarch64")]
108+
#[cfg(all(target_arch = "aarch64", not(feature ="gnustep")))]
110109
fn msg_send_super_fn<R>() -> unsafe extern fn(*const Super, Sel, ...) -> R {
111110
unsafe { mem::transmute(runtime::objc_msgSendSuper) }
112111
}
@@ -134,6 +133,10 @@ pub trait MessageArguments {
134133
macro_rules! message_args_impl {
135134
($($a:ident : $t:ident),*) => (
136135
impl<$($t),*> MessageArguments for ($($t,)*) {
136+
#[cfg(any(not(feature="gnustep"),
137+
any(target_arch = "arm",
138+
target_arch = "x86",
139+
target_arch = "x86_64")))]
137140
unsafe fn send<T, R>(self, obj: *mut T, sel: Sel) -> R
138141
where T: Message, R: Any {
139142
let msg_send_fn = msg_send_fn::<R>();
@@ -145,6 +148,25 @@ macro_rules! message_args_impl {
145148
})
146149
}
147150

151+
#[cfg(all(feature="gnustep",
152+
not(any(target_arch = "arm",
153+
target_arch = "x86",
154+
target_arch = "x86_64"))))]
155+
unsafe fn send<T, R>(self, obj: *mut T, sel: Sel) -> R
156+
where T: Message, R: Any {
157+
let mut receiver = obj as *mut Object;
158+
let nil: *mut Object = ::std::ptr::null_mut();
159+
let ref slot = *runtime::objc_msg_lookup_sender(&mut receiver as *mut *mut Object, sel, nil);
160+
let imp_fn = slot.method;
161+
let imp_fn: unsafe extern fn(*mut Object, Sel $(, $t)*) -> R =
162+
mem::transmute(imp_fn);
163+
let ($($a,)*) = self;
164+
objc_try!({
165+
imp_fn(receiver as *mut Object, sel $(, $a)*)
166+
})
167+
}
168+
169+
#[cfg(not(feature="gnustep"))]
148170
unsafe fn send_super<T, R>(self, obj: *mut T, superclass: &Class, sel: Sel) -> R
149171
where T: Message, R: Any {
150172
let msg_send_fn = msg_send_super_fn::<R>();
@@ -156,6 +178,20 @@ macro_rules! message_args_impl {
156178
msg_send_fn(&sup, sel $(, $a)*)
157179
})
158180
}
181+
182+
#[cfg(feature="gnustep")]
183+
unsafe fn send_super<T, R>(self, obj: *mut T, superclass: &Class, sel: Sel) -> R
184+
where T: Message, R: Any {
185+
let sup = Super { receiver: obj as *mut Object, superclass: superclass };
186+
let ref slot = *runtime::objc_slot_lookup_super(&sup, sel);
187+
let imp_fn = slot.method;
188+
let imp_fn: unsafe extern fn(*mut Object, Sel $(, $t)*) -> R =
189+
mem::transmute(imp_fn);
190+
let ($($a,)*) = self;
191+
objc_try!({
192+
imp_fn(obj as *mut Object, sel $(, $a)*)
193+
})
194+
}
159195
}
160196
);
161197
}

src/runtime.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ pub struct Sel {
3838
ptr: *const c_void,
3939
}
4040

41+
42+
/// A structure describing a safely cacheable method implementation
43+
/// in the GNUstep Objective-C runtime.
44+
#[cfg(feature="gnustep")]
45+
#[repr(C)]
46+
pub struct Slot {
47+
/// The class to which the slot is attached
48+
pub owner: *const Class,
49+
/// The class for which this slot was cached.
50+
pub cached_for: *mut Class,
51+
/// The type signature of the method
52+
pub types: *const c_char,
53+
/// The version of the method. Will change if overriden, invalidating
54+
/// the cache
55+
pub version: c_int,
56+
/// The implementation of the method
57+
pub method: Imp,
58+
/// The associated selector
59+
pub selector: Sel
60+
}
61+
4162
/// A marker type to be embedded into other types just so that they cannot be
4263
/// constructed externally.
4364
enum PrivateMarker { }
@@ -79,7 +100,7 @@ pub struct Super {
79100
pub type Imp = extern fn(*mut Object, Sel, ...) -> *mut Object;
80101

81102
#[link(name = "objc", kind = "dylib")]
82-
extern {
103+
extern "C" {
83104
pub fn sel_registerName(name: *const c_char) -> Sel;
84105
pub fn sel_getName(sel: Sel) -> *const c_char;
85106

@@ -123,6 +144,11 @@ extern {
123144
pub fn method_getNumberOfArguments(method: *const Method) -> c_uint;
124145
pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp;
125146
pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method);
147+
148+
#[cfg(feature="gnustep")]
149+
pub fn objc_msg_lookup_sender(receiver: *mut *mut Object, selector: Sel, sender: *mut Object, ...) -> *mut Slot;
150+
#[cfg(feature="gnustep")]
151+
pub fn objc_slot_lookup_super(sup: *const Super, selector: Sel) -> *mut Slot;
126152
}
127153

128154
impl Sel {
@@ -444,7 +470,7 @@ mod tests {
444470
assert!(method.name().name() == "description");
445471
assert!(method.arguments_count() == 2);
446472
assert!(method.return_type() == <*mut Object>::encode());
447-
assert!(method.argument_type(1).unwrap() == Sel::encode());
473+
assert_eq!(method.argument_type(1).unwrap(), Sel::encode());
448474

449475
let methods = cls.instance_methods();
450476
assert!(methods.len() > 0);

src/test_utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ use id::StrongPtr;
55
use runtime::{Class, Object, Sel};
66
use {Encode, Encoding};
77

8+
9+
10+
11+
#[cfg(feature="gnustep")]
12+
#[link(name = "NSObject", kind = "static")]
13+
extern {
14+
}
15+
16+
817
pub fn sample_object() -> StrongPtr {
918
let cls = Class::get("NSObject").unwrap();
1019
unsafe {

test_utils/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "test_ns_object"
3+
version = "0.0.1"
4+
authors = ["Niels Grewe"]
5+
6+
description = "Mock implementation of NSObject for tests"
7+
repository = "http://github.com/SSheldon/rust-objc"
8+
license = "MIT"
9+
10+
build = "build.rs"
11+
12+
[features]
13+
gnustep = [ "gcc" ]
14+
15+
[lib]
16+
name = "test_ns_object"
17+
path = "lib.rs"
18+
19+
[build-dependencies.gcc]
20+
gcc = "0.3"
21+
optional = true

test_utils/NSObject.m

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <objc/runtime.h>
2+
#include <stdint.h>
3+
/**
4+
* This is a mock implementation of NSObject, which will be linked against
5+
* the tests in order to provide a superclass for them.
6+
*/
7+
__attribute__((objc_root_class))
8+
@interface NSObject
9+
{
10+
Class isa;
11+
}
12+
@end
13+
14+
@implementation NSObject
15+
16+
+ (id)alloc
17+
{
18+
return class_createInstance(self, 0);
19+
}
20+
21+
- (id)init
22+
{
23+
return self;
24+
}
25+
26+
- (id)self
27+
{
28+
return self;
29+
}
30+
31+
- (uintptr_t)hash
32+
{
33+
return (uintptr_t)(void*)self;
34+
}
35+
36+
- (void)dealloc
37+
{
38+
object_dispose(self);
39+
}
40+
41+
- (NSObject*)description
42+
{
43+
return nil;
44+
}
45+
@end

test_utils/build.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#[cfg(feature="gnustep")]
2+
extern crate gcc;
3+
#[cfg(feature="gnustep")]
4+
use std::path::PathBuf;
5+
6+
7+
#[cfg(not(feature="gnustep"))]
8+
fn compile() {
9+
}
10+
11+
#[cfg(feature="gnustep")]
12+
fn compile() {
13+
gcc::Config::new().flag("-lobjc")
14+
.flag("-fobjc-runtime=gnustep-1.8")
15+
.flag("-fno-objc-legacy-dispatch")
16+
.file("NSObject.m")
17+
.compile("libNSObject.a");
18+
let path = ::std::env::var_os("OUT_DIR").map(PathBuf::from).unwrap();
19+
println!("cargo:rustc-link-search=native={}", path.display());
20+
}
21+
fn main() {
22+
compile();
23+
}

test_utils/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#![crate_name = "test_ns_object"]
2+
#![crate_type = "lib"]
3+

0 commit comments

Comments
 (0)