Skip to content

Commit

Permalink
Support for the GNUstep Objective-C runtime (SSheldon/rust-objc#27).
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ngrewe committed Nov 24, 2015
1 parent 9c5bec4 commit 7a7da42
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 11 deletions.
27 changes: 27 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
language: rust
rust:
- stable
- beta
- nightly
sudo: false
install:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then export FEATURE="gnustep"; export CC="clang"; export CXX="clang++"; else export FEATURE=""; fi
- if [[ "$FEATURE" == *"gnustep"* ]]; then git clone https://github.com/gnustep/libobjc2.git; fi
- if [[ "$FEATURE" == *"gnustep"* ]]; then mkdir libobjc2/build; pushd libobjc2/build; fi
- if [[ "$FEATURE" == *"gnustep"* ]]; then cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/libobjc2_staging ../; fi
- if [[ "$FEATURE" == *"gnustep"* ]]; then make install; fi
- 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
- if [[ "$FEATURE" == *"gnustep"* ]]; then popd; fi
- if [ -n "$FEATURE" ]; then export FEATURES="--features $FEATURE"; else export FEATURES=""; fi;
script:
- cargo build $FEATURES
- cargo test $FEATURES
- cargo doc $FEATURES
env:
notifications:
email:
on_success: never
os:
- linux
- osx
addons:
apt:
packages:
- clang-3.7
- cmake
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ repository = "http://github.com/SSheldon/rust-objc"
documentation = "http://ssheldon.github.io/rust-objc/objc/"
license = "MIT"

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

[features]
exception = ["objc_exception"]
verify_message = []
gnustep = [ "test_ns_object/gnustep" ]

[dependencies]
malloc_buf = "0.0"

[dependencies.objc_exception]
version = "0.1"
optional = true

[dev-dependencies.test_ns_object]
version = "0.0"
path = "test_utils"
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ will unwind into Rust resulting in unsafe, undefined behavior.
However, this crate has an `"exception"` feature which, when enabled, wraps
each `msg_send!` in a `@try`/`@catch` and panics if an exception is caught,
preventing Objective-C from unwinding into Rust.

## Support for other Operating Systems

The bindings can be used on Linux or *BSD utilizing the
[GNUstep Objective-C runtime](https://www.github.com/gnustep/libobjc2).
To enable it, you need to pass the required feature to cargo:
`cargo build --feature gnustep`.
2 changes: 1 addition & 1 deletion src/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ methods can then be added before the class is ultimately registered.
The following example demonstrates declaring a class named `MyNumber` that has
one ivar, a `u32` named `_number` and a `number` method that returns it:
```
```no_run
# #[macro_use] extern crate objc;
# use objc::declare::ClassDecl;
# use objc::runtime::{Class, Object, Sel};
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Objective-C Runtime bindings and wrapper for Rust.
Objective-C objects can be messaged using the [`msg_send!`](macro.msg_send!.html) macro:
```
```no_run
# #[macro_use] extern crate objc;
# use objc::runtime::{BOOL, Class, Object};
# fn main() {
Expand Down
48 changes: 42 additions & 6 deletions src/message.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::any::Any;
use std::mem;

use runtime::{Class, Object, Sel, Super, self};

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

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

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

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

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

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

unsafe { mem::transmute(runtime::objc_msgSend) }
}

#[cfg(target_arch = "aarch64")]
#[cfg(all(target_arch = "aarch64", not(feature ="gnustep")))]
fn msg_send_super_fn<R>() -> unsafe extern fn(*const Super, Sel, ...) -> R {
unsafe { mem::transmute(runtime::objc_msgSendSuper) }
}
Expand Down Expand Up @@ -134,6 +133,10 @@ pub trait MessageArguments {
macro_rules! message_args_impl {
($($a:ident : $t:ident),*) => (
impl<$($t),*> MessageArguments for ($($t,)*) {
#[cfg(any(not(feature="gnustep"),
any(target_arch = "arm",
target_arch = "x86",
target_arch = "x86_64")))]
unsafe fn send<T, R>(self, obj: *mut T, sel: Sel) -> R
where T: Message, R: Any {
let msg_send_fn = msg_send_fn::<R>();
Expand All @@ -145,6 +148,25 @@ macro_rules! message_args_impl {
})
}

#[cfg(all(feature="gnustep",
not(any(target_arch = "arm",
target_arch = "x86",
target_arch = "x86_64"))))]
unsafe fn send<T, R>(self, obj: *mut T, sel: Sel) -> R
where T: Message, R: Any {
let mut receiver = obj as *mut Object;
let nil: *mut Object = ::std::ptr::null_mut();
let ref slot = *runtime::objc_msg_lookup_sender(&mut receiver as *mut *mut Object, sel, nil);
let imp_fn = slot.method;
let imp_fn: unsafe extern fn(*mut Object, Sel $(, $t)*) -> R =
mem::transmute(imp_fn);
let ($($a,)*) = self;
objc_try!({
imp_fn(receiver as *mut Object, sel $(, $a)*)
})
}

#[cfg(not(feature="gnustep"))]
unsafe fn send_super<T, R>(self, obj: *mut T, superclass: &Class, sel: Sel) -> R
where T: Message, R: Any {
let msg_send_fn = msg_send_super_fn::<R>();
Expand All @@ -156,6 +178,20 @@ macro_rules! message_args_impl {
msg_send_fn(&sup, sel $(, $a)*)
})
}

#[cfg(feature="gnustep")]
unsafe fn send_super<T, R>(self, obj: *mut T, superclass: &Class, sel: Sel) -> R
where T: Message, R: Any {
let sup = Super { receiver: obj as *mut Object, superclass: superclass };
let ref slot = *runtime::objc_slot_lookup_super(&sup, sel);
let imp_fn = slot.method;
let imp_fn: unsafe extern fn(*mut Object, Sel $(, $t)*) -> R =
mem::transmute(imp_fn);
let ($($a,)*) = self;
objc_try!({
imp_fn(obj as *mut Object, sel $(, $a)*)
})
}
}
);
}
Expand Down
30 changes: 28 additions & 2 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ pub struct Sel {
ptr: *const c_void,
}


/// A structure describing a safely cacheable method implementation
/// in the GNUstep Objective-C runtime.
#[cfg(feature="gnustep")]
#[repr(C)]
pub struct Slot {
/// The class to which the slot is attached
pub owner: *const Class,
/// The class for which this slot was cached.
pub cached_for: *mut Class,
/// The type signature of the method
pub types: *const c_char,
/// The version of the method. Will change if overriden, invalidating
/// the cache
pub version: c_int,
/// The implementation of the method
pub method: Imp,
/// The associated selector
pub selector: Sel
}

/// A marker type to be embedded into other types just so that they cannot be
/// constructed externally.
enum PrivateMarker { }
Expand Down Expand Up @@ -79,7 +100,7 @@ pub struct Super {
pub type Imp = extern fn(*mut Object, Sel, ...) -> *mut Object;

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

Expand Down Expand Up @@ -123,6 +144,11 @@ extern {
pub fn method_getNumberOfArguments(method: *const Method) -> c_uint;
pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp;
pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method);

#[cfg(feature="gnustep")]
pub fn objc_msg_lookup_sender(receiver: *mut *mut Object, selector: Sel, sender: *mut Object, ...) -> *mut Slot;
#[cfg(feature="gnustep")]
pub fn objc_slot_lookup_super(sup: *const Super, selector: Sel) -> *mut Slot;
}

impl Sel {
Expand Down Expand Up @@ -444,7 +470,7 @@ mod tests {
assert!(method.name().name() == "description");
assert!(method.arguments_count() == 2);
assert!(method.return_type() == <*mut Object>::encode());
assert!(method.argument_type(1).unwrap() == Sel::encode());
assert_eq!(method.argument_type(1).unwrap(), Sel::encode());

let methods = cls.instance_methods();
assert!(methods.len() > 0);
Expand Down
9 changes: 9 additions & 0 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ use id::StrongPtr;
use runtime::{Class, Object, Sel};
use {Encode, Encoding};




#[cfg(feature="gnustep")]
#[link(name = "NSObject", kind = "static")]
extern {
}


pub fn sample_object() -> StrongPtr {
let cls = Class::get("NSObject").unwrap();
unsafe {
Expand Down
21 changes: 21 additions & 0 deletions test_utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "test_ns_object"
version = "0.0.1"
authors = ["Niels Grewe"]

description = "Mock implementation of NSObject for tests"
repository = "http://github.com/SSheldon/rust-objc"
license = "MIT"

build = "build.rs"

[features]
gnustep = [ "gcc" ]

[lib]
name = "test_ns_object"
path = "lib.rs"

[build-dependencies.gcc]
gcc = "0.3"
optional = true
45 changes: 45 additions & 0 deletions test_utils/NSObject.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <objc/runtime.h>
#include <stdint.h>
/**
* This is a mock implementation of NSObject, which will be linked against
* the tests in order to provide a superclass for them.
*/
__attribute__((objc_root_class))
@interface NSObject
{
Class isa;
}
@end

@implementation NSObject

+ (id)alloc
{
return class_createInstance(self, 0);
}

- (id)init
{
return self;
}

- (id)self
{
return self;
}

- (uintptr_t)hash
{
return (uintptr_t)(void*)self;
}

- (void)dealloc
{
object_dispose(self);
}

- (NSObject*)description
{
return nil;
}
@end
23 changes: 23 additions & 0 deletions test_utils/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[cfg(feature="gnustep")]
extern crate gcc;
#[cfg(feature="gnustep")]
use std::path::PathBuf;


#[cfg(not(feature="gnustep"))]
fn compile() {
}

#[cfg(feature="gnustep")]
fn compile() {
gcc::Config::new().flag("-lobjc")
.flag("-fobjc-runtime=gnustep-1.8")
.flag("-fno-objc-legacy-dispatch")
.file("NSObject.m")
.compile("libNSObject.a");
let path = ::std::env::var_os("OUT_DIR").map(PathBuf::from).unwrap();
println!("cargo:rustc-link-search=native={}", path.display());
}
fn main() {
compile();
}
3 changes: 3 additions & 0 deletions test_utils/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#![crate_name = "test_ns_object"]
#![crate_type = "lib"]

0 comments on commit 7a7da42

Please sign in to comment.