Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/gnustep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,17 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: check
args: --verbose --no-default-features
args: --verbose --no-default-features --features gnustep-1-9

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

- name: Test GNUStep with features
- name: Test with features
uses: actions-rs/cargo@v1
with:
command: test
# Temporary fix
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
# Not using --all-features because some features are nightly-only
args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,verify_message
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"objc2",
"objc2_block",
"objc2_block_sys",
"objc2_encode",
"objc2_exception",
"objc2_foundation",
Expand Down
4 changes: 0 additions & 4 deletions objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ extern crate std;
#[doc = include_str!("../README.md")]
extern "C" {}

#[cfg(doctest)]
#[doc = include_str!("../../README.md")]
extern "C" {}

pub use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};

pub use crate::message::{Message, MessageArguments, MessageError, MessageReceiver};
Expand Down
1 change: 1 addition & 0 deletions objc2_block/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ exclude = [

[dependencies]
objc2_encode = { path = "../objc2_encode" }
objc2_block_sys = { path = "../objc2_block_sys" }

[dev-dependencies]
objc2_test_utils = { path = "../objc2_test_utils" }
53 changes: 15 additions & 38 deletions objc2_block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,34 +55,16 @@ extern crate std;
#[cfg(test)]
mod test_utils;

use alloc::boxed::Box;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::mem;
use core::ops::{Deref, DerefMut};
use core::ptr;
use std::os::raw::{c_int, c_ulong};

pub use objc2_block_sys as ffi;
use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};

// TODO: Replace with `objc2::Class`
#[repr(C)]
struct ClassInternal {
_priv: [u8; 0],
}

#[cfg_attr(target_vendor = "apple", link(name = "System", kind = "dylib"))]
#[cfg_attr(
not(target_vendor = "apple"),
link(name = "BlocksRuntime", kind = "dylib")
)]
extern "C" {
static _NSConcreteStackBlock: ClassInternal;

fn _Block_copy(block: *const c_void) -> *mut c_void;
fn _Block_release(block: *const c_void);
}

/// Types that may be used as the arguments to an Objective-C block.
pub trait BlockArguments: Sized {
/// Calls the given `Block` with self as the arguments.
Expand Down Expand Up @@ -151,7 +133,7 @@ block_args_impl!(

#[repr(C)]
struct BlockBase<A, R> {
isa: *const ClassInternal,
isa: *const ffi::Class,
flags: c_int,
_reserved: c_int,
invoke: unsafe extern "C" fn(*mut Block<A, R>, ...) -> R,
Expand Down Expand Up @@ -207,7 +189,7 @@ impl<A, R> RcBlock<A, R> {
///
/// The given pointer must point to a valid `Block`.
pub unsafe fn copy(ptr: *mut Block<A, R>) -> Self {
let ptr = _Block_copy(ptr as *const c_void) as *mut Block<A, R>;
let ptr = ffi::_Block_copy(ptr as *const c_void) as *mut Block<A, R>;
RcBlock { ptr }
}
}
Expand All @@ -229,7 +211,7 @@ impl<A, R> Deref for RcBlock<A, R> {
impl<A, R> Drop for RcBlock<A, R> {
fn drop(&mut self) {
unsafe {
_Block_release(self.ptr as *const c_void);
ffi::_Block_release(self.ptr as *const c_void);
}
}
}
Expand Down Expand Up @@ -366,7 +348,7 @@ concrete_block_impl!(
#[repr(C)]
pub struct ConcreteBlock<A, R, F> {
base: BlockBase<A, R>,
descriptor: Box<BlockDescriptor<ConcreteBlock<A, R, F>>>,
descriptor: *const BlockDescriptor<ConcreteBlock<A, R, F>>,
closure: F,
}

Expand All @@ -391,19 +373,25 @@ where
}

impl<A, R, F> ConcreteBlock<A, R, F> {
const DESCRIPTOR: BlockDescriptor<Self> = BlockDescriptor {
_reserved: 0,
block_size: mem::size_of::<Self>() as c_ulong,
copy_helper: block_context_copy::<Self>,
dispose_helper: block_context_dispose::<Self>,
};

/// Constructs a `ConcreteBlock` with the given invoke function and closure.
/// Unsafe because the caller must ensure the invoke function takes the
/// correct arguments.
unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self {
ConcreteBlock {
base: BlockBase {
isa: &_NSConcreteStackBlock,
// 1 << 25 = BLOCK_HAS_COPY_DISPOSE
flags: 1 << 25,
isa: &ffi::_NSConcreteStackBlock,
flags: ffi::BLOCK_HAS_COPY_DISPOSE,
_reserved: 0,
invoke: mem::transmute(invoke),
},
descriptor: Box::new(BlockDescriptor::new()),
descriptor: &Self::DESCRIPTOR,
closure,
}
}
Expand Down Expand Up @@ -469,17 +457,6 @@ struct BlockDescriptor<B> {
dispose_helper: unsafe extern "C" fn(&mut B),
}

impl<B> BlockDescriptor<B> {
fn new() -> BlockDescriptor<B> {
BlockDescriptor {
_reserved: 0,
block_size: mem::size_of::<B>() as c_ulong,
copy_helper: block_context_copy::<B>,
dispose_helper: block_context_dispose::<B>,
}
}
}

#[cfg(test)]
mod tests {
use super::{ConcreteBlock, RcBlock};
Expand Down
50 changes: 50 additions & 0 deletions objc2_block_sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[package]
name = "objc2_block_sys"
version = "0.0.0" # Remember to update html_root_url in lib.rs
authors = ["Mads Marquart <mads@marquart.dk>"]
edition = "2018"

description = "Raw bindings to Apple's C language extension of blocks"
keywords = ["objective-c", "macos", "ios", "blocks", "sys"]
categories = [
"external-ffi-bindings",
# "no_std", # TODO
"os::macos-apis",
]
repository = "https://github.com/madsmtm/objc2"
documentation = "https://docs.rs/objc2_block_sys/"
license = "MIT"

readme = "README.md"

# Downstream users can customize the linking!
# See https://doc.rust-lang.org/cargo/reference/build-scripts.html#overriding-build-scripts
links = "block"
build = "build.rs"

[features]
# Link to Apple's libclosure (exists in libSystem)
#
# This is the default on Apple platforms
apple = []

# Link to libBlocksRuntime from compiler-rt
#
# This is the default on non-Apple platforms
compiler-rt = []

# Link to GNUStep's libobjc2 (which contains the block implementation)
gnustep-1-7 = ["objc2_sys"]
gnustep-1-8 = ["gnustep-1-7"]
gnustep-1-9 = ["gnustep-1-8"]
gnustep-2-0 = ["gnustep-1-9"]
gnustep-2-1 = ["gnustep-2-0"]

# Link to Microsoft's libobjc2
winobjc = ["gnustep-1-8"]

# TODO
objfw = []

[dependencies]
objc2_sys = { path = "../objc2_sys", optional = true }
111 changes: 111 additions & 0 deletions objc2_block_sys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# `objc2_block_sys`

[![Latest version](https://badgen.net/crates/v/objc2_block_sys)](https://crates.io/crates/objc2_block_sys)
[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt)
[![Documentation](https://docs.rs/objc2_block_sys/badge.svg)](https://docs.rs/objc2_block_sys/)
[![Apple CI](https://github.com/madsmtm/objc2/actions/workflows/apple.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/apple.yml)
[![GNUStep CI](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml)

Raw Rust bindings to Apple's C language extension of blocks

## Runtime Support

This library is basically just a raw interface to the aptly specified [Blocks
ABI](https://clang.llvm.org/docs/Block-ABI-Apple.html). However, different
runtime implementations exist and act in slightly different ways (and have
several different helper functions), the most important aspect being that the
libraries are named differently, so the linking must take that into account.

The user can choose the desired runtime by using the relevant cargo feature
flags, see the following sections:


### Apple's [`libclosure`](https://opensource.apple.com/source/libclosure/)

- Feature flag: `apple`.

This is naturally the most sophisticated runtime, and it has quite a lot more
features than the specification mandates. This is used by default on Apple
platforms when no feature flags are specified.

The minimum required operating system versions are as follows:
- macOS: `10.6`
- iOS: `3.2`
- tvOS: Unknown
- watchOS: Unknown

Though in practice Rust itself requires higher versions than this.

A git mirror of the sources is available [here](https://github.com/madsmtm/libclosure).


### LLVM `compiler-rt`'s [`libBlocksRuntime`](https://github.com/llvm/llvm-project/tree/release/13.x/compiler-rt/lib/BlocksRuntime)

- Feature flag: `compiler-rt`.

This is the default runtime on all non-Apple platforms when no feature flags
are specified.

This is effectively just a copy of Apple's older (around macOS 10.6) runtime,
and is now used in [Swift's `libdispatch`] and [Swift's Foundation] as well.

This can be easily used on many Linux systems with the `libblocksruntime-dev`
package.

[Swift's `libdispatch`]: https://github.com/apple/swift-corelibs-libdispatch/tree/swift-5.5.1-RELEASE/src/BlocksRuntime
[Swift's Foundation]: https://github.com/apple/swift-corelibs-foundation/tree/swift-5.5.1-RELEASE/Sources/BlocksRuntime


### GNUStep's [`libobjc2`](https://github.com/gnustep/libobjc2)

- Feature flag: `gnustep-1-7`, `gnustep-1-8`, `gnustep-1-9`, `gnustep-2-0` and
`gnustep-2-1` depending on the version you're using.

GNUStep is a bit odd, because it bundles blocks support into its Objective-C
runtime. This means we have to link to `libobjc`, and this is done by
depending on the `objc2_sys` crate. A bit unorthodox, yes, but it works.

Sources:
- [`Block.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_runtime.h)
- [`Block_private.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_private.h)


### Microsoft's [`WinObjC`](https://github.com/microsoft/WinObjC)

- Feature flag: `winobjc`.

Essentially just [a fork](https://github.com/microsoft/libobjc2) based on
GNUStep's `libobjc2` version 1.8.


### [`ObjFW`](https://github.com/ObjFW/ObjFW) (WIP)

- Feature flag: `objfw`.

TODO.


## C Compiler configuration

To our knowledge, currently only `clang` supports the [Language Specification
for Blocks][block-lang]. To assist in compiling C (or Objective-C) sources
using these features, this crate's build script expose the `DEP_BLOCK_CC_ARGS`
environment variable to downstream build scripts.

Example usage in your `build.rs` (using the `cc` crate) would be as follows:

```rust , ignore
fn main() {
let mut builder = cc::Build::new();
builder.compiler("clang");
builder.file("my_script_using_blocks.c");

for flag in std::env::var("DEP_BLOCK_CC_ARGS").unwrap().split(' ') {
builder.flag(flag);
}

builder.compile("libmy_script_using_blocks.a");
}
```

[block-lang]: https://clang.llvm.org/docs/BlockLanguageSpec.html
63 changes: 63 additions & 0 deletions objc2_block_sys/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::env;
use std::path::Path;

fn main() {
// Only rerun if this file changes; the script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");

let mut apple = env::var_os("CARGO_FEATURE_APPLE").is_some();
let mut compiler_rt = env::var_os("CARGO_FEATURE_COMPILER_RT").is_some();
let gnustep = env::var_os("CARGO_FEATURE_GNUSTEP_1_7").is_some();
let objfw = env::var_os("CARGO_FEATURE_OBJFW").is_some();

if let (false, false, false, false) = (apple, compiler_rt, gnustep, objfw) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if let "macos" | "ios" | "tvos" | "watchos" = &*target_os {
apple = true;
// Add cheaty #[cfg(feature = "apple")] directive
println!("cargo:rustc-cfg=feature=\"apple\"");
} else {
compiler_rt = true;
// Add cheaty #[cfg(feature = "compiler-rt")] directive
println!("cargo:rustc-cfg=feature=\"compiler-rt\"");
}
}

let mut cc_args = "-fblocks".to_owned();

match (apple, compiler_rt, gnustep, objfw) {
(true, false, false, false) => {
// Link to libclosure (internally called libsystem_blocks), which is
// exported by libSystem.dylib.
//
// Note that System.framework is just a deprecated wrapper over the
// dynamic library.
println!("cargo:rustc-link-lib=dylib=System");
// Alternative: Only link to libsystem_blocks.dylib
// println!("cargo:rustc-link-search=native=/usr/lib/system");
// println!("cargo:rustc-link-lib=dylib=system_blocks");
}
(false, true, false, false) => {
println!("cargo:rustc-link-lib=dylib=BlocksRuntime");
}
(false, false, true, false) => {
// Don't link to anything; objc2_sys already does that for us!

// Add GNUStep compability headers to make `#include <Block.h>`
// work (on newer GNUStep versions these headers are present)
if !env::var_os("CARGO_FEATURE_GNUSTEP_2_0").is_some() {
let compat_headers =
Path::new(env!("CARGO_MANIFEST_DIR")).join("gnustep-compat-headers");
cc_args.push_str(" -I");
cc_args.push_str(compat_headers.to_str().unwrap());
}
}
(false, false, false, true) => unimplemented!(),
// Checked in if-let above
(false, false, false, false) => unreachable!(),
(_, _, _, _) => panic!("Invalid feature combination; only one runtime may be selected!"),
}

// Add DEP_BLOCK_CC_ARGS
println!("cargo:cc_args={}", cc_args);
}
Loading