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." 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();