From d0c4e20d3c32449e3501e325c39f3a3bec1dd727 Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Sun, 12 Jul 2026 19:56:06 +0800 Subject: [PATCH 1/6] Implement React Native Noir bindings and enhance Android build process - Introduced a new module for React Native Noir bindings, integrating it into the build process for the React Native platform. - Enhanced the Android build logic to conditionally apply architecture-specific parameters based on the presence of iOS architectures and the Noir adapter. - Updated the `android_noir` module to utilize a custom Zig linker for resolving ABI compatibility issues with the barretenberg prebuilt. - Refactored the React Native project creation logic to ensure proper handling of architecture-specific Gradle properties and cleanup of stale files. - Improved the `package.json` template for React Native to include necessary configurations and dependencies for the new setup. These changes streamline the integration of Noir with React Native and improve the overall build process for Android. --- cli/src/build.rs | 48 +++- cli/src/build/android_noir.rs | 21 +- cli/src/create/react_native.rs | 42 +++- mopro-ffi/src/app_config/android.rs | 18 +- mopro-ffi/src/app_config/constants.rs | 1 + mopro-ffi/src/app_config/react_native.rs | 212 ++++++++++++++---- .../template/react_native/package.json | 151 ++++++------- 7 files changed, 355 insertions(+), 138 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index cc661814c..a0be6f730 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -24,6 +24,7 @@ use target_resolver::TargetSelection; mod android_noir; mod mode_resolver; +mod react_native_noir; mod target_resolver; pub fn build_project( @@ -192,7 +193,52 @@ pub fn build_project( Platform::ReactNative => { let arch_strings = selection.architecture_strings(); let arch_refs: Vec<&String> = arch_strings.iter().collect(); - build_from_str_arch::(mode, ¤t_dir, arch_refs, ())?; + + // Mirrors generate_react_native_bindings' own iOS-takes-precedence + // rule: only look at adapter-specific Android overrides when no + // iOS arch was requested alongside it. + let has_ios = arch_strings.iter().any(|a| a.contains("ios")); + let android_arch_strings: Vec = arch_strings + .iter() + .filter(|a| a.contains("android")) + .cloned() + .collect(); + + let params = if has_ios + || android_arch_strings.is_empty() + || !config.adapter_contains(Adapter::Noir) + { + AndroidBindingsParams::default() + } else { + let android_arch_refs: Vec<&String> = android_arch_strings.iter().collect(); + android_noir::android_bindings_params(¤t_dir, &android_arch_refs)? + }; + + if params.arch_overrides.is_empty() { + build_from_str_arch::(mode, ¤t_dir, arch_refs, ())?; + } else { + let bindings_dir = + mopro_ffi::app_config::react_native::setup_bindings_dir(¤t_dir)?; + mopro_ffi::app_config::react_native::generate_jsi_bindings(&bindings_dir)?; + react_native_noir::build( + ¤t_dir, + &bindings_dir, + &android_arch_strings, + mode, + ¶ms, + )?; + mopro_ffi::app_config::react_native::patch_android_cmake_lists_uniffi_bindgen_resolve(&bindings_dir)?; + mopro_ffi::app_config::react_native::set_xcframework_package_files( + &bindings_dir, + )?; + } + + if !android_arch_strings.is_empty() { + mopro_ffi::app_config::react_native::patch_gradle_properties_architectures( + ¤t_dir, + &android_arch_strings, + )?; + } } Platform::Web => { build_from_str_arch::( diff --git a/cli/src/build/android_noir.rs b/cli/src/build/android_noir.rs index efc36da6f..91e8a2534 100644 --- a/cli/src/build/android_noir.rs +++ b/cli/src/build/android_noir.rs @@ -7,6 +7,13 @@ //! glibc prebuilt Android can't `dlopen`. We pre-fetch the right prebuilt, point //! `BB_LIB_DIR` at it, and link with Zig so the prebuilt's libc++ (`std::__1`) //! resolves (the NDK's `std::__ndk1` is ABI-incompatible). +//! +//! Lives in the CLI (not `mopro-ffi`) and is only invoked when the project's +//! `Config.toml` declares the Noir adapter: computing these params touches +//! `Cargo.lock` (see `barretenberg_rs_version`), and forcing that on every +//! Android/React Native build — Noir or not — would force a full dependency +//! resolution (including unrelated optional deps like `flutter_rust_bridge`) +//! on projects that never asked for it. use anyhow::Context; use mopro_ffi::app_config::android::{AndroidBindingsParams, ArchBuildConfig}; @@ -41,7 +48,7 @@ pub fn android_bindings_params( for (triple, bb_arch) in &bb_targets { let bb_lib_dir = download_barretenberg_android_lib(bb_arch, &version, &build_dir)?; - let linker_flag = zig_linker_flag(triple, &build_dir)?; + let linker = zig_linker_path(triple, &build_dir)?; params.arch_overrides.insert( triple.clone(), ArchBuildConfig { @@ -49,7 +56,7 @@ pub fn android_bindings_params( "BB_LIB_DIR".to_string(), bb_lib_dir.to_string_lossy().into_owned(), )], - extra_rustflags: vec![linker_flag], + linker: Some(linker), }, ); } @@ -145,10 +152,10 @@ fn ensure_zig_available() -> anyhow::Result<()> { Ok(()) } -/// Write a Zig-cc linker wrapper for `triple` and return the `-Clinker=` -/// flag. Zig provides the barretenberg prebuilt's `std::__1` libc++ symbols; only -/// the linker is overridden, so the NDK still compiles everything else. -fn zig_linker_flag(triple: &str, build_dir: &Path) -> anyhow::Result { +/// Write a Zig-cc linker wrapper for `triple` and return its path. Zig provides +/// the barretenberg prebuilt's `std::__1` libc++ symbols; only the linker is +/// overridden, so the NDK still compiles everything else. +fn zig_linker_path(triple: &str, build_dir: &Path) -> anyhow::Result { let ndk = android_ndk_home()?; let host = ndk_host_tag(); let sysroot = ndk @@ -204,7 +211,7 @@ fn zig_linker_flag(triple: &str, build_dir: &Path) -> anyhow::Result { fs::set_permissions(&wrapper, perms)?; } - Ok(format!("-Clinker={}", wrapper.display())) + Ok(wrapper.to_string_lossy().into_owned()) } /// Locate the Android NDK from the usual environment variables. diff --git a/cli/src/create/react_native.rs b/cli/src/create/react_native.rs index c462d0ba1..f0264a3d7 100644 --- a/cli/src/create/react_native.rs +++ b/cli/src/create/react_native.rs @@ -1,17 +1,19 @@ use super::Create; +use crate::config::read_config; use crate::constants::Platform; use crate::create::utils::{check_bindings, copy_dir, copy_keys, download_and_extract_template}; use crate::print::print_footer_message; use crate::style::print_green_bold; use anyhow::{Error, Result}; -use mopro_ffi::app_config::constants::REACT_NATIVE_BINDINGS_DIR; +use mopro_ffi::app_config::constants::{REACT_NATIVE_APP_DIR, REACT_NATIVE_BINDINGS_DIR}; +use mopro_ffi::app_config::react_native::patch_gradle_properties_architectures; use std::{fs, path::PathBuf}; pub struct ReactNative; impl Create for ReactNative { - const NAME: &'static str = "react-native"; + const NAME: &'static str = REACT_NATIVE_APP_DIR; fn create(project_dir: PathBuf) -> Result<()> { let react_native_bindings_dir = check_bindings(&project_dir, Platform::ReactNative)?; @@ -37,6 +39,7 @@ impl Create for ReactNative { react_native_bindings_dir.as_ref().unwrap(), &mopro_module_dir, )?; + remove_stale_web_entrypoint(&mopro_module_dir)?; let assets_dir = target_dir.join("assets/keys"); fs::remove_dir_all(&assets_dir)?; @@ -44,6 +47,25 @@ impl Create for ReactNative { copy_keys(assets_dir)?; + // The downloaded scaffold's `android/gradle.properties` defaults + // `reactNativeArchitectures` to all four ABIs, but the bindings dir + // we just copied in only has `.so`s for the architectures this + // project was built for. Narrow it now so a fresh `create` doesn't + // reintroduce the "missing .so, no known rule to make it" ninja + // failure for ABIs that were never built. + let config_path = project_dir.join("Config.toml"); + if let Ok(config) = read_config(&config_path) { + if let Some(react_native_archs) = config.react_native { + let android_archs: Vec = react_native_archs + .into_iter() + .filter(|a| a.contains("android")) + .collect(); + if !android_archs.is_empty() { + patch_gradle_properties_architectures(&project_dir, &android_archs)?; + } + } + } + Self::print_message(); Ok(()) } @@ -59,3 +81,19 @@ impl Create for ReactNative { print_footer_message(); } } + +/// The downloaded `zkmopro/react-native-app` scaffold ships a static +/// `src/index.web.ts` (for optional web/wasm support) that imports from +/// `./generated/wasm-bindgen/index.js` and `index_bg.wasm`. `mopro build` never +/// generates that `generated/wasm-bindgen` directory — React Native builds only +/// target iOS/Android — so the file is always a dangling reference. Since +/// `copy_dir` only overwrites files present in the built bindings dir, it can't +/// remove this pre-existing one; left in place, it breaks `npm install`'s +/// `prepare: bob build` step (`tsc` fails to resolve the missing module). +fn remove_stale_web_entrypoint(mopro_module_dir: &std::path::Path) -> Result<()> { + let index_web_ts = mopro_module_dir.join("src").join("index.web.ts"); + if index_web_ts.exists() { + fs::remove_file(&index_web_ts)?; + } + Ok(()) +} diff --git a/mopro-ffi/src/app_config/android.rs b/mopro-ffi/src/app_config/android.rs index 802a26175..ad2549c85 100644 --- a/mopro-ffi/src/app_config/android.rs +++ b/mopro-ffi/src/app_config/android.rs @@ -31,8 +31,13 @@ pub fn build() { pub struct ArchBuildConfig { /// Extra environment variables for this arch's `cargo ndk` invocation. pub extra_env: Vec<(String, String)>, - /// Flags appended to `RUSTFLAGS` for this arch (e.g. `-Clinker=`). - pub extra_rustflags: Vec, + /// Path to a linker (or linker wrapper script) to use for this arch, if + /// overridden. Kept separate from raw `RUSTFLAGS` so callers that can't use + /// the `RUSTFLAGS`/`CARGO_TARGET__RUSTFLAGS` env channel (e.g. + /// uniffi-bindgen-react-native, which sets its own target-specific + /// `CARGO_TARGET__RUSTFLAGS` and would clobber anything set there) + /// can instead pass it as a `--config target..linker=` arg. + pub linker: Option, } /// Generic knobs that let a caller tailor the Android build without `mopro-ffi` @@ -183,7 +188,7 @@ fn build_for_arch( Ok(out_lib_path) } -/// Apply caller-supplied env vars and `RUSTFLAGS` additions to a `cargo ndk` command. +/// Apply caller-supplied env vars and a linker override to a `cargo ndk` command. fn apply_arch_config(build_cmd: &mut Command, config: Option<&ArchBuildConfig>) { let Some(config) = config else { return; @@ -191,8 +196,8 @@ fn apply_arch_config(build_cmd: &mut Command, config: Option<&ArchBuildConfig>) for (key, value) in &config.extra_env { build_cmd.env(key, value); } - if !config.extra_rustflags.is_empty() { - let extra = config.extra_rustflags.join(" "); + if let Some(linker) = &config.linker { + let extra = format!("-Clinker={linker}"); // Preserve any RUSTFLAGS the caller already set in the environment. let rustflags = match std::env::var("RUSTFLAGS") { Ok(existing) if !existing.trim().is_empty() => format!("{existing} {extra}"), @@ -206,8 +211,7 @@ fn apply_arch_config(build_cmd: &mut Command, config: Option<&ArchBuildConfig>) /// custom linker (e.g. Zig) can drop the `.symtab` uniffi-bindgen reads, which the /// NDK linker keeps. This lib may not run, but bindgen never runs it; a separate /// target dir keeps the shipped jniLibs untouched. Caller env (e.g. an overriding -/// lib dir) is applied, but `extra_rustflags` is not — this build must use the NDK -/// linker. +/// lib dir) is applied, but `linker` is not — this build must use the NDK linker. fn build_bindgen_lib( arch: AndroidArch, lib_name: &str, diff --git a/mopro-ffi/src/app_config/constants.rs b/mopro-ffi/src/app_config/constants.rs index 17df20835..200a2a328 100644 --- a/mopro-ffi/src/app_config/constants.rs +++ b/mopro-ffi/src/app_config/constants.rs @@ -27,6 +27,7 @@ pub const ARCH_ARM_64_V8: &str = "arm64-v8a"; pub const FLUTTER_BINDINGS_DIR: &str = "mopro_flutter_bindings"; pub const REACT_NATIVE_BINDINGS_DIR: &str = "MoproReactNativeBindings"; +pub const REACT_NATIVE_APP_DIR: &str = "react-native"; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Mode { diff --git a/mopro-ffi/src/app_config/react_native.rs b/mopro-ffi/src/app_config/react_native.rs index d5f0c4875..8a3e26cdd 100644 --- a/mopro-ffi/src/app_config/react_native.rs +++ b/mopro-ffi/src/app_config/react_native.rs @@ -4,7 +4,8 @@ use std::path::{Path, PathBuf}; use std::process::Command; use crate::app_config::constants::{ - Arch, Mode, ReactNativeArch, ReactNativePlatform, REACT_NATIVE_BINDINGS_DIR, + Arch, Mode, ReactNativeArch, ReactNativePlatform, ARCH_ARM_64_V8, ARCH_ARM_V7_ABI, ARCH_I686, + ARCH_X86_64, REACT_NATIVE_APP_DIR, REACT_NATIVE_BINDINGS_DIR, }; use super::PlatformBuilder; @@ -25,43 +26,14 @@ impl PlatformBuilder for ReactNativePlatform { target_archs: Vec, _params: Self::Params, ) -> anyhow::Result { - install_uniffi_bindgen_react_native()?; - - fs::create_dir_all(project_dir.join(REACT_NATIVE_BINDINGS_DIR)) - .expect("failed to create bindings directory"); - - // Copy the react_native template to the project directory - // Get the path to the template directory relative to this source file - let template_dir = - Path::new(env!("CARGO_MANIFEST_DIR")).join("src/app_config/template/react_native"); - let mut copy_options = fs_extra::dir::CopyOptions::new(); - copy_options.overwrite = true; - copy_options.content_only = true; - fs_extra::dir::copy( - &template_dir, - project_dir.join(REACT_NATIVE_BINDINGS_DIR), - ©_options, - ) - .with_context(|| format!("Failed to copy react_native folder from {:?}", template_dir))?; - - // Replace the <%PATH_TO_PROJECT%> in the ubrn.config.yaml template with the project directory - let target_file = project_dir - .join(REACT_NATIVE_BINDINGS_DIR) - .join("ubrn.config.yaml"); - - let contents = fs::read_to_string(&target_file) - .with_context(|| format!("Failed to read ubrn.config.yaml from {:?}", target_file))? - .replace("<%PATH_TO_PROJECT%>", &project_dir.to_string_lossy()); - - fs::write(&target_file, contents) - .with_context(|| format!("Failed to write ubrn.config.yaml to {:?}", target_file))?; - - generate_react_native_bindings(project_dir, target_archs, mode)?; + let bindings_dir = setup_bindings_dir(project_dir)?; + generate_react_native_bindings(project_dir, target_archs, mode, &bindings_dir)?; Ok(PathBuf::from(REACT_NATIVE_BINDINGS_DIR)) } } -fn install_uniffi_bindgen_react_native() -> anyhow::Result<()> { +/// Install `uniffi-bindgen-react-native` if it isn't already on `PATH`. +pub fn install_uniffi_bindgen_react_native() -> anyhow::Result<()> { let output = Command::new("uniffi-bindgen-react-native").output(); match output { Ok(_) => { @@ -76,7 +48,7 @@ fn install_uniffi_bindgen_react_native() -> anyhow::Result<()> { let status = Command::new("git") .args([ "clone", - "https://github.com/jhugman/uniffi-bindgen-react-native.git", + "https://github.com/zkmopro/uniffi-bindgen-react-native.git", ]) .current_dir(current_path.clone()) .status() @@ -112,20 +84,58 @@ fn install_uniffi_bindgen_react_native() -> anyhow::Result<()> { Ok(()) } -fn generate_react_native_bindings( - project_dir: &Path, - target_archs: Vec, - mode: Mode, -) -> anyhow::Result<()> { +/// Install `uniffi-bindgen-react-native`, and set up the `MoproReactNativeBindings` +/// directory (copy the template, point `ubrn.config.yaml` at `project_dir`). +/// Returns the bindings directory. Idempotent: safe to call before every build. +pub fn setup_bindings_dir(project_dir: &Path) -> anyhow::Result { + install_uniffi_bindgen_react_native()?; + let bindings_dir = project_dir.join(REACT_NATIVE_BINDINGS_DIR); + fs::create_dir_all(&bindings_dir).expect("failed to create bindings directory"); + + // Copy the react_native template to the project directory + // Get the path to the template directory relative to this source file + let template_dir = + Path::new(env!("CARGO_MANIFEST_DIR")).join("src/app_config/template/react_native"); + let mut copy_options = fs_extra::dir::CopyOptions::new(); + copy_options.overwrite = true; + copy_options.content_only = true; + fs_extra::dir::copy(&template_dir, &bindings_dir, ©_options) + .with_context(|| format!("Failed to copy react_native folder from {:?}", template_dir))?; + + // Replace the <%PATH_TO_PROJECT%> in the ubrn.config.yaml template with the project directory + let target_file = bindings_dir.join("ubrn.config.yaml"); + + let contents = fs::read_to_string(&target_file) + .with_context(|| format!("Failed to read ubrn.config.yaml from {:?}", target_file))? + .replace("<%PATH_TO_PROJECT%>", &project_dir.to_string_lossy()); + + fs::write(&target_file, contents) + .with_context(|| format!("Failed to write ubrn.config.yaml to {:?}", target_file))?; + + Ok(bindings_dir) +} + +/// Run `uniffi-bindgen-react-native generate jsi turbo-module` in `bindings_dir`. +pub fn generate_jsi_bindings(bindings_dir: &Path) -> anyhow::Result<()> { let status = Command::new("uniffi-bindgen-react-native") .args(["generate", "jsi", "turbo-module"]) - .current_dir(bindings_dir.clone()) + .current_dir(bindings_dir) .status() .expect("failed to generate react native bindings"); if !status.success() { return Err(anyhow::anyhow!("Failed to generate react native bindings")); } + Ok(()) +} + +fn generate_react_native_bindings( + _project_dir: &Path, + target_archs: Vec, + mode: Mode, + bindings_dir: &Path, +) -> anyhow::Result<()> { + generate_jsi_bindings(bindings_dir)?; let ios_target_string = target_archs .iter() @@ -142,13 +152,19 @@ fn generate_react_native_bindings( if !ios_target_string.is_empty() { let platform = "ios"; - build_for_arch(platform, mode, &ios_target_string, &bindings_dir)?; + build_for_arch(platform, mode, &ios_target_string, bindings_dir)?; } else if !android_target_string.is_empty() { - let platform = "android"; - build_for_arch(platform, mode, &android_target_string, &bindings_dir)?; + build_for_arch("android", mode, &android_target_string, bindings_dir)?; + patch_android_cmake_lists_uniffi_bindgen_resolve(bindings_dir)?; } - // Include the xcframework in the package.json for mopro-react-native-package + set_xcframework_package_files(bindings_dir)?; + + Ok(()) +} + +/// Include the xcframework in `package.json` for `mopro-react-native-package`. +pub fn set_xcframework_package_files(bindings_dir: &Path) -> anyhow::Result<()> { let npm_status = Command::new("npm") .args(["pkg", "set", "files[]=*.xcframework/**"]) .current_dir(bindings_dir) @@ -157,7 +173,6 @@ fn generate_react_native_bindings( if !npm_status.success() { return Err(anyhow::anyhow!("Failed to set files in package.json")); } - Ok(()) } @@ -190,3 +205,108 @@ fn build_for_arch( } Ok(()) } + +// uniffi-bindgen-react-native's generated android/CMakeLists.txt resolves its +// own package root via `require.resolve('uniffi-bindgen-react-native/package.json')`, +// which throws ERR_PACKAGE_PATH_NOT_EXPORTED on package versions whose +// "exports" field omits that subpath (0.31.0-3+, see +// zkmopro/uniffi-bindgen-react-native#399), breaking the Android C++ build +// (missing headers like UniffiCallInvoker.h). Rewrite the generated file to +// resolve the package's public "." export instead and walk up to the +// package root, which works regardless of the "exports" field. This is a +// no-op once the upstream fix ships in a published release. +pub fn patch_android_cmake_lists_uniffi_bindgen_resolve(bindings_dir: &Path) -> anyhow::Result<()> { + let cmake_lists_path = bindings_dir.join("android").join("CMakeLists.txt"); + let Ok(contents) = fs::read_to_string(&cmake_lists_path) else { + return Ok(()); + }; + + const BROKEN: &str = "require.resolve('uniffi-bindgen-react-native/package.json')"; + if !contents.contains(BROKEN) { + return Ok(()); + } + + const FIXED: &str = "require('path').dirname(require('path').dirname(require('path').dirname(require('path').dirname(require.resolve('uniffi-bindgen-react-native')))))"; + let patched = contents.replace(BROKEN, FIXED).replace( + "\n# Get the directory; get_filename_component and cmake_path will normalize\n\ +# paths with Windows path separators.\n\ +get_filename_component(UNIFFI_BINDGEN_PATH \"${UNIFFI_BINDGEN_PATH}\" DIRECTORY)\n", + "\n", + ); + + fs::write(&cmake_lists_path, patched) + .with_context(|| format!("Failed to patch {:?}", cmake_lists_path))?; + Ok(()) +} + +fn android_abi_for_triple(triple: &str) -> Option<&'static str> { + match triple { + "aarch64-linux-android" => Some(ARCH_ARM_64_V8), + "armv7-linux-androideabi" => Some(ARCH_ARM_V7_ABI), + "i686-linux-android" => Some(ARCH_I686), + "x86_64-linux-android" => Some(ARCH_X86_64), + _ => None, + } +} + +/// The `react-native/android/gradle.properties` template (from the +/// `zkmopro/react-native-app` scaffold) defaults `reactNativeArchitectures` to +/// all four Android ABIs, but the native library is only built for the +/// architectures configured for this project. Gradle's CMake build fails with +/// a missing `.so` for any listed ABI that wasn't actually built, so narrow +/// the property to match what was built (appending the line if it's missing +/// entirely). `project_dir` is the mopro project root (the parent of the +/// `react-native/` app directory); a no-op if that app directory hasn't been +/// created yet (i.e. before `mopro create react-native` has run). +pub fn patch_gradle_properties_architectures( + project_dir: &Path, + android_target_strings: &[String], +) -> anyhow::Result<()> { + let abis: Vec<&str> = android_target_strings + .iter() + .filter_map(|triple| android_abi_for_triple(triple)) + .collect(); + if abis.is_empty() { + return Ok(()); + } + + let gradle_properties_path = project_dir + .join(REACT_NATIVE_APP_DIR) + .join("android") + .join("gradle.properties"); + let Ok(contents) = fs::read_to_string(&gradle_properties_path) else { + return Ok(()); + }; + + const PREFIX: &str = "reactNativeArchitectures="; + let new_line = format!("{PREFIX}{}", abis.join(",")); + + // Match only a real assignment line (trimmed line starting with the + // property name), not any occurrence of the substring — the template's + // own comment block (`# ./gradlew -PreactNativeArchitectures=x86_64`) + // also contains "reactNativeArchitectures=" and would otherwise be + // matched first, silently leaving the real property untouched. + let existing_line = contents + .lines() + .find(|line| line.trim_start().starts_with(PREFIX)); + + let patched = match existing_line { + Some(line) => contents.replacen(line, &new_line, 1), + // Property was removed from the template (e.g. by hand); append it + // rather than silently leaving Gradle to fall back to building all + // four ABIs again. + None => { + let mut patched = contents.clone(); + if !patched.ends_with('\n') && !patched.is_empty() { + patched.push('\n'); + } + patched.push_str(&new_line); + patched.push('\n'); + patched + } + }; + + fs::write(&gradle_properties_path, patched) + .with_context(|| format!("Failed to patch {:?}", gradle_properties_path))?; + Ok(()) +} diff --git a/mopro-ffi/src/app_config/template/react_native/package.json b/mopro-ffi/src/app_config/template/react_native/package.json index a3f87e92b..e113b574b 100644 --- a/mopro-ffi/src/app_config/template/react_native/package.json +++ b/mopro-ffi/src/app_config/template/react_native/package.json @@ -1,18 +1,18 @@ { - "name": "mopro-ffi", - "version": "0.1.0", - "description": "Mopro FFI bindings for React Native", - "main": "./lib/module/index.js", - "types": "./lib/typescript/src/index.d.ts", - "exports": { + "name": "mopro-ffi", + "version": "0.1.0", + "description": "Mopro FFI bindings for React Native", + "main": "./lib/module/index.js", + "types": "./lib/typescript/src/index.d.ts", + "exports": { ".": { - "source": "./src/index.tsx", - "types": "./lib/typescript/src/index.d.ts", - "default": "./lib/module/index.js" + "source": "./src/index.tsx", + "types": "./lib/typescript/src/index.d.ts", + "default": "./lib/module/index.js" }, "./package.json": "./package.json" - }, - "files": [ + }, + "files": [ "src", "lib", "android", @@ -29,9 +29,10 @@ "!**/__tests__", "!**/__fixtures__", "!**/__mocks__", - "!**/.*" - ], - "scripts": { + "!**/.*", + "*.xcframework/**" + ], + "scripts": { "ubrn:ios": "ubrn build ios --and-generate --release && (cd example/ios && pod install)", "ubrn:android": "ubrn build android --and-generate --release --targets aarch64-linux-android", "test": "jest", @@ -40,26 +41,26 @@ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "release": "release-it --only-version" - }, - "keywords": [ + }, + "keywords": [ "react-native", "ios", "android" - ], - "repository": { + ], + "repository": { "type": "git", "url": "git+https://github.com/zkmopro/react-native-app.git" - }, - "author": "ZK Mopro Team (https://zkmopro.com)", - "license": "MIT", - "bugs": { + }, + "author": "ZK Mopro Team (https://zkmopro.com)", + "license": "MIT", + "bugs": { "url": "https://github.com/zkmopro/react-native-app/issues" - }, - "homepage": "https://github.com/zkmopro/react-native-app#readme", - "publishConfig": { + }, + "homepage": "https://github.com/zkmopro/react-native-app#readme", + "publishConfig": { "registry": "https://registry.npmjs.org/" - }, - "devDependencies": { + }, + "devDependencies": { "@commitlint/config-conventional": "^19.8.1", "@eslint/compat": "^1.3.2", "@eslint/eslintrc": "^3.3.1", @@ -84,86 +85,86 @@ "release-it": "^19.0.4", "turbo": "^2.5.6", "typescript": "^5.9.2" - }, - "peerDependencies": { + }, + "peerDependencies": { "react": "*", "react-native": "*" - }, - "workspaces": [ + }, + "workspaces": [ "example" - ], - "packageManager": "yarn@3.6.1", - "jest": { + ], + "packageManager": "yarn@3.6.1", + "jest": { "preset": "react-native", "modulePathIgnorePatterns": [ - "/example/node_modules", - "/lib/" + "/example/node_modules", + "/lib/" ] - }, - "commitlint": { + }, + "commitlint": { "extends": [ - "@commitlint/config-conventional" + "@commitlint/config-conventional" ] - }, - "release-it": { + }, + "release-it": { "git": { - "commitMessage": "chore: release ${version}", - "tagName": "v${version}" + "commitMessage": "chore: release ${version}", + "tagName": "v${version}" }, "npm": { - "publish": true + "publish": true }, "github": { - "release": true + "release": true }, "plugins": { - "@release-it/conventional-changelog": { - "preset": { - "name": "angular" + "@release-it/conventional-changelog": { + "preset": { + "name": "angular" + } } - } } - }, - "prettier": { + }, + "prettier": { "quoteProps": "consistent", "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", "useTabs": false - }, - "react-native-builder-bob": { + }, + "react-native-builder-bob": { "source": "src", "output": "lib", "targets": [ - [ - "module", - { - "esm": true - } - ], - [ - "typescript", - { - "project": "tsconfig.build.json" - } - ] + [ + "module", + { + "esm": true + } + ], + [ + "typescript", + { + "project": "tsconfig.build.json" + } + ] ] - }, - "codegenConfig": { + }, + "codegenConfig": { "name": "MoproFFISpec", "type": "modules", "jsSrcsDir": "src", "android": { - "javaPackageName": "com.moproffi" + "javaPackageName": "com.moproffi" } - }, - "create-react-native-library": { + }, + "create-react-native-library": { "languages": "kotlin-objc", "type": "turbo-module", "version": "0.54.5" - }, - "dependencies": { - "uniffi-bindgen-react-native": "^0.29.3-1" - } + }, + "dependencies": { + "@ubjs/core": "^0.31.0-3", + "uniffi-bindgen-react-native": "^0.31.0-3" } - \ No newline at end of file +} From df2eee4983a17468a958b2c755b2cddd59602d24 Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Sun, 12 Jul 2026 21:21:04 +0800 Subject: [PATCH 2/6] Refactor build process for React Native and improve print output - Enhanced the build logic in `build.rs` to better handle architecture-specific parameters for iOS and Android, ensuring proper integration of Noir bindings. - Simplified the initialization message in `print.rs` for improved readability by using direct string interpolation. - Updated Android build configurations to streamline the generation of bindings and ensure compatibility with the latest architecture requirements. These changes improve the overall build process and user experience when initializing projects. --- cli/src/build.rs | 39 ++++++++++++++++-------- cli/src/print.rs | 7 ++--- mopro-ffi/src/app_config/android.rs | 4 +-- mopro-ffi/src/app_config/react_native.rs | 10 +++--- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index a0be6f730..f36d62d0c 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -194,32 +194,45 @@ pub fn build_project( let arch_strings = selection.architecture_strings(); let arch_refs: Vec<&String> = arch_strings.iter().collect(); - // Mirrors generate_react_native_bindings' own iOS-takes-precedence - // rule: only look at adapter-specific Android overrides when no - // iOS arch was requested alongside it. - let has_ios = arch_strings.iter().any(|a| a.contains("ios")); + let ios_arch_strings: Vec = arch_strings + .iter() + .filter(|a| a.contains("ios")) + .cloned() + .collect(); let android_arch_strings: Vec = arch_strings .iter() .filter(|a| a.contains("android")) .cloned() .collect(); - let params = if has_ios - || android_arch_strings.is_empty() - || !config.adapter_contains(Adapter::Noir) - { - AndroidBindingsParams::default() - } else { - let android_arch_refs: Vec<&String> = android_arch_strings.iter().collect(); - android_noir::android_bindings_params(¤t_dir, &android_arch_refs)? - }; + let params = + if android_arch_strings.is_empty() || !config.adapter_contains(Adapter::Noir) { + AndroidBindingsParams::default() + } else { + let android_arch_refs: Vec<&String> = android_arch_strings.iter().collect(); + android_noir::android_bindings_params(¤t_dir, &android_arch_refs)? + }; if params.arch_overrides.is_empty() { + // Noir doesn't need any special handling here: mopro-ffi builds + // iOS and Android independently, so both run in one pass. build_from_str_arch::(mode, ¤t_dir, arch_refs, ())?; } else { + // Android needs the Zig-linked Noir build; iOS (if requested) + // still goes through the normal path since Noir only affects + // Android linking. let bindings_dir = mopro_ffi::app_config::react_native::setup_bindings_dir(¤t_dir)?; mopro_ffi::app_config::react_native::generate_jsi_bindings(&bindings_dir)?; + if !ios_arch_strings.is_empty() { + let ios_target_string = ios_arch_strings.join(","); + mopro_ffi::app_config::react_native::build_for_arch( + "ios", + mode, + &ios_target_string, + &bindings_dir, + )?; + } react_native_noir::build( ¤t_dir, &bindings_dir, diff --git a/cli/src/print.rs b/cli/src/print.rs index 1d495c087..2e7e79cda 100644 --- a/cli/src/print.rs +++ b/cli/src/print.rs @@ -13,15 +13,12 @@ pub fn print_footer_message() { } pub(crate) fn print_init_instructions(project_name: String) { - println!( - "🚀 Project '{}' initialized successfully! 🎉", - &project_name - ); + println!("🚀 Project '{}' initialized successfully! 🎉", project_name); println!(); println!("To get started, follow these steps:"); println!(); print_green_bold("1. Navigate to your project directory:".to_string()); - print_bold(format!(" cd {}", &project_name)); + print_bold(format!(" cd {project_name}")); println!(); print_green_bold("2. Run the following commands to build and run the project:".to_string()); print_bold(" mopro build".to_string()); diff --git a/mopro-ffi/src/app_config/android.rs b/mopro-ffi/src/app_config/android.rs index ad2549c85..04bb8acc1 100644 --- a/mopro-ffi/src/app_config/android.rs +++ b/mopro-ffi/src/app_config/android.rs @@ -74,9 +74,9 @@ impl PlatformBuilder for AndroidPlatform { let out_android_kt_file_name = ANDROID_KT_FILE; // Names for the generated files by uniffi - let lib_name = format!("lib{}.so", &uniffi_style_identifier); + let lib_name = format!("lib{uniffi_style_identifier}.so"); let gen_android_module_name = &uniffi_style_identifier; - let gen_android_kt_file_name = format!("{}.kt", &uniffi_style_identifier); + let gen_android_kt_file_name = format!("{uniffi_style_identifier}.kt"); #[cfg(feature = "witnesscalc")] let _ = std::env::var("ANDROID_NDK").context("ANDROID_NDK is not set")?; diff --git a/mopro-ffi/src/app_config/react_native.rs b/mopro-ffi/src/app_config/react_native.rs index 8a3e26cdd..5a9cb6fc4 100644 --- a/mopro-ffi/src/app_config/react_native.rs +++ b/mopro-ffi/src/app_config/react_native.rs @@ -151,9 +151,9 @@ fn generate_react_native_bindings( .join(","); if !ios_target_string.is_empty() { - let platform = "ios"; - build_for_arch(platform, mode, &ios_target_string, bindings_dir)?; - } else if !android_target_string.is_empty() { + build_for_arch("ios", mode, &ios_target_string, bindings_dir)?; + } + if !android_target_string.is_empty() { build_for_arch("android", mode, &android_target_string, bindings_dir)?; patch_android_cmake_lists_uniffi_bindgen_resolve(bindings_dir)?; } @@ -176,7 +176,9 @@ pub fn set_xcframework_package_files(bindings_dir: &Path) -> anyhow::Result<()> Ok(()) } -fn build_for_arch( +/// Run `uniffi-bindgen-react-native build --and-generate --targets ` +/// in `bindings_dir`. `platform` is `"ios"` or `"android"`. +pub fn build_for_arch( platform: &str, mode: Mode, target_string: &str, From 8f214045cfcba32527b1b3caee2fd98ced0b5c88 Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Sun, 12 Jul 2026 21:27:41 +0800 Subject: [PATCH 3/6] Update build configuration for React Native Noir bindings - Modified `.gitignore` to exclude the `cli/src/build/` directory from being ignored, allowing for better management of build artifacts. - Introduced a new file `react_native_noir.rs` in `cli/src/build/`, implementing a specialized build process for React Native Noir, which includes architecture-specific handling and binding generation from NDK-linked libraries. These changes enhance the build process for React Native projects utilizing Noir, ensuring proper integration and compatibility with architecture-specific requirements. --- .gitignore | 3 +- cli/src/build/react_native_noir.rs | 219 +++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 cli/src/build/react_native_noir.rs diff --git a/.gitignore b/.gitignore index fdf1f8488..030264710 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ mopro_flutter_bindings MoproAndroidBindings MoproiOSBindings MoproReactNativeBindings -build/ \ No newline at end of file +build/ +!cli/src/build/ \ No newline at end of file diff --git a/cli/src/build/react_native_noir.rs b/cli/src/build/react_native_noir.rs new file mode 100644 index 000000000..ef465287b --- /dev/null +++ b/cli/src/build/react_native_noir.rs @@ -0,0 +1,219 @@ +//! Noir/barretenberg-specific React Native Android build. +//! +//! Mirrors `android_noir`'s handling for the plain Android builder (see +//! `super::android_noir`), but `uniffi-bindgen-react-native` needs a different build +//! strategy than a single `cargo ndk` invocation: +//! +//! - It can't take per-target env in one multi-target invocation, and each +//! arch may need a different `BB_LIB_DIR` (barretenberg's Android prebuilt +//! is arch-specific), so each arch is built in its own invocation. +//! - Its `--and-generate` reads UniFFI metadata straight from the compiled +//! Android `.so`, and the Zig-linked build (needed to link barretenberg's +//! prebuilt) has stripped the `.symtab` that needs. So metadata is instead +//! extracted from a separate NDK-linked (non-Zig) scratch build, mirroring +//! the plain Android builder's `relink_with_ndk_for_bindgen`, via +//! `uniffi-bindgen-react-native generate all `. + +use anyhow::Context; +use mopro_ffi::app_config::android::AndroidBindingsParams; +use mopro_ffi::app_config::constants::Mode; +use mopro_ffi::app_config::project_name_from_toml; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Build every requested Android arch for React Native with `params`' overrides +/// applied, then generate JSI/turbo-module bindings from a separate NDK-linked +/// scratch build. Assumes `bindings_dir` is already set up (template copied, +/// `ubrn.config.yaml` pointed at `project_dir`, `generate jsi turbo-module` run). +pub fn build( + project_dir: &Path, + bindings_dir: &Path, + android_arch_strings: &[String], + mode: Mode, + params: &AndroidBindingsParams, +) -> anyhow::Result<()> { + patch_ubrn_config_for_noir(bindings_dir, params)?; + + for arch in android_arch_strings { + let env = params + .arch_overrides + .get(arch) + .map(|config| config.extra_env.clone()) + .unwrap_or_default(); + build_android_once(mode, arch, bindings_dir, &env)?; + } + + let bindgen_arch = android_arch_strings + .first() + .context("No Android architectures provided for binding generation")?; + let bindgen_env = params + .arch_overrides + .get(bindgen_arch) + .map(|config| config.extra_env.clone()) + .unwrap_or_default(); + let bindgen_lib = build_ndk_linked_lib_for_bindgen( + project_dir, + bindgen_arch, + mode, + params.min_sdk_version, + &bindgen_env, + )?; + + let status = Command::new("uniffi-bindgen-react-native") + .args([ + "generate", + "all", + &bindgen_lib.to_string_lossy(), + "--flavor", + "jsi", + ]) + .current_dir(bindings_dir) + .status() + .expect("failed to generate react native bindings"); + if !status.success() { + anyhow::bail!( + "Failed to generate react native bindings from {}", + bindgen_lib.display() + ); + } + Ok(()) +} + +fn build_android_once( + mode: Mode, + target_string: &str, + bindings_dir: &Path, + env: &[(String, String)], +) -> anyhow::Result<()> { + let mut args = vec![ + "build".to_string(), + "android".to_string(), + "--targets".to_string(), + target_string.to_string(), + ]; + if mode == Mode::Release { + args.push("--release".to_string()); + } + + let mut cmd = Command::new("uniffi-bindgen-react-native"); + cmd.args(&args).current_dir(bindings_dir); + for (key, value) in env { + cmd.env(key, value); + } + let status = cmd.status().expect("failed to build react native bindings"); + if !status.success() { + anyhow::bail!("Failed to build react native bindings for {target_string}"); + } + Ok(()) +} + +/// Build `arch` with the plain NDK linker (no Zig override) purely to extract +/// UniFFI metadata via `uniffi-bindgen-react-native generate all `. +/// This lib is never shipped or run; a separate target dir keeps the +/// already-built (Zig-linked) jniLibs untouched. +fn build_ndk_linked_lib_for_bindgen( + project_dir: &Path, + arch: &str, + mode: Mode, + min_sdk_version: Option, + extra_env: &[(String, String)], +) -> anyhow::Result { + let lib_name = format!( + "lib{}.so", + project_name_from_toml(project_dir) + .context("Failed to get project name from Cargo.toml")? + ); + let bindgen_target = project_dir.join("build").join("rn-bindgen"); + + let mut cmd = Command::new("cargo"); + cmd.arg("ndk").arg("-t").arg(arch); + if let Some(min_sdk) = min_sdk_version { + cmd.arg("--platform").arg(min_sdk.to_string()); + } + cmd.arg("build").arg("--link-libcxx-shared").arg("--lib"); + if mode == Mode::Release { + cmd.arg("--release"); + } + for (key, value) in extra_env { + cmd.env(key, value); + } + cmd.env("CARGO_BUILD_TARGET_DIR", &bindgen_target) + .env("CARGO_BUILD_TARGET", arch) + .env("CARGO_NDK_OUTPUT_PATH", bindgen_target.join("jniLibs")); + + let status = cmd + .status() + .expect("failed to build NDK-linked lib for bindgen"); + if !status.success() { + anyhow::bail!("cargo ndk build (bindgen lib) failed for {arch}"); + } + + let profile_dir = if mode == Mode::Release { + "release" + } else { + "debug" + }; + let out_lib_path = bindgen_target.join(arch).join(profile_dir).join(&lib_name); + if !out_lib_path.exists() { + anyhow::bail!( + "NDK bindgen lib missing at {} (needed for uniffi metadata)", + out_lib_path.display() + ); + } + Ok(out_lib_path) +} + +/// Bump `ubrn.config.yaml`'s Android `apiLevel` and add `cargoExtras` so +/// `uniffi-bindgen-react-native`'s own `cargo ndk` invocation picks up the +/// per-arch linker override. This can't go through `RUSTFLAGS`/ +/// `CARGO_TARGET__RUSTFLAGS`: ubrn already sets the latter itself (for +/// a 16KB-page-size flag) on every target build, which would clobber ours. +fn patch_ubrn_config_for_noir( + bindings_dir: &Path, + params: &AndroidBindingsParams, +) -> anyhow::Result<()> { + let config_path = bindings_dir.join("ubrn.config.yaml"); + let mut contents = + fs::read_to_string(&config_path).context("Failed to read ubrn.config.yaml")?; + + if let Some(min_sdk) = params.min_sdk_version { + if let Some(pos) = contents.find("apiLevel: ") { + let line_end = contents[pos..] + .find('\n') + .map(|i| pos + i) + .unwrap_or(contents.len()); + let current: u32 = contents[pos + "apiLevel: ".len()..line_end] + .trim() + .parse() + .unwrap_or(0); + if min_sdk > current { + contents.replace_range(pos..line_end, &format!("apiLevel: {min_sdk}")); + } + } + } + + let cargo_extras: Vec = params + .arch_overrides + .iter() + .filter_map(|(triple, config)| { + config + .linker + .as_ref() + .map(|linker| format!("target.{triple}.linker='{linker}'")) + }) + .flat_map(|entry| ["--config".to_string(), entry]) + .collect(); + + if !cargo_extras.is_empty() { + let yaml_list = cargo_extras + .iter() + .map(|entry| format!(" - \"{}\"", entry.replace('"', "\\\""))) + .collect::>() + .join("\n"); + contents.push_str(&format!("\n cargoExtras:\n{yaml_list}\n")); + } + + fs::write(&config_path, contents).context("Failed to write ubrn.config.yaml")?; + Ok(()) +} From 77b157a679c7ee67f32db153bac26dcef49458be Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Sun, 12 Jul 2026 22:29:40 +0800 Subject: [PATCH 4/6] Fix CI workflow conditions and add Zig setup for Noir adapter - Corrected the conditional checks in the GitHub Actions workflow to ensure proper execution during pull requests. - Introduced a new step to set up Zig for the Noir adapter, addressing ABI compatibility issues with the barretenberg prebuilt. These changes enhance the reliability of the CI process and improve the build environment for the Noir adapter. --- .github/workflows/build-and-test.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 908d9fddc..1cf1305a8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -380,7 +380,7 @@ jobs: matrix: adapter: [circom, halo2, noir, gnark] runs-on: macos-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo_full_name + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v6 @@ -390,6 +390,14 @@ jobs: with: go-version: "1.24" + # The barretenberg prebuilt is Zig-built instead of NDK + # link libc++ with Zig so it resolves. + - name: Setup Zig (for noir/barretenberg libc++ ABI) + if: matrix.adapter == 'noir' + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + # Build mopro example project for iOS + Android first, and create RN frameworks - name: Prepare template project uses: ./.github/actions/mopro-init-project @@ -488,7 +496,7 @@ jobs: matrix: adapter: [halo2] # web build only for halo2 runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo_full_name + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v6 From 16a3745b63386967aff934500a8c132baab9430e Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Mon, 13 Jul 2026 22:32:17 +0800 Subject: [PATCH 5/6] Enhance error handling in React Native project setup - Updated the error handling in `react_native.rs` to provide more context when reading configuration files fails, specifically for missing files. - Refactored the logic for reading the Gradle properties in `react_native.rs` and `react_native.rs` to use a match statement, improving clarity and robustness. - Improved error reporting in `patch_android_cmake_lists_uniffi_bindgen_resolve` and `patch_gradle_properties_architectures` functions to handle file read errors gracefully. These changes improve the reliability of the project setup process and enhance the user experience by providing clearer error messages. --- cli/src/create/react_native.rs | 24 +++++++++++++++--------- mopro-ffi/src/app_config/react_native.rs | 14 ++++++++++---- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/cli/src/create/react_native.rs b/cli/src/create/react_native.rs index f0264a3d7..d53d112a6 100644 --- a/cli/src/create/react_native.rs +++ b/cli/src/create/react_native.rs @@ -5,7 +5,7 @@ use crate::create::utils::{check_bindings, copy_dir, copy_keys, download_and_ext use crate::print::print_footer_message; use crate::style::print_green_bold; -use anyhow::{Error, Result}; +use anyhow::{Context, Error, Result}; use mopro_ffi::app_config::constants::{REACT_NATIVE_APP_DIR, REACT_NATIVE_BINDINGS_DIR}; use mopro_ffi::app_config::react_native::patch_gradle_properties_architectures; use std::{fs, path::PathBuf}; @@ -54,16 +54,22 @@ impl Create for ReactNative { // reintroduce the "missing .so, no known rule to make it" ninja // failure for ABIs that were never built. let config_path = project_dir.join("Config.toml"); - if let Ok(config) = read_config(&config_path) { - if let Some(react_native_archs) = config.react_native { - let android_archs: Vec = react_native_archs - .into_iter() - .filter(|a| a.contains("android")) - .collect(); - if !android_archs.is_empty() { - patch_gradle_properties_architectures(&project_dir, &android_archs)?; + match read_config(&config_path) { + Ok(config) => { + if let Some(react_native_archs) = config.react_native { + let android_archs: Vec = react_native_archs + .into_iter() + .filter(|a| a.contains("android")) + .collect(); + if !android_archs.is_empty() { + patch_gradle_properties_architectures(&project_dir, &android_archs)?; + } } } + Err(e) + if e.downcast_ref::() + .is_some_and(|io_err| io_err.kind() == std::io::ErrorKind::NotFound) => {} + Err(e) => return Err(e).with_context(|| format!("Failed to read {:?}", config_path)), } Self::print_message(); diff --git a/mopro-ffi/src/app_config/react_native.rs b/mopro-ffi/src/app_config/react_native.rs index 5a9cb6fc4..2ff51f146 100644 --- a/mopro-ffi/src/app_config/react_native.rs +++ b/mopro-ffi/src/app_config/react_native.rs @@ -219,8 +219,10 @@ pub fn build_for_arch( // no-op once the upstream fix ships in a published release. pub fn patch_android_cmake_lists_uniffi_bindgen_resolve(bindings_dir: &Path) -> anyhow::Result<()> { let cmake_lists_path = bindings_dir.join("android").join("CMakeLists.txt"); - let Ok(contents) = fs::read_to_string(&cmake_lists_path) else { - return Ok(()); + let contents = match fs::read_to_string(&cmake_lists_path) { + Ok(contents) => contents, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(e) => return Err(e).with_context(|| format!("Failed to read {:?}", cmake_lists_path)), }; const BROKEN: &str = "require.resolve('uniffi-bindgen-react-native/package.json')"; @@ -276,8 +278,12 @@ pub fn patch_gradle_properties_architectures( .join(REACT_NATIVE_APP_DIR) .join("android") .join("gradle.properties"); - let Ok(contents) = fs::read_to_string(&gradle_properties_path) else { - return Ok(()); + let contents = match fs::read_to_string(&gradle_properties_path) { + Ok(contents) => contents, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(e) => { + return Err(e).with_context(|| format!("Failed to read {:?}", gradle_properties_path)) + } }; const PREFIX: &str = "reactNativeArchitectures="; From 10df2fb8cb3f6998021610d3582a1404908f9161 Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Tue, 14 Jul 2026 12:05:23 +0800 Subject: [PATCH 6/6] v0.3.7 --- Cargo.lock | 4 ++-- cli/Cargo.toml | 4 ++-- cli/src/init/write_toml.rs | 6 +++--- mopro-ffi/Cargo.toml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da7d12724..aeddbb278 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1723,7 +1723,7 @@ dependencies = [ [[package]] name = "mopro-cli" -version = "0.3.7-alpha.0" +version = "0.3.7" dependencies = [ "anyhow", "clap", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mopro-ffi" -version = "0.3.7-alpha.0" +version = "0.3.7" dependencies = [ "anyhow", "camino", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ca4df6375..076820acb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mopro-cli" -version = "0.3.7-alpha.0" +version = "0.3.7" edition = "2021" description = "A CLI tool for creating a mobile native app with Mopro FFI" license = "MIT OR Apache-2.0" @@ -14,7 +14,7 @@ name = "mopro" path = "src/main.rs" [dependencies] -mopro-ffi = { path = "../mopro-ffi", version = "=0.3.7-alpha.0", features = [ +mopro-ffi = { path = "../mopro-ffi", version = "=0.3.7", features = [ "build", "uniffi", ], default-features = false } diff --git a/cli/src/init/write_toml.rs b/cli/src/init/write_toml.rs index 32371547e..1a32d866c 100644 --- a/cli/src/init/write_toml.rs +++ b/cli/src/init/write_toml.rs @@ -16,7 +16,7 @@ flutter = ["mopro-ffi/flutter"] wasm = ["mopro-ffi/wasm"] [dependencies] -mopro-ffi = { version = "=0.3.7-alpha.0" } +mopro-ffi = { version = "=0.3.7" } thiserror = "2.0.12" anyhow = "1.0.99" @@ -32,7 +32,7 @@ anyhow = "1.0.99" # GNARK_BUILD_DEPENDENCIES [dev-dependencies] -mopro-ffi = { version = "=0.3.7-alpha.0", features = ["uniffi-tests"] } +mopro-ffi = { version = "=0.3.7", features = ["uniffi-tests"] } # CIRCOM_DEV_DEPENDENCIES # HALO2_DEV_DEPENDENCIES @@ -40,7 +40,7 @@ mopro-ffi = { version = "=0.3.7-alpha.0", features = ["uniffi-tests"] } # GNARK_DEV_DEPENDENCIES [target.wasm32-unknown-unknown.dependencies] -mopro-ffi = { version = "=0.3.7-alpha.0", features = ["wasm"] } +mopro-ffi = { version = "=0.3.7", features = ["wasm"] } wasm-bindgen = "0.2" serde-wasm-bindgen = "0.6" diff --git a/mopro-ffi/Cargo.toml b/mopro-ffi/Cargo.toml index 9617ec07d..aa5f499a4 100644 --- a/mopro-ffi/Cargo.toml +++ b/mopro-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mopro-ffi" -version = "0.3.7-alpha.0" +version = "0.3.7" edition = "2021" description = "Mopro is a toolkit for ZK app development on mobile. Mopro makes client-side proving on mobile simple." license = "MIT OR Apache-2.0"