From 4428666081aa3aecf94cd4a41af35a85bba54b9a Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Wed, 16 Jul 2025 11:34:00 -0400 Subject: [PATCH 1/2] feat!: use target_arch instead of feature flags for wasm vs uniffi Rust features are intended to be purely additive, so this changes aligns with that philosophy. This also makes it a bit more ergonomic to compile the FFI packages since we no longer need to specify features. It should be noted that the sanity script now iterates over each crate individually to run cargo check. Otherwise, feature unification at the workspace level causes problems. I've also temporary switched the usage of RwLock in utils to FfiMutex so it compiles when targeting WASM. We can come back to this later and optimize down the road Use quotes around dir Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/ci_cd.yml | 14 ++---- Cargo.lock | 5 +++ .../templates/base/Cargo.toml.j2 | 2 +- crates/algod_client/Cargo.toml | 6 ++- crates/algokit_http_client/Cargo.toml | 22 ++++------ crates/algokit_http_client/src/lib.rs | 43 +++++++++---------- crates/algokit_transact_ffi/Cargo.toml | 20 ++++----- crates/algokit_transact_ffi/src/lib.rs | 40 ++++++++--------- .../src/transactions/application_call.rs | 6 +-- .../src/transactions/keyreg.rs | 2 +- crates/algokit_utils/Cargo.toml | 4 ++ .../src/clients/client_manager.rs | 14 +++--- crates/ffi_macros/Cargo.toml | 2 +- crates/ffi_macros/src/lib.rs | 24 +++++------ crates/ffi_mutex/Cargo.toml | 14 +++--- crates/ffi_mutex/src/lib.rs | 16 +++---- docs/book/crates/algokit_transact_ffi.md | 9 +--- scripts/sanity.sh | 9 ++-- tools/build_pkgs/src/typescript.rs | 2 +- 19 files changed, 123 insertions(+), 131 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index a459029d..4c4d8b8f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -25,6 +25,7 @@ jobs: ref: ${{ github.ref }} - uses: dtolnay/rust-toolchain@master with: + targets: wasm32-unknown-unknown toolchain: 1.85.0 components: clippy, rustfmt @@ -34,19 +35,12 @@ jobs: - name: Install algokit CLI run: uv tool install algokit - - name: Check formatting - run: cargo fmt --check - - name: Clippy - # Run clippy and treat warnings as errors - run: cargo clippy -- -D warnings - - name: Check - run: cargo check - - name: Check WASM - run: cargo check --no-default-features --features ffi_wasm - - name: Start localnet run: algokit localnet start + - name: Run sanity script + run: bash scripts/sanity.sh + - name: Build all crates run: cargo build --workspace diff --git a/Cargo.lock b/Cargo.lock index 91c4d884..a5d170ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,7 @@ dependencies = [ "algokit_http_client", "algokit_transact", "base64 0.22.1", + "getrandom 0.2.15", "rmp-serde", "serde", "serde_bytes", @@ -129,7 +130,9 @@ dependencies = [ "dotenvy", "ed25519-dalek", "env_logger 0.11.6", + "ffi_mutex", "futures", + "getrandom 0.2.15", "hex", "lazy_static", "log", @@ -1440,8 +1443,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] diff --git a/api/oas_generator/rust_oas_generator/templates/base/Cargo.toml.j2 b/api/oas_generator/rust_oas_generator/templates/base/Cargo.toml.j2 index 1df4249e..55291d60 100644 --- a/api/oas_generator/rust_oas_generator/templates/base/Cargo.toml.j2 +++ b/api/oas_generator/rust_oas_generator/templates/base/Cargo.toml.j2 @@ -23,7 +23,7 @@ serde_repr = "^0.1" serde_bytes = "^0.11" # HTTP client -algokit_http_client = { path = "../algokit_http_client", features = ["ffi_uniffi"] } +algokit_http_client = { path = "../algokit_http_client" } url = "^2.5" {% if spec.has_msgpack_operations %} diff --git a/crates/algod_client/Cargo.toml b/crates/algod_client/Cargo.toml index 6631db55..47a81edb 100644 --- a/crates/algod_client/Cargo.toml +++ b/crates/algod_client/Cargo.toml @@ -23,7 +23,7 @@ serde_repr = "^0.1" serde_bytes = "^0.11" # HTTP client -algokit_http_client = { path = "../algokit_http_client", features = ["ffi_uniffi"] } +algokit_http_client = { path = "../algokit_http_client" } url = "^2.5" # AlgoKit dependencies for msgpack and signed transactions @@ -41,3 +41,7 @@ uuid = { version = "^1.0", features = ["v4"] } [dev-dependencies] tokio = { version = "1.0", features = ["full"] } tokio-test = "^0.4" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2.15", features = ["js"] } + diff --git a/crates/algokit_http_client/Cargo.toml b/crates/algokit_http_client/Cargo.toml index 7fb8d26c..d0a15fb0 100644 --- a/crates/algokit_http_client/Cargo.toml +++ b/crates/algokit_http_client/Cargo.toml @@ -4,24 +4,20 @@ version = "0.1.0" edition = "2024" [features] -default = ["default_client"] -ffi_uniffi = ["dep:uniffi"] -ffi_wasm = [ - "dep:wasm-bindgen", - "dep:js-sys", - "dep:tsify-next", - "dep:serde-wasm-bindgen", -] default_client = ["dep:reqwest"] [dependencies] async-trait = "0.1.88" -js-sys = { workspace = true, optional = true } reqwest = { version = "0.12.19", optional = true } serde = { version = "1.0", features = ["derive"] } -serde-wasm-bindgen = { version = "0.6", optional = true } thiserror.workspace = true -tsify-next = { workspace = true, optional = true } -uniffi = { workspace = true, optional = true } -wasm-bindgen = { workspace = true, optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = { workspace = true } +tsify-next = { workspace = true } +wasm-bindgen = { workspace = true } wasm-bindgen-futures = "0.4.50" +serde-wasm-bindgen = { version = "0.6" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +uniffi = { workspace = true } diff --git a/crates/algokit_http_client/src/lib.rs b/crates/algokit_http_client/src/lib.rs index dadadffa..77113a11 100644 --- a/crates/algokit_http_client/src/lib.rs +++ b/crates/algokit_http_client/src/lib.rs @@ -1,21 +1,21 @@ use async_trait::async_trait; use std::collections::HashMap; -#[cfg(feature = "ffi_uniffi")] +#[cfg(not(target_arch = "wasm32"))] uniffi::setup_scaffolding!(); #[derive(Debug, thiserror::Error)] -#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Error))] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Error))] pub enum HttpError { #[error("HttpError: {0}")] RequestError(String), } #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Enum))] -#[cfg_attr(feature = "ffi_wasm", derive(tsify_next::Tsify))] -#[cfg_attr(feature = "ffi_wasm", tsify(into_wasm_abi, from_wasm_abi))] -#[cfg_attr(feature = "ffi_wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] +#[cfg_attr(target_arch = "wasm32", derive(tsify_next::Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] +#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize, serde::Deserialize))] pub enum HttpMethod { Get, Post, @@ -41,23 +41,20 @@ impl HttpMethod { } #[derive(Debug, Clone)] -#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))] -#[cfg_attr(feature = "ffi_wasm", derive(tsify_next::Tsify))] -#[cfg_attr(feature = "ffi_wasm", tsify(into_wasm_abi, from_wasm_abi))] -#[cfg_attr(feature = "ffi_wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Record))] +#[cfg_attr(target_arch = "wasm32", derive(tsify_next::Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] +#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize, serde::Deserialize))] pub struct HttpResponse { pub body: Vec, pub headers: HashMap, } -#[cfg(not(feature = "ffi_wasm"))] -#[cfg_attr(feature = "ffi_uniffi", uniffi::export(with_foreign))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(not(target_arch = "wasm32"), uniffi::export(with_foreign))] #[async_trait] /// This trait must be implemented by any HTTP client that is used by our Rust crates. /// It is assumed the implementing type will provide the hostname, port, headers, etc. as needed for each request. -/// -/// By default, this trait requires the implementing type to be `Send + Sync`. -/// For WASM targets, enable the `ffi_wasm` feature to use a different implementation that is compatible with WASM. pub trait HttpClient: Send + Sync { async fn request( &self, @@ -110,8 +107,8 @@ impl DefaultHttpClient { } #[cfg(feature = "default_client")] -#[cfg_attr(feature = "ffi_wasm", async_trait(?Send))] -#[cfg_attr(not(feature = "ffi_wasm"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl HttpClient for DefaultHttpClient { async fn request( &self, @@ -178,16 +175,16 @@ impl HttpClient for DefaultHttpClient { } // WASM-specific implementations -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use js_sys::Uint8Array; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use tsify_next::Tsify; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] #[async_trait(?Send)] pub trait HttpClient { async fn request( @@ -201,7 +198,7 @@ pub trait HttpClient { } #[wasm_bindgen] -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] extern "C" { /// The interface for the JavaScript-based HTTP client that will be used in WASM environments. /// @@ -219,7 +216,7 @@ extern "C" { ) -> Result; } -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] #[async_trait(?Send)] impl HttpClient for WasmHttpClient { async fn request( diff --git a/crates/algokit_transact_ffi/Cargo.toml b/crates/algokit_transact_ffi/Cargo.toml index a5a58b5b..00f1bf5e 100644 --- a/crates/algokit_transact_ffi/Cargo.toml +++ b/crates/algokit_transact_ffi/Cargo.toml @@ -6,11 +6,6 @@ edition = "2021" [lib] crate-type = ["lib", "cdylib", "staticlib"] -[features] -default = ["ffi_uniffi"] -ffi_wasm = ["dep:wasm-bindgen", "dep:tsify-next", "dep:js-sys"] -ffi_uniffi = ["dep:uniffi"] - [dependencies] algokit_transact = { path = "../algokit_transact", features = ['test_utils'] } ffi_macros = { path = "../ffi_macros" } @@ -22,14 +17,17 @@ serde_bytes = "0.11.15" serde_json = "1.0.133" base64 = "0.22.1" -tsify-next = { workspace = true, optional = true } -uniffi = { workspace = true, features = [ - "scaffolding-ffi-buffer-fns", -], optional = true } -wasm-bindgen = { workspace = true, optional = true } -js-sys = { workspace = true, optional = true } pretty_assertions = "1.4.1" +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { workspace = true } +tsify-next = { workspace = true } +js-sys = { workspace = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +uniffi = { workspace = true, features = [ + "scaffolding-ffi-buffer-fns", +]} [dev-dependencies] wasm-pack = "0.13.1" diff --git a/crates/algokit_transact_ffi/src/lib.rs b/crates/algokit_transact_ffi/src/lib.rs index 93e37406..f464f5d4 100644 --- a/crates/algokit_transact_ffi/src/lib.rs +++ b/crates/algokit_transact_ffi/src/lib.rs @@ -18,7 +18,7 @@ pub use transactions::KeyRegistrationTransactionFields; // thiserror is used to easily create errors than can be propagated to the language bindings // UniFFI will create classes for errors (i.e. `MsgPackError.EncodingError` in Python) #[derive(Debug, thiserror::Error)] -#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Error))] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Error))] pub enum AlgoKitTransactError { #[error("EncodingError: {0}")] EncodingError(String), @@ -33,7 +33,7 @@ pub enum AlgoKitTransactError { // For now, in WASM we just throw the string, hence the error // type being included in the error string above // Perhaps in the future we could use a class like in UniFFI -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] impl From for JsValue { fn from(e: AlgoKitTransactError) -> Self { JsValue::from(e.to_string()) @@ -72,22 +72,22 @@ impl From for AlgoKitTransactError { } } -#[cfg(feature = "ffi_uniffi")] +#[cfg(not(target_arch = "wasm32"))] use uniffi::{self}; -#[cfg(feature = "ffi_uniffi")] +#[cfg(not(target_arch = "wasm32"))] uniffi::setup_scaffolding!(); -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use js_sys::Uint8Array; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use tsify_next::Tsify; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; // We need to use ByteBuf directly in the structs to get Uint8Array in TSify // custom_type! and this impl is used to convert the ByteBuf to a Vec for the UniFFI bindings -#[cfg(feature = "ffi_uniffi")] +#[cfg(not(target_arch = "wasm32"))] impl UniffiCustomTypeConverter for ByteBuf { type Builtin = Vec; @@ -100,15 +100,15 @@ impl UniffiCustomTypeConverter for ByteBuf { } } -#[cfg(feature = "ffi_uniffi")] +#[cfg(not(target_arch = "wasm32"))] uniffi::custom_type!(ByteBuf, Vec); // This becomes an enum in UniFFI language bindings and a // string literal union in TS #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[cfg_attr(feature = "ffi_wasm", derive(Tsify))] -#[cfg_attr(feature = "ffi_wasm", tsify(into_wasm_abi, from_wasm_abi))] -#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Enum))] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] pub enum TransactionType { Payment, AssetTransfer, @@ -603,7 +603,7 @@ pub fn encode_transaction(tx: Transaction) -> Result, AlgoKitTransactErr /// /// # Returns /// A collection of MsgPack encoded bytes or an error if encoding fails. -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] #[ffi_func] /// Encode transactions with the domain separation (e.g. "TX") prefix pub fn encode_transactions(txs: Vec) -> Result, AlgoKitTransactError> { @@ -619,7 +619,7 @@ pub fn encode_transactions(txs: Vec) -> Result, Alg /// /// # Returns /// A collection of MsgPack encoded bytes or an error if encoding fails. -#[cfg(not(feature = "ffi_wasm"))] +#[cfg(not(target_arch = "wasm32"))] #[ffi_func] pub fn encode_transactions(txs: Vec) -> Result>, AlgoKitTransactError> { txs.into_iter().map(encode_transaction).collect() @@ -653,7 +653,7 @@ pub fn decode_transaction(encoded_tx: &[u8]) -> Result, @@ -671,7 +671,7 @@ pub fn decode_transactions( /// /// # Returns /// A collection of decoded transactions or an error if decoding fails. -#[cfg(not(feature = "ffi_wasm"))] +#[cfg(not(target_arch = "wasm32"))] #[ffi_func] pub fn decode_transactions( encoded_txs: Vec>, @@ -854,7 +854,7 @@ pub fn decode_signed_transaction(bytes: &[u8]) -> Result, @@ -872,7 +872,7 @@ pub fn decode_signed_transactions( /// /// # Returns /// A collection of decoded signed transactions or an error if decoding fails. -#[cfg(not(feature = "ffi_wasm"))] +#[cfg(not(target_arch = "wasm32"))] #[ffi_func] pub fn decode_signed_transactions( encoded_signed_txs: Vec>, @@ -909,7 +909,7 @@ pub fn encode_signed_transaction( /// /// # Returns /// A collection of MsgPack encoded bytes or an error if encoding fails. -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] #[ffi_func] pub fn encode_signed_transactions( signed_txs: Vec, @@ -929,7 +929,7 @@ pub fn encode_signed_transactions( /// /// # Returns /// A collection of MsgPack encoded bytes or an error if encoding fails. -#[cfg(not(feature = "ffi_wasm"))] +#[cfg(not(target_arch = "wasm32"))] #[ffi_func] pub fn encode_signed_transactions( signed_txs: Vec, diff --git a/crates/algokit_transact_ffi/src/transactions/application_call.rs b/crates/algokit_transact_ffi/src/transactions/application_call.rs index 271c2f39..636fbe3d 100644 --- a/crates/algokit_transact_ffi/src/transactions/application_call.rs +++ b/crates/algokit_transact_ffi/src/transactions/application_call.rs @@ -184,9 +184,9 @@ impl From for algokit_transact::BoxReference { /// /// These values define what additional actions occur with the transaction. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[cfg_attr(feature = "ffi_wasm", derive(Tsify))] -#[cfg_attr(feature = "ffi_wasm", tsify(into_wasm_abi, from_wasm_abi))] -#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Enum))] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi))] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] pub enum OnApplicationComplete { /// NoOp indicates that an application transaction will simply call its /// approval program without any additional action. diff --git a/crates/algokit_transact_ffi/src/transactions/keyreg.rs b/crates/algokit_transact_ffi/src/transactions/keyreg.rs index 3fa23848..91f12fbd 100644 --- a/crates/algokit_transact_ffi/src/transactions/keyreg.rs +++ b/crates/algokit_transact_ffi/src/transactions/keyreg.rs @@ -4,7 +4,7 @@ //! transactions that can be used across language bindings. use crate::*; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] use tsify_next::Tsify; #[ffi_record] diff --git a/crates/algokit_utils/Cargo.toml b/crates/algokit_utils/Cargo.toml index 78b82f89..a91c3e2f 100644 --- a/crates/algokit_utils/Cargo.toml +++ b/crates/algokit_utils/Cargo.toml @@ -36,6 +36,10 @@ hex = "0.4.3" rand = "0.8" sha2 = "0.10.8" lazy_static = "1.4" +ffi_mutex = { version = "0.1.0", path = "../ffi_mutex" } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2.15", features = ["js"] } [dev-dependencies] env_logger = "0.11" diff --git a/crates/algokit_utils/src/clients/client_manager.rs b/crates/algokit_utils/src/clients/client_manager.rs index cbab35db..ad2b4cbe 100644 --- a/crates/algokit_utils/src/clients/client_manager.rs +++ b/crates/algokit_utils/src/clients/client_manager.rs @@ -5,19 +5,19 @@ use crate::clients::network_client::{ use algod_client::AlgodClient; use algokit_http_client::DefaultHttpClient; use base64::{Engine, engine::general_purpose}; +use ffi_mutex::FfiMutex; use std::{env, sync::Arc}; -use tokio::sync::RwLock; pub struct ClientManager { algod: AlgodClient, - cached_network_details: RwLock>>, + cached_network_details: FfiMutex>>, } impl ClientManager { pub fn new(config: AlgoConfig) -> Self { Self { algod: Self::get_algod_client(&config.algod_config), - cached_network_details: RwLock::new(None), + cached_network_details: FfiMutex::new(None), } } @@ -30,14 +30,14 @@ impl ClientManager { ) -> Result, Box> { // Fast path: multiple readers can access concurrently { - let cached = self.cached_network_details.read().await; + let cached = self.cached_network_details.lock().await; if let Some(ref details) = *cached { return Ok(Arc::clone(details)); } } // Slow path: exclusive write access for initialization - let mut cached = self.cached_network_details.write().await; + let mut cached = self.cached_network_details.lock().await; // Double-check: someone else might have initialized while we waited for write lock if let Some(ref details) = *cached { @@ -179,7 +179,7 @@ mod tests { let manager = ClientManager::new(config); // Cache should be initially empty - let cache = manager.cached_network_details.try_read().unwrap(); + let cache = manager.cached_network_details.blocking_lock(); assert!(cache.is_none()); } @@ -199,7 +199,7 @@ mod tests { assert!(manager.network().await.is_err()); // Cache should remain empty after errors - let cache = manager.cached_network_details.read().await; + let cache = manager.cached_network_details.lock().await; assert!(cache.is_none()); } diff --git a/crates/ffi_macros/Cargo.toml b/crates/ffi_macros/Cargo.toml index c2e4f438..946eb8d1 100644 --- a/crates/ffi_macros/Cargo.toml +++ b/crates/ffi_macros/Cargo.toml @@ -9,4 +9,4 @@ proc-macro = true [dependencies] convert_case = "0.8.0" quote = "1.0.39" -syn = "2.0.99" +syn = { version = "2.0.99", features = ["full"] } diff --git a/crates/ffi_macros/src/lib.rs b/crates/ffi_macros/src/lib.rs index 77d02d92..d27cd05d 100644 --- a/crates/ffi_macros/src/lib.rs +++ b/crates/ffi_macros/src/lib.rs @@ -16,8 +16,8 @@ pub fn ffi_func(_attr: TokenStream, item: TokenStream) -> TokenStream { let output = quote! { #(#attrs)* - #[cfg_attr(feature = "ffi_wasm", wasm_bindgen(js_name = #js_name))] - #[cfg_attr(feature = "ffi_uniffi", uniffi::export)] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = #js_name))] + #[cfg_attr(not(target_arch = "wasm32"), uniffi::export)] #vis #sig #body }; @@ -38,13 +38,13 @@ pub fn ffi_record(_attr: TokenStream, item: TokenStream) -> TokenStream { let struct_attrs = quote! { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] - #[cfg_attr(feature = "ffi_wasm", derive(Tsify))] + #[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[cfg_attr( - feature = "ffi_wasm", + target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi, large_number_types_as_bigints) )] - #[cfg_attr(feature = "ffi_wasm", serde(rename_all = "camelCase"))] - #[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))] + #[cfg_attr(target_arch = "wasm32", serde(rename_all = "camelCase"))] + #[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Record))] }; // Combine original attributes with new ones @@ -62,13 +62,13 @@ pub fn ffi_enum(_attr: TokenStream, item: TokenStream) -> TokenStream { let enum_attrs = quote! { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] - #[cfg_attr(feature = "ffi_wasm", derive(Tsify))] + #[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[cfg_attr( - feature = "ffi_wasm", + target_arch = "wasm32", tsify(into_wasm_abi, from_wasm_abi, large_number_types_as_bigints) )] - #[cfg_attr(feature = "ffi_wasm", serde(rename_all = "camelCase"))] - #[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Enum))] + #[cfg_attr(target_arch = "wasm32", serde(rename_all = "camelCase"))] + #[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] }; // Combine original attributes with new ones @@ -91,14 +91,14 @@ fn is_option_type(field: &Field) -> bool { fn add_option_field_attributes(field: &mut Field) { let wasm_attr: syn::Attribute = - syn::parse_quote!(#[cfg_attr(feature = "ffi_wasm", tsify(optional))]); + syn::parse_quote!(#[cfg_attr(target_arch = "wasm32", tsify(optional))]); field.attrs.push(wasm_attr); let field_name = field.ident.to_token_stream().to_string(); if field_name != "genesis_id" && field_name != "genesis_hash" { let uniffi_attr: syn::Attribute = - syn::parse_quote!(#[cfg_attr(feature = "ffi_uniffi", uniffi(default = None))]); + syn::parse_quote!(#[cfg_attr(not(target_arch = "wasm32"), uniffi(default = None))]); field.attrs.push(uniffi_attr); } } diff --git a/crates/ffi_mutex/Cargo.toml b/crates/ffi_mutex/Cargo.toml index 1f821984..5059c256 100644 --- a/crates/ffi_mutex/Cargo.toml +++ b/crates/ffi_mutex/Cargo.toml @@ -3,12 +3,10 @@ name = "ffi_mutex" version = "0.1.0" edition = "2024" -[features] -default = ["ffi_uniffi"] -ffi_uniffi = ["dep:uniffi", "dep:tokio"] -ffi_wasm = ["dep:wasm-bindgen"] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.45.1", default-features = false, features = ["sync"] } +uniffi = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { workspace = true } -[dependencies] -tokio = { version = "1.45.1", default-features = false, features = ["sync"], optional = true } -uniffi = { workspace = true, optional = true } -wasm-bindgen = { workspace = true, optional = true } diff --git a/crates/ffi_mutex/src/lib.rs b/crates/ffi_mutex/src/lib.rs index 4a6c528d..682e4aa1 100644 --- a/crates/ffi_mutex/src/lib.rs +++ b/crates/ffi_mutex/src/lib.rs @@ -1,7 +1,7 @@ -#[cfg(feature = "ffi_uniffi")] +#[cfg(not(target_arch = "wasm32"))] pub type InnerMutex = tokio::sync::Mutex; -#[cfg(feature = "ffi_wasm")] +#[cfg(target_arch = "wasm32")] pub type InnerMutex = std::cell::RefCell; /// A wrapper around `tokio::sync::Mutex` (for uniffi) or `std::cell::RefCell` (for WASM) @@ -16,29 +16,29 @@ pub struct FfiMutex(InnerMutex); impl FfiMutex { pub fn new(value: T) -> Self { - #[cfg(feature = "ffi_uniffi")] + #[cfg(not(target_arch = "wasm32"))] return Self(tokio::sync::Mutex::new(value)); - #[cfg(feature = "ffi_wasm")] + #[cfg(target_arch = "wasm32")] return Self(std::cell::RefCell::new(value)); } - #[cfg(feature = "ffi_uniffi")] + #[cfg(not(target_arch = "wasm32"))] pub fn blocking_lock(&self) -> tokio::sync::MutexGuard<'_, T> { self.0.blocking_lock() } - #[cfg(feature = "ffi_wasm")] + #[cfg(target_arch = "wasm32")] pub fn blocking_lock(&self) -> std::cell::RefMut<'_, T> { self.0.borrow_mut() } - #[cfg(feature = "ffi_uniffi")] + #[cfg(not(target_arch = "wasm32"))] pub async fn lock(&self) -> tokio::sync::MutexGuard<'_, T> { self.0.lock().await } - #[cfg(feature = "ffi_wasm")] + #[cfg(target_arch = "wasm32")] pub async fn lock(&self) -> std::cell::RefMut<'_, T> { self.0.borrow_mut() } diff --git a/docs/book/crates/algokit_transact_ffi.md b/docs/book/crates/algokit_transact_ffi.md index bbf84c61..401ee0a6 100644 --- a/docs/book/crates/algokit_transact_ffi.md +++ b/docs/book/crates/algokit_transact_ffi.md @@ -10,11 +10,6 @@ Foreign Function Interface bindings for `algokit_transact`, enabling usage from - **WebAssembly Bindings** - For JavaScript/TypeScript usage - **C-compatible ABI** - For integration with C/C++ and other systems languages -## Features - -- `ffi_uniffi` (default) - UniFFI-based bindings -- `ffi_wasm` - WebAssembly/JavaScript bindings - ## Crate Types Built as both: @@ -49,13 +44,13 @@ The complete API documentation with all FFI types, functions, and binding exampl ### UniFFI Bindings ```bash -cargo build --package algokit_transact_ffi --features ffi_uniffi +cargo build --package algokit_transact_ffi ``` ### WebAssembly Bindings ```bash -cargo build --package algokit_transact_ffi --features ffi_wasm +cargo build --package algokit_transact_ffi --target wasm32-unknown-unknown ``` ## Language Support diff --git a/scripts/sanity.sh b/scripts/sanity.sh index 9f6d3984..309e8240 100755 --- a/scripts/sanity.sh +++ b/scripts/sanity.sh @@ -7,9 +7,10 @@ cargo fmt --check # Run clippy and treat warnings as errors cargo clippy -- -D warnings -cargo check - -# By default uniffi is enabled for FFI crates, so we need to explicitly check WASM -cargo check --no-default-features --features ffi_wasm +for dir in crates/*/; do + # Run cargo check + cargo check --manifest-path "${dir}Cargo.toml" + cargo check --manifest-path "${dir}Cargo.toml" --target wasm32-unknown-unknown +done cargo test diff --git a/tools/build_pkgs/src/typescript.rs b/tools/build_pkgs/src/typescript.rs index d8631c0b..201e54e2 100644 --- a/tools/build_pkgs/src/typescript.rs +++ b/tools/build_pkgs/src/typescript.rs @@ -37,7 +37,7 @@ impl Display for WasmPackMode { fn wasm_pack(package: &Package, target: &WasmPackTarget, dir: &Path) -> Result { let crate_name = package.crate_name(); let command = format!( - "bunx wasm-pack build --out-dir ../../packages/typescript/{package}/pkg --mode normal --release --target {target} ../../../crates/{crate_name} --no-default-features --features ffi_wasm" + "bunx wasm-pack build --out-dir ../../packages/typescript/{package}/pkg --mode normal --release --target {target} ../../../crates/{crate_name}" ); let mut env_vars = HashMap::new(); From b4523437415397d1c3498e99e4fa31b0869a2383 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Mon, 21 Jul 2025 13:52:05 -0400 Subject: [PATCH 2/2] ci: use NO_PAGER instead of --no-pager --- .github/workflows/api_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api_ci.yml b/.github/workflows/api_ci.yml index d6c5b0b5..dc43464f 100644 --- a/.github/workflows/api_ci.yml +++ b/.github/workflows/api_ci.yml @@ -82,7 +82,7 @@ jobs: cat /tmp/post_generation_status.txt echo "" echo "🔍 Detailed diff:" - git diff --no-pager ${{ env.OUTPUT_DIR }} + GIT_PAGER="" git diff ${{ env.OUTPUT_DIR }} echo "" echo "💡 This indicates that the generated code is not in sync with the current specification." echo " Please run 'cargo api generate-algod' and commit the changes."