diff --git a/.travis.yml b/.travis.yml index 0b833aa65..cb1d5c06f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index eed7ea262..84ee9560a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ 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" @@ -22,3 +23,7 @@ malloc_buf = "0.0" [dependencies.objc_exception] version = "0.1" optional = true + +[dev-dependencies.test_ns_object] +version = "0.0" +path = "test_utils" diff --git a/README.md b/README.md index cd261fa14..42d540557 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/src/declare.rs b/src/declare.rs index b620ee4a6..4133fd35d 100644 --- a/src/declare.rs +++ b/src/declare.rs @@ -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}; diff --git a/src/lib.rs b/src/lib.rs index a3bca51c4..d34ba055f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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() { diff --git a/src/message.rs b/src/message.rs index 7b5210fca..32be698f1 100644 --- a/src/message.rs +++ b/src/message.rs @@ -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. @@ -31,7 +30,7 @@ fn msg_send_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { } } -#[cfg(target_arch = "x86")] +#[cfg(all(target_arch = "x86", not(feature = "gnustep")))] fn msg_send_super_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { let size = mem::size_of::(); if size == 0 || size == 1 || size == 2 || size == 4 || size == 8 { @@ -55,7 +54,7 @@ fn msg_send_fn() -> 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() -> unsafe extern fn(*const Super, Sel, ...) -> R { if mem::size_of::() <= 16 { unsafe { mem::transmute(runtime::objc_msgSendSuper) } @@ -83,7 +82,7 @@ fn msg_send_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { } } -#[cfg(target_arch = "arm")] +#[cfg(all(target_arch = "arm", not(feature = "gnustep")))] fn msg_send_super_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { use std::any::TypeId; @@ -98,7 +97,7 @@ fn msg_send_super_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { } } -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_arch = "aarch64", not(feature = "gnustep")))] fn msg_send_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { // stret is not even available in arm64. // https://twitter.com/gparker/status/378079715824660480 @@ -106,7 +105,7 @@ fn msg_send_fn() -> unsafe extern fn(*mut Object, Sel, ...) -> R { unsafe { mem::transmute(runtime::objc_msgSend) } } -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_arch = "aarch64", not(feature ="gnustep")))] fn msg_send_super_fn() -> unsafe extern fn(*const Super, Sel, ...) -> R { unsafe { mem::transmute(runtime::objc_msgSendSuper) } } @@ -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(self, obj: *mut T, sel: Sel) -> R where T: Message, R: Any { let msg_send_fn = msg_send_fn::(); @@ -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(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(self, obj: *mut T, superclass: &Class, sel: Sel) -> R where T: Message, R: Any { let msg_send_fn = msg_send_super_fn::(); @@ -156,6 +178,20 @@ macro_rules! message_args_impl { msg_send_fn(&sup, sel $(, $a)*) }) } + + #[cfg(feature="gnustep")] + unsafe fn send_super(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)*) + }) + } } ); } diff --git a/src/runtime.rs b/src/runtime.rs index 6f2a905b9..0b6b1e743 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -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 { } @@ -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; @@ -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 { @@ -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); diff --git a/src/test_utils.rs b/src/test_utils.rs index 5522db716..4d16406f4 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -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 { diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml new file mode 100644 index 000000000..076603868 --- /dev/null +++ b/test_utils/Cargo.toml @@ -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 diff --git a/test_utils/NSObject.m b/test_utils/NSObject.m new file mode 100644 index 000000000..ce839784a --- /dev/null +++ b/test_utils/NSObject.m @@ -0,0 +1,45 @@ +#include +#include +/** + * 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 diff --git a/test_utils/build.rs b/test_utils/build.rs new file mode 100644 index 000000000..e5418fc89 --- /dev/null +++ b/test_utils/build.rs @@ -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(); +} diff --git a/test_utils/lib.rs b/test_utils/lib.rs new file mode 100644 index 000000000..418573658 --- /dev/null +++ b/test_utils/lib.rs @@ -0,0 +1,3 @@ +#![crate_name = "test_ns_object"] +#![crate_type = "lib"] +