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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ mopro_flutter_bindings
MoproAndroidBindings
MoproiOSBindings
MoproReactNativeBindings
build/
build/
!cli/src/build/
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 }
Expand Down
61 changes: 60 additions & 1 deletion cli/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -192,7 +193,65 @@ 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::<ReactNativePlatform>(mode, &current_dir, arch_refs, ())?;

let ios_arch_strings: Vec<String> = arch_strings
.iter()
.filter(|a| a.contains("ios"))
.cloned()
.collect();
let android_arch_strings: Vec<String> = arch_strings
.iter()
.filter(|a| a.contains("android"))
.cloned()
.collect();

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(&current_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::<ReactNativePlatform>(mode, &current_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(&current_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(
&current_dir,
&bindings_dir,
&android_arch_strings,
mode,
&params,
)?;
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(
&current_dir,
&android_arch_strings,
)?;
}
}
Platform::Web => {
build_from_str_arch::<WebPlatform>(
Expand Down
21 changes: 14 additions & 7 deletions cli/src/build/android_noir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -41,15 +48,15 @@ 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 {
extra_env: vec![(
"BB_LIB_DIR".to_string(),
bb_lib_dir.to_string_lossy().into_owned(),
)],
extra_rustflags: vec![linker_flag],
linker: Some(linker),
},
);
}
Expand Down Expand Up @@ -145,10 +152,10 @@ fn ensure_zig_available() -> anyhow::Result<()> {
Ok(())
}

/// Write a Zig-cc linker wrapper for `triple` and return the `-Clinker=<wrapper>`
/// 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<String> {
/// 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<String> {
let ndk = android_ndk_home()?;
let host = ndk_host_tag();
let sysroot = ndk
Expand Down Expand Up @@ -204,7 +211,7 @@ fn zig_linker_flag(triple: &str, build_dir: &Path) -> anyhow::Result<String> {
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.
Expand Down
Loading
Loading