Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(WIP): adding uniffi bindings to support android and ios #473

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
1,825 changes: 1,072 additions & 753 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["cli", "grpc", "node", "node-wasm", "proto", "rpc", "types"]
members = ["cli", "grpc", "node", "node-wasm", "node-uniffi", "proto", "rpc", "types"]

[workspace.dependencies]
blockstore = "0.7.1"
Expand All @@ -13,7 +13,7 @@ celestia-types = { version = "0.9.0", path = "types", default-features = false }
tendermint = { version = "0.40.0", default-features = false }
tendermint-proto = "0.40.0"

libp2p = "0.54.0"
libp2p = "0.54.1"
nmt-rs = "0.2.1"
prost = "0.13.3"
prost-build = "0.13.3"
Expand Down
26 changes: 26 additions & 0 deletions node-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "lumina-node-uniffi"
version = "0.1.0"
edition = "2021"
description = "Mobile bindings for Lumina node"

[lib]
crate-type = ["lib", "staticlib", "cdylib"]

[[bin]]
name = "uniffi-bindgen"
path = "./src/bin/uniffi-bindgen.rs"

[dependencies]
lumina-node = { workspace = true, features = ["uniffi"] }
celestia-types.workspace = true
tendermint.workspace = true
libp2p.workspace = true
redb = "2.1.1"
thiserror = "1.0.61"
serde_json = "1.0.64"
uniffi = { version = "0.28.3", features = ["bindgen", "tokio", "cli"] }
tokio = { version = "1.38.0", features = ["macros", "sync"] }

[target.'cfg(target_os = "ios")'.dependencies]
directories = "5.0.1"
21 changes: 21 additions & 0 deletions node-uniffi/build-android.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

cargo build -p lumina-node-uniffi

rustup target add \
aarch64-linux-android \
armv7-linux-androideabi \
x86_64-linux-android \
i686-linux-android

cargo ndk -o ./app/src/main/jniLibs \
--manifest-path ./Cargo.toml \
-t armeabi-v7a \
-t arm64-v8a \
-t x86 \
-t x86_64 \
build --release

cargo run --bin uniffi-bindgen generate --library ../target/debug/liblumina_node_uniffi.dylib --language kotlin --out-dir ./app/src/main/java/tech/forgen/lumina_node_uniffi/rust

echo "Android build complete"
39 changes: 39 additions & 0 deletions node-uniffi/build-ios.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

cargo build

cd ..

for TARGET in \
aarch64-apple-ios \
aarch64-apple-ios-sim
do
rustup target add $TARGET
cargo build --release --target=$TARGET
done

cd node-uniffi

rm -rf ./bindings ./ios
mkdir -p ./bindings
mkdir -p ./ios
mkdir -p ./bindings/Headers

cargo run --bin uniffi-bindgen generate --library ../target/debug/liblumina_node_uniffi.dylib --language swift --out-dir ./bindings

cat "./bindings/lumina_node_uniffiFFI.modulemap" "./bindings/lumina_nodeFFI.modulemap" > "./bindings/Headers/module.modulemap"

cp ./bindings/*.h ./bindings/Headers/

rm -rf "ios/lumina.xcframework"

xcodebuild -create-xcframework \
-library ../target/aarch64-apple-ios-sim/release/liblumina_node_uniffi.a -headers ./bindings/Headers \
-library ../target/aarch64-apple-ios/release/liblumina_node_uniffi.a -headers ./bindings/Headers \
-output "ios/lumina.xcframework"

cp ./bindings/*.swift ./ios/

rm -rf bindings

echo "iOS build complete"
8 changes: 8 additions & 0 deletions node-uniffi/src/bin/uniffi-bindgen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Binary executable for generating UniFFI bindings.
//!
//! This binary is used by the build system to generate language bindings.

/// This is the entry point for the uniffi-bindgen binary.
fn main() {
uniffi::uniffi_bindgen_main()
}
90 changes: 90 additions & 0 deletions node-uniffi/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use lumina_node::NodeError;
use thiserror::Error;

/// Result type alias for LuminaNode operations that can fail with a LuminaError
pub type Result<T> = std::result::Result<T, LuminaError>;

/// Represents all possible errors that can occur in the LuminaNode.
#[derive(Error, Debug, uniffi::Error)]
pub enum LuminaError {
/// Error returned when trying to perform operations on a node that isn't running
#[error("Node is not running")]
NodeNotRunning,

/// Error returned when network operations fail
#[error("Network error: {msg}")]
Network {
/// Description of the network error
msg: String,
},

/// Error returned when storage operations fail
#[error("Storage error: {msg}")]
Storage {
/// Description of the storage error
msg: String,
},

/// Error returned when trying to start a node that's already running
#[error("Node is already running")]
AlreadyRunning,

/// Error returned when a mutex lock operation fails
#[error("Lock error")]
Lock,

/// Error returned when a hash string is invalid or malformed
#[error("Invalid hash format: {msg}")]
InvalidHash {
/// Description of why the hash is invalid
msg: String,
},

/// Error returned when a header is invalid or malformed
#[error("Invalid header format: {msg}")]
InvalidHeader {
/// Description of why the header is invalid
msg: String,
},

/// Error returned when storage initialization fails
#[error("Storage initialization failed: {msg}")]
StorageInit {
/// Description of why storage initialization failed
msg: String,
},
}

impl LuminaError {
pub fn network(msg: impl Into<String>) -> Self {
Self::Network { msg: msg.into() }
}

pub fn storage(msg: impl Into<String>) -> Self {
Self::Storage { msg: msg.into() }
}

pub fn invalid_hash(msg: impl Into<String>) -> Self {
Self::InvalidHash { msg: msg.into() }
}

pub fn invalid_header(msg: impl Into<String>) -> Self {
Self::InvalidHeader { msg: msg.into() }
}

pub fn storage_init(msg: impl Into<String>) -> Self {
Self::StorageInit { msg: msg.into() }
}
}

impl From<NodeError> for LuminaError {
fn from(error: NodeError) -> Self {
LuminaError::network(error.to_string())
}
}

impl From<libp2p::multiaddr::Error> for LuminaError {
fn from(e: libp2p::multiaddr::Error) -> Self {
LuminaError::network(format!("Invalid multiaddr: {}", e))
}
}
Loading
Loading