Skip to content

Commit 4060608

Browse files
authored
Merge pull request #49 from madsmtm/block-sys
Add sys crate for blocks (`objc2_block_sys`) and make blocks work on GNUStep
2 parents 4dd5852 + bf9268d commit 4060608

File tree

17 files changed

+691
-63
lines changed

17 files changed

+691
-63
lines changed

.github/workflows/gnustep.yml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,17 @@ jobs:
101101
uses: actions-rs/cargo@v1
102102
with:
103103
command: check
104-
args: --verbose --no-default-features
104+
args: --verbose --no-default-features --features gnustep-1-9
105105

106-
- name: Test GNUStep
106+
- name: Test without features
107107
uses: actions-rs/cargo@v1
108108
with:
109109
command: test
110-
# Temporary fix
111-
args: --verbose --no-fail-fast --no-default-features --package objc2_sys --package objc2 --package objc2_encode --package objc2_exception --package objc2_foundation
110+
args: --verbose --no-fail-fast --no-default-features --features gnustep-1-9
112111

113-
- name: Test GNUStep with features
112+
- name: Test with features
114113
uses: actions-rs/cargo@v1
115114
with:
116115
command: test
117-
# Temporary fix
118-
args: --verbose --no-fail-fast --no-default-features --features exception,verify_message --package objc2_sys --package objc2 --package objc2_encode --package objc2_exception --package objc2_foundation
116+
# Not using --all-features because some features are nightly-only
117+
args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,verify_message

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
members = [
33
"objc2",
44
"objc2_block",
5+
"objc2_block_sys",
56
"objc2_encode",
67
"objc2_exception",
78
"objc2_foundation",

objc2/src/lib.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ extern crate std;
7575
#[doc = include_str!("../README.md")]
7676
extern "C" {}
7777

78-
#[cfg(doctest)]
79-
#[doc = include_str!("../../README.md")]
80-
extern "C" {}
81-
8278
pub use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
8379

8480
pub use crate::message::{Message, MessageArguments, MessageError, MessageReceiver};

objc2_block/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ exclude = [
2323

2424
[dependencies]
2525
objc2_encode = { path = "../objc2_encode" }
26+
objc2_block_sys = { path = "../objc2_block_sys" }
2627

2728
[dev-dependencies]
2829
objc2_test_utils = { path = "../objc2_test_utils" }

objc2_block/src/lib.rs

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -55,34 +55,16 @@ extern crate std;
5555
#[cfg(test)]
5656
mod test_utils;
5757

58-
use alloc::boxed::Box;
5958
use core::ffi::c_void;
6059
use core::marker::PhantomData;
6160
use core::mem;
6261
use core::ops::{Deref, DerefMut};
6362
use core::ptr;
6463
use std::os::raw::{c_int, c_ulong};
6564

65+
pub use objc2_block_sys as ffi;
6666
use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
6767

68-
// TODO: Replace with `objc2::Class`
69-
#[repr(C)]
70-
struct ClassInternal {
71-
_priv: [u8; 0],
72-
}
73-
74-
#[cfg_attr(target_vendor = "apple", link(name = "System", kind = "dylib"))]
75-
#[cfg_attr(
76-
not(target_vendor = "apple"),
77-
link(name = "BlocksRuntime", kind = "dylib")
78-
)]
79-
extern "C" {
80-
static _NSConcreteStackBlock: ClassInternal;
81-
82-
fn _Block_copy(block: *const c_void) -> *mut c_void;
83-
fn _Block_release(block: *const c_void);
84-
}
85-
8668
/// Types that may be used as the arguments to an Objective-C block.
8769
pub trait BlockArguments: Sized {
8870
/// Calls the given `Block` with self as the arguments.
@@ -151,7 +133,7 @@ block_args_impl!(
151133

152134
#[repr(C)]
153135
struct BlockBase<A, R> {
154-
isa: *const ClassInternal,
136+
isa: *const ffi::Class,
155137
flags: c_int,
156138
_reserved: c_int,
157139
invoke: unsafe extern "C" fn(*mut Block<A, R>, ...) -> R,
@@ -207,7 +189,7 @@ impl<A, R> RcBlock<A, R> {
207189
///
208190
/// The given pointer must point to a valid `Block`.
209191
pub unsafe fn copy(ptr: *mut Block<A, R>) -> Self {
210-
let ptr = _Block_copy(ptr as *const c_void) as *mut Block<A, R>;
192+
let ptr = ffi::_Block_copy(ptr as *const c_void) as *mut Block<A, R>;
211193
RcBlock { ptr }
212194
}
213195
}
@@ -229,7 +211,7 @@ impl<A, R> Deref for RcBlock<A, R> {
229211
impl<A, R> Drop for RcBlock<A, R> {
230212
fn drop(&mut self) {
231213
unsafe {
232-
_Block_release(self.ptr as *const c_void);
214+
ffi::_Block_release(self.ptr as *const c_void);
233215
}
234216
}
235217
}
@@ -366,7 +348,7 @@ concrete_block_impl!(
366348
#[repr(C)]
367349
pub struct ConcreteBlock<A, R, F> {
368350
base: BlockBase<A, R>,
369-
descriptor: Box<BlockDescriptor<ConcreteBlock<A, R, F>>>,
351+
descriptor: *const BlockDescriptor<ConcreteBlock<A, R, F>>,
370352
closure: F,
371353
}
372354

@@ -391,19 +373,25 @@ where
391373
}
392374

393375
impl<A, R, F> ConcreteBlock<A, R, F> {
376+
const DESCRIPTOR: BlockDescriptor<Self> = BlockDescriptor {
377+
_reserved: 0,
378+
block_size: mem::size_of::<Self>() as c_ulong,
379+
copy_helper: block_context_copy::<Self>,
380+
dispose_helper: block_context_dispose::<Self>,
381+
};
382+
394383
/// Constructs a `ConcreteBlock` with the given invoke function and closure.
395384
/// Unsafe because the caller must ensure the invoke function takes the
396385
/// correct arguments.
397386
unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self {
398387
ConcreteBlock {
399388
base: BlockBase {
400-
isa: &_NSConcreteStackBlock,
401-
// 1 << 25 = BLOCK_HAS_COPY_DISPOSE
402-
flags: 1 << 25,
389+
isa: &ffi::_NSConcreteStackBlock,
390+
flags: ffi::BLOCK_HAS_COPY_DISPOSE,
403391
_reserved: 0,
404392
invoke: mem::transmute(invoke),
405393
},
406-
descriptor: Box::new(BlockDescriptor::new()),
394+
descriptor: &Self::DESCRIPTOR,
407395
closure,
408396
}
409397
}
@@ -469,17 +457,6 @@ struct BlockDescriptor<B> {
469457
dispose_helper: unsafe extern "C" fn(&mut B),
470458
}
471459

472-
impl<B> BlockDescriptor<B> {
473-
fn new() -> BlockDescriptor<B> {
474-
BlockDescriptor {
475-
_reserved: 0,
476-
block_size: mem::size_of::<B>() as c_ulong,
477-
copy_helper: block_context_copy::<B>,
478-
dispose_helper: block_context_dispose::<B>,
479-
}
480-
}
481-
}
482-
483460
#[cfg(test)]
484461
mod tests {
485462
use super::{ConcreteBlock, RcBlock};

objc2_block_sys/Cargo.toml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[package]
2+
name = "objc2_block_sys"
3+
version = "0.0.0" # Remember to update html_root_url in lib.rs
4+
authors = ["Mads Marquart <[email protected]>"]
5+
edition = "2018"
6+
7+
description = "Raw bindings to Apple's C language extension of blocks"
8+
keywords = ["objective-c", "macos", "ios", "blocks", "sys"]
9+
categories = [
10+
"external-ffi-bindings",
11+
# "no_std", # TODO
12+
"os::macos-apis",
13+
]
14+
repository = "https://github.com/madsmtm/objc2"
15+
documentation = "https://docs.rs/objc2_block_sys/"
16+
license = "MIT"
17+
18+
readme = "README.md"
19+
20+
# Downstream users can customize the linking!
21+
# See https://doc.rust-lang.org/cargo/reference/build-scripts.html#overriding-build-scripts
22+
links = "block"
23+
build = "build.rs"
24+
25+
[features]
26+
# Link to Apple's libclosure (exists in libSystem)
27+
#
28+
# This is the default on Apple platforms
29+
apple = []
30+
31+
# Link to libBlocksRuntime from compiler-rt
32+
#
33+
# This is the default on non-Apple platforms
34+
compiler-rt = []
35+
36+
# Link to GNUStep's libobjc2 (which contains the block implementation)
37+
gnustep-1-7 = ["objc2_sys"]
38+
gnustep-1-8 = ["gnustep-1-7"]
39+
gnustep-1-9 = ["gnustep-1-8"]
40+
gnustep-2-0 = ["gnustep-1-9"]
41+
gnustep-2-1 = ["gnustep-2-0"]
42+
43+
# Link to Microsoft's libobjc2
44+
winobjc = ["gnustep-1-8"]
45+
46+
# TODO
47+
objfw = []
48+
49+
[dependencies]
50+
objc2_sys = { path = "../objc2_sys", optional = true }

objc2_block_sys/README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# `objc2_block_sys`
2+
3+
[![Latest version](https://badgen.net/crates/v/objc2_block_sys)](https://crates.io/crates/objc2_block_sys)
4+
[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt)
5+
[![Documentation](https://docs.rs/objc2_block_sys/badge.svg)](https://docs.rs/objc2_block_sys/)
6+
[![Apple CI](https://github.com/madsmtm/objc2/actions/workflows/apple.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/apple.yml)
7+
[![GNUStep CI](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml)
8+
9+
Raw Rust bindings to Apple's C language extension of blocks
10+
11+
## Runtime Support
12+
13+
This library is basically just a raw interface to the aptly specified [Blocks
14+
ABI](https://clang.llvm.org/docs/Block-ABI-Apple.html). However, different
15+
runtime implementations exist and act in slightly different ways (and have
16+
several different helper functions), the most important aspect being that the
17+
libraries are named differently, so the linking must take that into account.
18+
19+
The user can choose the desired runtime by using the relevant cargo feature
20+
flags, see the following sections:
21+
22+
23+
### Apple's [`libclosure`](https://opensource.apple.com/source/libclosure/)
24+
25+
- Feature flag: `apple`.
26+
27+
This is naturally the most sophisticated runtime, and it has quite a lot more
28+
features than the specification mandates. This is used by default on Apple
29+
platforms when no feature flags are specified.
30+
31+
The minimum required operating system versions are as follows:
32+
- macOS: `10.6`
33+
- iOS: `3.2`
34+
- tvOS: Unknown
35+
- watchOS: Unknown
36+
37+
Though in practice Rust itself requires higher versions than this.
38+
39+
A git mirror of the sources is available [here](https://github.com/madsmtm/libclosure).
40+
41+
42+
### LLVM `compiler-rt`'s [`libBlocksRuntime`](https://github.com/llvm/llvm-project/tree/release/13.x/compiler-rt/lib/BlocksRuntime)
43+
44+
- Feature flag: `compiler-rt`.
45+
46+
This is the default runtime on all non-Apple platforms when no feature flags
47+
are specified.
48+
49+
This is effectively just a copy of Apple's older (around macOS 10.6) runtime,
50+
and is now used in [Swift's `libdispatch`] and [Swift's Foundation] as well.
51+
52+
This can be easily used on many Linux systems with the `libblocksruntime-dev`
53+
package.
54+
55+
[Swift's `libdispatch`]: https://github.com/apple/swift-corelibs-libdispatch/tree/swift-5.5.1-RELEASE/src/BlocksRuntime
56+
[Swift's Foundation]: https://github.com/apple/swift-corelibs-foundation/tree/swift-5.5.1-RELEASE/Sources/BlocksRuntime
57+
58+
59+
### GNUStep's [`libobjc2`](https://github.com/gnustep/libobjc2)
60+
61+
- Feature flag: `gnustep-1-7`, `gnustep-1-8`, `gnustep-1-9`, `gnustep-2-0` and
62+
`gnustep-2-1` depending on the version you're using.
63+
64+
GNUStep is a bit odd, because it bundles blocks support into its Objective-C
65+
runtime. This means we have to link to `libobjc`, and this is done by
66+
depending on the `objc2_sys` crate. A bit unorthodox, yes, but it works.
67+
68+
Sources:
69+
- [`Block.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_runtime.h)
70+
- [`Block_private.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_private.h)
71+
72+
73+
### Microsoft's [`WinObjC`](https://github.com/microsoft/WinObjC)
74+
75+
- Feature flag: `winobjc`.
76+
77+
Essentially just [a fork](https://github.com/microsoft/libobjc2) based on
78+
GNUStep's `libobjc2` version 1.8.
79+
80+
81+
### [`ObjFW`](https://github.com/ObjFW/ObjFW) (WIP)
82+
83+
- Feature flag: `objfw`.
84+
85+
TODO.
86+
87+
88+
## C Compiler configuration
89+
90+
To our knowledge, currently only `clang` supports the [Language Specification
91+
for Blocks][block-lang]. To assist in compiling C (or Objective-C) sources
92+
using these features, this crate's build script expose the `DEP_BLOCK_CC_ARGS`
93+
environment variable to downstream build scripts.
94+
95+
Example usage in your `build.rs` (using the `cc` crate) would be as follows:
96+
97+
```rust , ignore
98+
fn main() {
99+
let mut builder = cc::Build::new();
100+
builder.compiler("clang");
101+
builder.file("my_script_using_blocks.c");
102+
103+
for flag in std::env::var("DEP_BLOCK_CC_ARGS").unwrap().split(' ') {
104+
builder.flag(flag);
105+
}
106+
107+
builder.compile("libmy_script_using_blocks.a");
108+
}
109+
```
110+
111+
[block-lang]: https://clang.llvm.org/docs/BlockLanguageSpec.html

objc2_block_sys/build.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use std::env;
2+
use std::path::Path;
3+
4+
fn main() {
5+
// Only rerun if this file changes; the script doesn't depend on our code
6+
println!("cargo:rerun-if-changed=build.rs");
7+
8+
let mut apple = env::var_os("CARGO_FEATURE_APPLE").is_some();
9+
let mut compiler_rt = env::var_os("CARGO_FEATURE_COMPILER_RT").is_some();
10+
let gnustep = env::var_os("CARGO_FEATURE_GNUSTEP_1_7").is_some();
11+
let objfw = env::var_os("CARGO_FEATURE_OBJFW").is_some();
12+
13+
if let (false, false, false, false) = (apple, compiler_rt, gnustep, objfw) {
14+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
15+
if let "macos" | "ios" | "tvos" | "watchos" = &*target_os {
16+
apple = true;
17+
// Add cheaty #[cfg(feature = "apple")] directive
18+
println!("cargo:rustc-cfg=feature=\"apple\"");
19+
} else {
20+
compiler_rt = true;
21+
// Add cheaty #[cfg(feature = "compiler-rt")] directive
22+
println!("cargo:rustc-cfg=feature=\"compiler-rt\"");
23+
}
24+
}
25+
26+
let mut cc_args = "-fblocks".to_owned();
27+
28+
match (apple, compiler_rt, gnustep, objfw) {
29+
(true, false, false, false) => {
30+
// Link to libclosure (internally called libsystem_blocks), which is
31+
// exported by libSystem.dylib.
32+
//
33+
// Note that System.framework is just a deprecated wrapper over the
34+
// dynamic library.
35+
println!("cargo:rustc-link-lib=dylib=System");
36+
// Alternative: Only link to libsystem_blocks.dylib
37+
// println!("cargo:rustc-link-search=native=/usr/lib/system");
38+
// println!("cargo:rustc-link-lib=dylib=system_blocks");
39+
}
40+
(false, true, false, false) => {
41+
println!("cargo:rustc-link-lib=dylib=BlocksRuntime");
42+
}
43+
(false, false, true, false) => {
44+
// Don't link to anything; objc2_sys already does that for us!
45+
46+
// Add GNUStep compability headers to make `#include <Block.h>`
47+
// work (on newer GNUStep versions these headers are present)
48+
if !env::var_os("CARGO_FEATURE_GNUSTEP_2_0").is_some() {
49+
let compat_headers =
50+
Path::new(env!("CARGO_MANIFEST_DIR")).join("gnustep-compat-headers");
51+
cc_args.push_str(" -I");
52+
cc_args.push_str(compat_headers.to_str().unwrap());
53+
}
54+
}
55+
(false, false, false, true) => unimplemented!(),
56+
// Checked in if-let above
57+
(false, false, false, false) => unreachable!(),
58+
(_, _, _, _) => panic!("Invalid feature combination; only one runtime may be selected!"),
59+
}
60+
61+
// Add DEP_BLOCK_CC_ARGS
62+
println!("cargo:cc_args={}", cc_args);
63+
}

0 commit comments

Comments
 (0)