diff --git a/crates/ubrn_cli/src/codegen/mod.rs b/crates/ubrn_cli/src/codegen/mod.rs index a1fc8376..7b15b744 100644 --- a/crates/ubrn_cli/src/codegen/mod.rs +++ b/crates/ubrn_cli/src/codegen/mod.rs @@ -166,7 +166,7 @@ mod files { pub(super) fn get_files(config: Rc) -> Vec> { vec![ // typescript - IndexTs::rc_new(config.clone()), + IndexTsx::rc_new(config.clone()), // C++ TMHeader::rc_new(config.clone()), TMCpp::rc_new(config.clone()), @@ -189,7 +189,7 @@ mod files { templated_file!(JavaModule, "ModuleTemplate.java"); impl RenderedFile for JavaModule { fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf { - let name = self.config.project.name_upper_camel(); + let name = self.config.project.module_cpp(); let filename = format!("{name}Module.java"); self.config .project @@ -202,7 +202,7 @@ mod files { templated_file!(JavaPackage, "PackageTemplate.java"); impl RenderedFile for JavaPackage { fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf { - let name = self.config.project.name_upper_camel(); + let name = self.config.project.module_cpp(); let filename = format!("{name}Package.java"); self.config .project @@ -261,10 +261,10 @@ mod files { } } - templated_file!(IndexTs, "index.ts"); - impl RenderedFile for IndexTs { + templated_file!(IndexTsx, "index.tsx"); + impl RenderedFile for IndexTsx { fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf { - let filename = "index.ts"; + let filename = "index.tsx"; self.config.project.tm.ts_path(project_root).join(filename) } } @@ -295,7 +295,7 @@ mod files { templated_file!(ModuleTemplateH, "ModuleTemplate.h"); impl RenderedFile for ModuleTemplateH { fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf { - let name = self.config.project.name_upper_camel(); + let name = self.config.project.module_cpp(); let filename = format!("{name}.h"); self.config .project @@ -311,7 +311,7 @@ mod files { templated_file!(ModuleTemplateMm, "ModuleTemplate.mm"); impl RenderedFile for ModuleTemplateMm { fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf { - let name = self.config.project.name_upper_camel(); + let name = self.config.project.module_cpp(); let filename = format!("{name}.mm"); self.config .project @@ -432,7 +432,7 @@ mod tests { templated_file!(TemplateTester, "TemplateTester.txt"); impl RenderedFile for TemplateTester { fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf { - let name = self.config.project.name_upper_camel(); + let name = self.config.project.module_cpp(); let filename = format!("{name}.txt"); self.config .project @@ -454,10 +454,10 @@ mod tests { // This is hard coded into the file. If this isn't here, then the test file hasn't rendered. assert!(s.contains("hardcoded into template.")); assert_eq!( - config.project.name_upper_camel(), + config.project.module_cpp(), "MyTesterTemplateProject".to_string() ); - assert!(s.contains("name_upper_camel = MyTesterTemplateProject.")); + assert!(s.contains("module_cpp = MyTesterTemplateProject.")); assert!(s.contains("list of modules = ['NativeAlice', 'NativeBob', 'NativeCharlie']")); Ok(()) } diff --git a/crates/ubrn_cli/src/codegen/templates/CMakeLists.txt b/crates/ubrn_cli/src/codegen/templates/CMakeLists.txt index 759de225..d976034f 100644 --- a/crates/ubrn_cli/src/codegen/templates/CMakeLists.txt +++ b/crates/ubrn_cli/src/codegen/templates/CMakeLists.txt @@ -1,6 +1,6 @@ # Generated by uniffi-bindgen-react-native cmake_minimum_required(VERSION 3.9.0) -project({{ self.config.project.cpp_filename() }}) +project({{ self.config.project.module_cpp() }}) {%- let root = self.project_root() %} {%- let dir = self.config.project.bindings.cpp_path(root) %} diff --git a/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.h b/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.h index cb2cdd4a..46846fdc 100644 --- a/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.h +++ b/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.h @@ -6,11 +6,11 @@ #ifdef RCT_NEW_ARCH_ENABLED #import "{{ self.config.project.tm.name() }}.h" -@interface {{ self.config.project.name_upper_camel() }} : NSObject <{{ self.config.project.codegen_filename() }}Spec> +@interface {{ self.config.project.module_cpp() }} : NSObject <{{ self.config.project.codegen_filename() }}Spec> #else #import -@interface {{ self.config.project.name_upper_camel() }} : NSObject +@interface {{ self.config.project.module_cpp() }} : NSObject #endif @end diff --git a/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.java b/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.java index fffc7c89..7219d9bb 100644 --- a/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.java +++ b/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.java @@ -1,5 +1,5 @@ {%- let android = self.config.project.android.clone() %} -{%- let name = self.config.project.name_upper_camel() %} +{%- let name = self.config.project.module_cpp() %} {%- let module_class_name = name|fmt("{}Module") -%} // Generated by uniffi-bindgen-react-native package {{ android.package_name() }}; diff --git a/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.mm b/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.mm index fffc3795..40dacd3c 100644 --- a/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.mm +++ b/crates/ubrn_cli/src/codegen/templates/ModuleTemplate.mm @@ -1,4 +1,4 @@ -{%- let module_name = self.config.project.name_upper_camel() %} +{%- let module_name = self.config.project.module_cpp() %} {%- let spec_jsi = self.config.project.tm.spec_name()|fmt("{}JSI") %} {%- let ns = self.config.project.cpp_namespace() %} {%- let uniffi_ns = "uniffi_generated" %} @@ -40,6 +40,15 @@ @implementation {{ module_name }} // Don't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED + +// Automated testing checks {{ ns }} +// by comparing the whole line here. +/* +- (NSNumber *)multiply:(double)a b:(double)b { + NSNumber *result = @({{ ns }}::multiply(a, b)); +} +*/ + - (NSNumber *)installRustCrate { @throw [NSException exceptionWithName:@"UnreachableException" reason:@"This method should never be called." diff --git a/crates/ubrn_cli/src/codegen/templates/NativeCodegenTemplate.ts b/crates/ubrn_cli/src/codegen/templates/NativeCodegenTemplate.ts index 0b2e69be..ea3cc058 100644 --- a/crates/ubrn_cli/src/codegen/templates/NativeCodegenTemplate.ts +++ b/crates/ubrn_cli/src/codegen/templates/NativeCodegenTemplate.ts @@ -7,4 +7,4 @@ export interface Spec extends TurboModule { cleanupRustCrate(): boolean; } -export default TurboModuleRegistry.getEnforcing('{{ self.config.project.name_upper_camel() }}'); +export default TurboModuleRegistry.getEnforcing('{{ self.config.project.spec_name() }}'); diff --git a/crates/ubrn_cli/src/codegen/templates/PackageTemplate.java b/crates/ubrn_cli/src/codegen/templates/PackageTemplate.java index aac54712..dfaf4268 100644 --- a/crates/ubrn_cli/src/codegen/templates/PackageTemplate.java +++ b/crates/ubrn_cli/src/codegen/templates/PackageTemplate.java @@ -1,4 +1,4 @@ -{%- let name = self.config.project.name_upper_camel() %} +{%- let name = self.config.project.module_cpp() %} {%- let package_class_name = name|fmt("{}Package") %} {%- let module_class_name = name|fmt("{}Module") -%} // Generated by uniffi-bindgen-react-native diff --git a/crates/ubrn_cli/src/codegen/templates/TemplateTester.txt b/crates/ubrn_cli/src/codegen/templates/TemplateTester.txt index d9760cc0..4b4a3b12 100644 --- a/crates/ubrn_cli/src/codegen/templates/TemplateTester.txt +++ b/crates/ubrn_cli/src/codegen/templates/TemplateTester.txt @@ -1,5 +1,5 @@ hardcoded into template. -name_upper_camel = {{ self.config.project.name_upper_camel() }}. +module_cpp = {{ self.config.project.module_cpp() }}. list of modules = [ {%- for m in self.config.modules %}' diff --git a/crates/ubrn_cli/src/codegen/templates/build.gradle b/crates/ubrn_cli/src/codegen/templates/build.gradle index 63ff7619..7cc4b8d3 100644 --- a/crates/ubrn_cli/src/codegen/templates/build.gradle +++ b/crates/ubrn_cli/src/codegen/templates/build.gradle @@ -1,5 +1,5 @@ // Generated by uniffi-bindgen-react-native -{%- let name = self.config.project.name_upper_camel() %} +{%- let name = self.config.project.module_cpp() %} {%- let package_name = self.config.project.android.package_name() %} buildscript { diff --git a/crates/ubrn_cli/src/codegen/templates/cpp-adapter.cpp b/crates/ubrn_cli/src/codegen/templates/cpp-adapter.cpp index 994bb7e0..d8d1c1a1 100644 --- a/crates/ubrn_cli/src/codegen/templates/cpp-adapter.cpp +++ b/crates/ubrn_cli/src/codegen/templates/cpp-adapter.cpp @@ -4,7 +4,7 @@ #include #include "{{ self.config.project.cpp_filename() }}.h" {%- let package_name = self.config.project.android.package_name().replace(".", "_") %} -{%- let name = self.config.project.name_upper_camel() %} +{%- let name = self.config.project.module_cpp() %} {%- let module_class_name = name|fmt("{}Module") %} {%- let prefix = "Java_{}_{}"|format(package_name, module_class_name) %} {%- let ns = self.config.project.cpp_namespace() %} @@ -12,6 +12,14 @@ namespace jsi = facebook::jsi; namespace react = facebook::react; +// Automated testing checks {{ prefix }} and {{ ns }} +// by comparing the whole line here. +/* +{{ prefix }}_nativeMultiply(JNIEnv *env, jclass type, jdouble a, jdouble b) { + return {{ ns }}::multiply(a, b); +} +*/ + // Installer coming from {{ module_class_name }} extern "C" JNIEXPORT jboolean JNICALL diff --git a/crates/ubrn_cli/src/codegen/templates/index.ts b/crates/ubrn_cli/src/codegen/templates/index.tsx similarity index 100% rename from crates/ubrn_cli/src/codegen/templates/index.ts rename to crates/ubrn_cli/src/codegen/templates/index.tsx diff --git a/crates/ubrn_cli/src/config/mod.rs b/crates/ubrn_cli/src/config/mod.rs index f3339e47..3fda1216 100644 --- a/crates/ubrn_cli/src/config/mod.rs +++ b/crates/ubrn_cli/src/config/mod.rs @@ -7,6 +7,7 @@ mod npm; use camino::{Utf8Path, Utf8PathBuf}; use globset::GlobSet; +use heck::ToUpperCamelCase; pub(crate) use npm::PackageJson; use serde::Deserialize; @@ -55,15 +56,21 @@ impl ProjectConfig { } } -fn trim_react_native(name: &str) -> String { +fn trim(name: &str) -> String { name.trim_matches('-').trim_matches('_').to_string() } -fn trim_react_native_2(name: &str) -> String { - name.strip_prefix("RN") - .unwrap_or(name) - .replace("ReactNative", "") - .replace("react-native", "") +#[allow(dead_code)] +fn trim_rn(name: &str) -> String { + trim_react_native(strip_prefix(name, "RN")) +} + +fn strip_prefix<'a>(name: &'a str, prefix: &str) -> &'a str { + name.strip_prefix(prefix).unwrap_or(name) +} + +pub(crate) fn trim_react_native(name: &str) -> String { + strip_prefix(strip_prefix(name, "ReactNative"), "react-native") .trim_matches('-') .trim_matches('_') .to_string() @@ -73,13 +80,13 @@ impl ProjectConfig { pub(crate) fn project_root(&self) -> &Utf8Path { &self.crate_.project_root } -} -impl ProjectConfig { - fn name(&self) -> String { - trim_react_native(&self.name) + pub(crate) fn module_cpp(&self) -> String { + trim_react_native(&self.name).to_upper_camel_case() } +} +impl ProjectConfig { pub(crate) fn raw_name(&self) -> &str { &self.name } @@ -88,13 +95,10 @@ impl ProjectConfig { &self.repository } - pub(crate) fn name_upper_camel(&self) -> String { - use heck::ToUpperCamelCase; - self.name().to_upper_camel_case() - } - pub(crate) fn cpp_namespace(&self) -> String { - self.name_upper_camel().to_lowercase() + trim_react_native(&self.name) + .to_upper_camel_case() + .to_lowercase() } pub(crate) fn cpp_filename(&self) -> String { @@ -103,7 +107,11 @@ impl ProjectConfig { } pub(crate) fn codegen_filename(&self) -> String { - format!("Native{}", self.name_upper_camel()) + format!("Native{}", self.spec_name()) + } + + pub(crate) fn spec_name(&self) -> String { + trim_react_native(&self.name).to_upper_camel_case() } pub(crate) fn exclude_files(&self) -> &GlobSet { @@ -185,7 +193,7 @@ impl TurboModulesConfig { fn default_spec_name() -> String { let package_json = workspace::package_json(); let codegen_name = &package_json.codegen().name; - trim_react_native(codegen_name) + trim(codegen_name) } } diff --git a/crates/ubrn_cli/src/config/npm.rs b/crates/ubrn_cli/src/config/npm.rs index 641af69a..3a64056f 100644 --- a/crates/ubrn_cli/src/config/npm.rs +++ b/crates/ubrn_cli/src/config/npm.rs @@ -7,7 +7,7 @@ use heck::ToUpperCamelCase; use serde::Deserialize; -use super::{trim_react_native, trim_react_native_2}; +use super::{trim, trim_react_native}; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] @@ -26,7 +26,7 @@ impl PackageJson { } pub(crate) fn name(&self) -> String { - trim_react_native(&self.name) + trim(&self.name) } pub(crate) fn android_package_name(&self) -> String { @@ -37,7 +37,7 @@ impl PackageJson { .unwrap_or_else(|| { format!( "com.{}", - trim_react_native_2(&self.name) + trim_react_native(&self.name) .to_upper_camel_case() .to_lowercase() ) diff --git a/crates/ubrn_cli/src/ios.rs b/crates/ubrn_cli/src/ios.rs index ca0dfbb1..53a6a15f 100644 --- a/crates/ubrn_cli/src/ios.rs +++ b/crates/ubrn_cli/src/ios.rs @@ -9,13 +9,12 @@ use std::{collections::HashMap, fmt::Display, process::Command, str::FromStr}; use anyhow::{Context, Error, Result}; use camino::{Utf8Path, Utf8PathBuf}; use clap::Args; -use heck::ToUpperCamelCase; use serde::{Deserialize, Serialize}; use ubrn_common::{mk_dir, rm_dir, run_cmd, CrateMetadata}; use crate::{ building::{CommonBuildArgs, ExtraArgs}, - config::ProjectConfig, + config::{trim_react_native, ProjectConfig}, rust::CrateConfig, workspace, }; @@ -47,7 +46,7 @@ impl IOsConfig { fn default_framework_name() -> String { format!( "{}Framework", - workspace::package_json().name().to_upper_camel_case() + trim_react_native(&workspace::package_json().name()) ) } diff --git a/docs/src/reference/turbo-module-files.md b/docs/src/reference/turbo-module-files.md index 7cd2373c..69433ef8 100644 --- a/docs/src/reference/turbo-module-files.md +++ b/docs/src/reference/turbo-module-files.md @@ -7,7 +7,7 @@ There is a host of smaller files that need to be configured with these namespace These include: - For Javascript: - - An `index.ts` file, to call into the installation process, initialize the bindings for each namespace, and re-export the generated bindings for client code. + - An `index.tsx` file, to call into the installation process, initialize the bindings for each namespace, and re-export the generated bindings for client code. - A Codegen file, to generates install methods from Javascript to Java and Objective C. - For Android: - A `Package.java` and `Module.java` file, which receives the codegen'd install method calls, to get the Hermes `JavascriptRuntime` and `CallInvokerHolder` to pass it via JNI to @@ -21,3 +21,5 @@ These include: - To build for Android - A `CMakeLists.txt` file to configure the Android specific tool chain for all the generated C++ files. - The `build.gradle` file which tells keeps the codegen package name in-sync and configures `cmake`. (note to self, this could be done from within the `CMakeLists.txt` file). + +An up-to-date list can be found in [`ubrn_cli/src/codegen/templates`](https://github.com/jhugman/uniffi-bindgen-react-native/tree/main/crates/ubrn_cli/src/codegen/templates). diff --git a/integration/fixtures/turbo-module-testing/ubrn.config.yaml b/integration/fixtures/turbo-module-testing/ubrn.config.yaml new file mode 100644 index 00000000..61ae5610 --- /dev/null +++ b/integration/fixtures/turbo-module-testing/ubrn.config.yaml @@ -0,0 +1,4 @@ +rust: + repo: https://github.com/ianthetechie/uniffi-starter + branch: main + manifestPath: rust/foobar/Cargo.toml diff --git a/scripts/test-turbo-modules.sh b/scripts/test-turbo-modules.sh new file mode 100755 index 00000000..580749a4 --- /dev/null +++ b/scripts/test-turbo-modules.sh @@ -0,0 +1,433 @@ +#!/bin/bash +set -e +ROOT= +UBRN_BIN= +PWD= + +reset_args() { + PROJECT_DIR=my-test-library + KEEP_ROOT_ON_ERROR=false + BOB_VERSION=latest + PROJECT_SLUG=my-test-library + FORCE_NEW_DIR=false + IOS_NAME=MyTestLibrary + SKIP_IOS=false + SKIP_ANDROID=false + UBRN_CONFIG= +} + +usage() { + echo "Usage: $0 [options] [PROJECT_DIR]" + echo "" + echo "Options:" + echo " -A, --skip-android Skip building for Android." + echo " -I, --skip-ios Skip building for iOS." + echo " -C, --ubrn-config Use a ubrn config file." + + echo " -u, --builder-bob-version VERSION Specify the version of builder-bob to use." + echo " -s, --slug PROJECT_SLUG Specify the project slug." + echo " -i, --ios-name IOS_NAME Specify the iOS project name." + echo " -k, --keep-directory-on-exit Keep the PROJECT_DIR directory even if an error occurs." + echo " -f, --force-new-directory If PROJECT_DIR directory exist, remove it first." + echo " -h, --help Display this help message." + echo "" + echo "Arguments:" + echo " PROJECT_DIR Specify the root directory for the project (default: my-test-library)." +} + +cleanup() { + echo "Removing $PROJECT_DIR..." + rm -rf "$PROJECT_DIR" + cd "$PWD" +} + +diagnostics() { + echo "-- PROJECT_DIR = $PROJECT_DIR" + echo "-- PROJECT_SLUG = $PROJECT_SLUG" + echo "-- IOS_NAME = $IOS_NAME" +} + +error() { + if [ "$KEEP_ROOT_ON_ERROR" == false ] && [ -d "$PROJECT_DIR" ]; then + cleanup + fi + diagnostics + echo "❌ Error: $1" + exit 1 +} + +find_git_project_root() { + git rev-parse --show-toplevel 2>/dev/null || { + echo "Project root not found" >&2 + return 1 + } +} + +derive_paths() { + ROOT=$(find_git_project_root) + UBRN_BIN="$ROOT/bin/cli" + PWD=$(pwd) +} + +parse_cli_options() { + reset_args + # Parse command line options + while [ $# -gt 0 ]; do + case "$1" in + -u|--builder-bob-version) + BOB_VERSION="$2" + shift + ;; + -s|--slug) + PROJECT_SLUG="$2" + shift + ;; + -i|--ios-name) + IOS_NAME="$2" + shift + ;; + -C|--ubrn-config) + local config_file + config_file="$2" + if [[ "$config_file" = /* ]] ; then + UBRN_CONFIG="$config_file" + else + UBRN_CONFIG="$PWD/$config_file" + fi + shift + ;; + -k|--keep-directory-on-exit) + KEEP_ROOT_ON_ERROR=true + ;; + -f|--force-new-directory) + FORCE_NEW_DIR=true + ;; + -A|--skip-android) + SKIP_ANDROID=true + ;; + -I|--skip-ios) + SKIP_IOS=true + ;; + -h|--help) + usage + exit 0 + ;; + -*) + KEEP_ROOT_ON_ERROR=true + error "Bad argument: $1" + ;; + *) + PROJECT_DIR="$1" + ;; + esac + shift + done + # Ensure PROJECT_DIR is specified + if [ -z "$PROJECT_DIR" ]; then + PROJECT_DIR=my-test-library + fi +} + +enter_dir() { + local dir=$1 + pushd "$dir" >/dev/null || error "Cannot enter $dir" +} + +exit_dir() { + popd >/dev/null || error "Cannot exit directory" +} + +create_library() { + local directory + local base + directory=$(dirname "$PROJECT_DIR") + base=$(basename "$PROJECT_DIR") + if [ ! -d "$directory" ]; then + mkdir -p "$directory" || error "Cannot create $directory" + fi + + enter_dir "$directory" + + if [ "$FORCE_NEW_DIR" == true ] && [ -d "$base" ]; then + rm -rf "$base" || error "Failed to remove existing directory $base" + fi + + local example_type + if [ "$BOB_VERSION" == "latest" ] ; then + example_type=test-app + fi + echo "-- Creating library $PROJECT_SLUG with create-react-native-library@$BOB_VERSION" + npx "create-react-native-library@$BOB_VERSION" \ + --slug "$PROJECT_SLUG" \ + --description "An automated test" \ + --author-name "James" \ + --author-email "noop@nomail.com" \ + --author-url "https://nowhere.com/james" \ + --repo-url "https://github.com/jhugman/uniffi-bindgen-react-native" \ + --languages cpp \ + --type module-new \ + --example $example_type \ + "$base" > /dev/null || error "Failed to create library in $PROJECT_DIR" + exit_dir +} + +install_dependencies() { + enter_dir "$PROJECT_DIR" + # touch yarn.lock + yarn || error "Failed to install dependencies" + # rm yarn.lock + exit_dir +} + +install_example_dependencies() { + enter_dir "$PROJECT_DIR/example" + # touch yarn.lock + yarn || error "Failed to install example dependencies" + # rm yarn.lock + # rm -Rf .yarn + exit_dir +} + +check_deleted_files() { + local extensions="$1" + local deleted_files + echo "-- Checking for deleted files with extensions $extensions" + deleted_files=$(git status --porcelain | grep '^ D' || true | grep -E "\\.(${extensions// /|})$" || true ) + + if [ -n "$deleted_files" ]; then + echo "Error: The following files have been deleted:" + echo "$deleted_files" + error + fi +} + +check_line_unchanged() { + local file_pattern="$1" + local search_string="$2" + # Find all files matching the pattern + local files + files=$(find . -path "$file_pattern") + for file_path in $files; do + # Get the current content of the line containing the search string + current_line=$(grep -E "$search_string" "$file_path" || true) + # Get the content of the line containing the search string from the last commit + last_commit_line=$(git show HEAD:"$file_path" | grep -E "$search_string" || true) + + # Compare the current line with the line from the last commit + if [ "$current_line" != "$last_commit_line" ]; then + error "$file_path: found line with \"$search_string\" to have changed" + fi + done +} + +check_lines() { + echo "-- Checking for unmodified lines in generated code" + check_line_unchanged "./cpp/*.h" "#ifndef" + check_line_unchanged "./cpp/*.h" "^namespace" + check_line_unchanged "./cpp/*.cpp" ".h\"" + check_line_unchanged "./cpp/*.cpp" "^namespace" + check_line_unchanged "./src/Native*" "getEnforcing" + + check_line_unchanged "./android/CMakeLists.txt" "^project" + check_line_unchanged "./android/build.gradle" "return rootProject" + check_line_unchanged "./android/build.gradle" "libraryName" + check_line_unchanged "./android/src/*/*Package*" "package" + check_line_unchanged "./android/src/*/*Module*" "System.loadLibrary" + check_line_unchanged "./android/src/*/*Module*" "@ReactModule" + check_line_unchanged "./android/src/*/*Module*" "package" + check_line_unchanged "./android/src/*/*Module*" "public class" + check_line_unchanged "./android/cpp-adapter.cpp" "#include \"" + check_line_unchanged "./android/cpp-adapter.cpp" "nativeMultiply" + check_line_unchanged "./android/cpp-adapter.cpp" "::multiply" + + check_line_unchanged "./ios/*.h" "#import" + check_line_unchanged "./ios/*.h" "Spec.h" + check_line_unchanged "./ios/*.h" "/dev/null + echo "-- Running ubrn generate turbo-module" + "$UBRN_BIN" generate turbo-module --config "$UBRN_CONFIG" fake_module + + local jvm_lang + if [ "$BOB_VERSION" == "latest" ] ; then + jvm_lang=kt + else + jvm_lang=java + fi + check_deleted_files "$jvm_lang h mm ts podspec tsx" + check_lines + + exit_dir +} + +generate_turbo_module_for_compiling() { + enter_dir "$PROJECT_DIR" + echo "-- Running ubrn checkout" + clean_turbo_modules + "$UBRN_BIN" checkout --config "$UBRN_CONFIG" + exit_dir +} + +copy_into_node_modules() { + # Source and destination directories + local SRC_DIR="$ROOT" + local DEST_DIR="$PROJECT_DIR/node_modules/uniffi-bindgen-react-native" + + # Use rsync to copy contents, excluding cpp_modules and rust_modules directories + rsync -av \ + --exclude '.git' \ + --exclude 'cpp_modules' \ + --exclude 'rust_modules' \ + --exclude 'build' \ + --exclude 'target' \ + "$SRC_DIR/" "$DEST_DIR/" +} + +build_android_example() { + enter_dir "$PROJECT_DIR" + echo "-- Running ubrn build" + "$UBRN_BIN" build android --config "$UBRN_CONFIG" --and-generate --targets aarch64-linux-android + exit_dir + enter_dir "$PROJECT_DIR/example/android" + ./gradlew build || error "Failed to build Android example" + exit_dir +} + +build_ios_example() { + enter_dir "$PROJECT_DIR" + echo "-- Running ubrn build" + "$UBRN_BIN" build ios --config "$UBRN_CONFIG" --and-generate --targets aarch64-apple-ios-sim + exit_dir + enter_dir "$PROJECT_DIR/example/ios" + echo "pod 'uniffi-bindgen-react-native', :path => '../../node_modules/uniffi-bindgen-react-native'" >> Podfile + pod install || error "Cannot run Podfile" + + # Find the UDID of the first booted device, or fall back to the first available device + udid=$(xcrun simctl list --json devices | jq -r '.devices | to_entries | .[].value | map(select(.state == "Booted")) | .[0].udid') + if [ "$udid" == "null" ]; then + udid=$(xcrun simctl list --json devices | jq -r '.devices | to_entries | .[].value | map(select(.isAvailable == true)) | .[0].udid') + fi + + if [ "$udid" == "null" ]; then + error "No available iOS simulator found" + fi + + xcodebuild -workspace "${IOS_NAME}Example.xcworkspace" -scheme "${IOS_NAME}Example" -configuration Debug -destination "id=$udid" || error "Failed to build iOS example" + exit_dir +} + +main() { + parse_cli_options "$@" + echo "ℹ️ Starting $PROJECT_SLUG" + create_library + if [ "$SKIP_ANDROID" == false ] || [ "$SKIP_IOS" == false ]; then + generate_turbo_module_for_compiling + install_dependencies + install_example_dependencies + copy_into_node_modules + else + generate_turbo_module_for_diffing + fi + if [ "$SKIP_ANDROID" == false ]; then + build_android_example + fi + if [ "$SKIP_IOS" == false ]; then + build_ios_example + fi + cleanup + echo "✅ Success!" +} + +run_default() { + local fixture_dir="$ROOT/integration/fixtures/turbo-module-testing" + local working_dir="/tmp/turbomodule-tests" + local config="$fixture_dir/ubrn.config.yaml" + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --skip-ios \ + --skip-android \ + --slug dummy-lib \ + "$working_dir/dummy-lib" + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --skip-ios \ + --skip-android \ + --slug rn-dummy-lib \ + "$working_dir/rn-dummy-lib" + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --skip-ios \ + --skip-android \ + --slug react-native-dummy-lib \ + "$working_dir/react-native-dummy-lib" + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --skip-ios \ + --skip-android \ + --slug dummy-lib-react-native \ + "$working_dir/dummy-lib-react-native" + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --skip-ios \ + --skip-android \ + --slug dummy-lib-react-native \ + "$working_dir/dummy-lib-rn" + # ReactNativeDummyLib fails with "› Must be a valid npm package name" + local os + os=$(uname -o) + if [ "$os" == "Darwin" ] ; then + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --slug react-native-dummy-lib-for-ios \ + --skip-android \ + --ios-name DummyLibForIos \ + "$working_dir/react-native-dummy-lib-for-ios" + fi + main \ + --force-new-directory \ + --keep-directory-on-exit \ + --ubrn-config "$config" \ + --builder-bob-version 0.35.1 \ + --slug react-native-dummy-lib-for-android \ + --skip-ios \ + "$working_dir/react-native-dummy-lib-for-android" +} + +derive_paths +# Check if there are no command line arguments +if [ $# -eq 0 ]; then + run_default +else + main "$@" +fi