diff --git a/.github/workflows/compat.yml b/.github/workflows/compat.yml new file mode 100644 index 00000000..ee7c4daa --- /dev/null +++ b/.github/workflows/compat.yml @@ -0,0 +1,60 @@ +name: Compat + +on: + schedule: + - cron: "0 0 * * *" # Once a day at midnight + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +jobs: + integration-tests-generation: + strategy: + matrix: + runner: + - macos-latest + - ubuntu-latest + bob-version: + - 0.42.2 + - 0.42.3 + - latest + rn-version: + - 0.76.0 + - latest + + runs-on: ${{ matrix.runner }} + name: "bob ${{ matrix.bob-version }} / rn ${{ matrix.rn-version }} / ${{ matrix.runner == 'macos-latest' && 'ios' || 'android' }}" + + steps: + - uses: actions/checkout@v4 + + - name: Install cargo-ndk + if: ${{ matrix.runner != 'macos-latest' }} + run: | + cargo install cargo-ndk + + - name: Install JDK + if: ${{ matrix.runner != 'macos-latest' }} + uses: actions/setup-java@v3 + with: + distribution: "zulu" + java-version: "17" + + - name: Install Rust toolchains + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.runner == 'macos-latest' && 'aarch64-apple-ios-sim' || 'aarch64-linux-android' }} + + - name: Generate & build turbo module + run: | + ./scripts/test-turbo-modules.sh \ + --slug '@my-org/my-lib' \ + --ubrn-config integration/fixtures/compat/ubrn.config.yaml \ + --builder-bob-version ${{ matrix.bob-version }} \ + --rn-version ${{ matrix.rn-version }} \ + --packgage-json-mixin integration/fixtures/compat/package.json \ + --react-native-config integration/fixtures/compat/react-native.config.js \ + --${{ matrix.runner == 'macos-latest' && 'ios' || 'android' }} \ + ../turbo-module diff --git a/README.md b/README.md index c46a2941..17fde123 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![CI](https://github.com/jhugman/uniffi-bindgen-react-native/actions/workflows/ci.yml/badge.svg)](https://github.com/jhugman/uniffi-bindgen-react-native/actions/workflows/ci.yml) +[![build-bob compatibility](https://github.com/jhugman/uniffi-bindgen-react-native/actions/workflows/compat.yml/badge.svg)](https://github.com/jhugman/uniffi-bindgen-react-native/actions/workflows/compat.yml) # uniffi-bindgen-react-native [UniFFI](https://mozilla.github.io/uniffi-rs/latest/) is a multi-language bindings generator for Rust. diff --git a/crates/ubrn_cli/src/android.rs b/crates/ubrn_cli/src/android.rs index 794ed05c..b42df382 100644 --- a/crates/ubrn_cli/src/android.rs +++ b/crates/ubrn_cli/src/android.rs @@ -39,6 +39,9 @@ pub(crate) struct AndroidConfig { #[serde(default = "AndroidConfig::default_package_name")] pub(crate) package_name: String, + + #[serde(default = "AndroidConfig::default_codegen_output_dir")] + pub(crate) codegen_output_dir: String, } impl Default for AndroidConfig { @@ -82,6 +85,10 @@ impl AndroidConfig { fn default_jni_libs() -> String { "src/main/jniLibs".to_string() } + + fn default_codegen_output_dir() -> String { + workspace::package_json().android_codegen_output_dir() + } } impl AndroidConfig { @@ -89,6 +96,10 @@ impl AndroidConfig { project_root.join(&self.directory) } + pub(crate) fn codegen_output_dir(&self, project_root: &Utf8Path) -> Utf8PathBuf { + project_root.join(&self.codegen_output_dir) + } + pub(crate) fn jni_libs(&self, project_root: &Utf8Path) -> Utf8PathBuf { self.directory(project_root).join(&self.jni_libs) } diff --git a/crates/ubrn_cli/src/codegen/mod.rs b/crates/ubrn_cli/src/codegen/mod.rs index d71f7ab5..45b76dd5 100644 --- a/crates/ubrn_cli/src/codegen/mod.rs +++ b/crates/ubrn_cli/src/codegen/mod.rs @@ -470,6 +470,7 @@ mod tests { cargo_extras: ExtraArgs::default(), api_level: 21, package_name: "com.tester".to_string(), + codegen_output_dir: "android/generated".to_string(), }; let ios = IOsConfig { directory: "ios".to_string(), @@ -477,6 +478,7 @@ mod tests { xcodebuild_extras: ExtraArgs::default(), targets: Default::default(), cargo_extras: ExtraArgs::default(), + codegen_output_dir: "ios/generated".to_string(), }; let bindings = BindingsConfig { cpp: "cpp/bindings".to_string(), diff --git a/crates/ubrn_cli/src/codegen/templates/build.kt.gradle b/crates/ubrn_cli/src/codegen/templates/build.kt.gradle index 6e37fff2..ad9d0a8d 100644 --- a/crates/ubrn_cli/src/codegen/templates/build.kt.gradle +++ b/crates/ubrn_cli/src/codegen/templates/build.kt.gradle @@ -117,8 +117,11 @@ android { main { if (isNewArchitectureEnabled()) { java.srcDirs += [ - "generated/java", - "generated/jni" + {%- let root = self.project_root() %} + {%- let dir = self.config.project.android.codegen_output_dir(root) %} + {%- let codegen = self.relative_to(root, dir) %} + "{{ codegen }}/java", + "{{ codegen }}/jni" ] } } diff --git a/crates/ubrn_cli/src/codegen/templates/module-template.podspec b/crates/ubrn_cli/src/codegen/templates/module-template.podspec index 0999ca89..a39d3194 100644 --- a/crates/ubrn_cli/src/codegen/templates/module-template.podspec +++ b/crates/ubrn_cli/src/codegen/templates/module-template.podspec @@ -24,11 +24,13 @@ Pod::Spec.new do |s| {%- let framework = self.relative_to(root, dir) %} {%- let dir = self.config.project.ios.directory(root) %} {%- let ios = self.relative_to(root, dir) %} + {%- let dir = self.config.project.ios.codegen_output_dir(root) %} + {%- let codegen = self.relative_to(root, dir) %} {%- let dir = self.config.project.tm.cpp_path(root) %} {%- let tm = self.relative_to(root, dir) %} {%- let dir = self.config.project.bindings.cpp_path(root) %} {%- let bindings = self.relative_to(root, dir) -%} - s.source_files = "{{ ios }}/**/*.{h,m,mm}", "{{ tm }}/**/*.{hpp,cpp,c,h}", "{{ bindings }}/**/*.{hpp,cpp,c,h}" + s.source_files = "{{ ios }}/**/*.{h,m,mm}", "{{ codegen }}/**/*.{h,m,mm}", "{{ tm }}/**/*.{hpp,cpp,c,h}", "{{ bindings }}/**/*.{hpp,cpp,c,h}" s.vendored_frameworks = "{{ framework }}" # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. diff --git a/crates/ubrn_cli/src/config/npm.rs b/crates/ubrn_cli/src/config/npm.rs index 956b193f..6c522192 100644 --- a/crates/ubrn_cli/src/config/npm.rs +++ b/crates/ubrn_cli/src/config/npm.rs @@ -47,6 +47,14 @@ impl PackageJson { } } + pub(crate) fn android_codegen_output_dir(&self) -> String { + self.codegen_config.output_dir.android.clone() + } + + pub(crate) fn ios_codegen_output_dir(&self) -> String { + self.codegen_config.output_dir.ios.clone() + } + pub(crate) fn repo(&self) -> &PackageJsonRepo { &self.repository } @@ -69,6 +77,8 @@ pub(crate) struct RnCodegenConfig { pub(crate) js_srcs_dir: String, #[serde(default)] android: RnAndroidCodegenConfig, + #[serde(default)] + output_dir: RnOutputDirCodegenConfig, } impl Default for RnCodegenConfig { @@ -82,3 +92,29 @@ impl Default for RnCodegenConfig { struct RnAndroidCodegenConfig { java_package_name: Option, } + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RnOutputDirCodegenConfig { + #[serde(default = "default_ios_codegen_output_dir")] + ios: String, + #[serde(default = "default_android_codegen_output_dir")] + android: String, +} + +impl Default for RnOutputDirCodegenConfig { + fn default() -> Self { + Self { + ios: default_ios_codegen_output_dir(), + android: default_android_codegen_output_dir(), + } + } +} + +fn default_android_codegen_output_dir() -> String { + "android/generated".to_string() +} + +fn default_ios_codegen_output_dir() -> String { + "ios/generated".to_string() +} diff --git a/crates/ubrn_cli/src/ios.rs b/crates/ubrn_cli/src/ios.rs index 992acd82..eaa33e2f 100644 --- a/crates/ubrn_cli/src/ios.rs +++ b/crates/ubrn_cli/src/ios.rs @@ -37,6 +37,9 @@ pub(crate) struct IOsConfig { #[serde(default = "IOsConfig::default_cargo_extras")] pub(crate) cargo_extras: ExtraArgs, + + #[serde(default = "IOsConfig::default_codegen_output_dir")] + pub(crate) codegen_output_dir: String, } impl IOsConfig { @@ -74,6 +77,10 @@ impl IOsConfig { let args: &[&str] = &["aarch64-apple-ios", sim_target]; args.iter().map(|s| Target::from_str(s).unwrap()).collect() } + + fn default_codegen_output_dir() -> String { + workspace::package_json().ios_codegen_output_dir() + } } impl Default for IOsConfig { @@ -87,6 +94,10 @@ impl IOsConfig { project_root.join(&self.directory) } + pub(crate) fn codegen_output_dir(&self, project_root: &Utf8Path) -> Utf8PathBuf { + project_root.join(&self.codegen_output_dir) + } + pub(crate) fn framework_path(&self, project_root: &Utf8Path) -> Utf8PathBuf { let filename = format!("{}.xcframework", self.framework_name); project_root.join(filename) diff --git a/docs/src/reference/config-yaml.md b/docs/src/reference/config-yaml.md index a4ab07bb..6f208782 100644 --- a/docs/src/reference/config-yaml.md +++ b/docs/src/reference/config-yaml.md @@ -77,6 +77,7 @@ android: apiLevel: 21 jniLibs: src/main/jniLibs packageName: + codegenOutputDir: ``` The `directory` is the location of the Android project, relative to the root of the React Native library project. @@ -91,10 +92,16 @@ The `directory` is the location of the Android project, relative to the root of Reducing the number of targets to build for will speed up the edit-compile-run cycle. ``` -`packageName` is the name of the Android package that Codegen used to generate the TurboModule. This is derived from the `package.json` file, and can almost always be left. +`packageName` is the name of the Android package that Codegen used to generate the TurboModule. `codegenOutputDir` is the path under which Codegen stores its generated files. Both are derived from the `package.json` file, and can almost always be left. To customize the `packageName`, you should edit or add the entry at the path `codegenConfig`/`android`/`javaPackageName` in `package.json`. +To customize the `codegenOutputDir`, you should edit or add the entry at the path `codegenConfig`/`outputDir`/`android` in `package.json`. + +```admonish warning +Note that for Android the `outputDir` value in `package.json` needs to have a matching entry under `dependency`/`platforms`/`android`/`cmakeListsPath` in `react-native.config.js`. For example, if you set the Android output directory in `package.json` to `android/tmp`, the `cmakeListsPath` value in `react-native.config.js` needs to be set to `tmp/jni/CMakeLists.txt`. +``` + ## `ios` This is to configure the build steps for the Rust, the bindings, and the turbo-module code for iOS. @@ -110,6 +117,7 @@ ios: - aarch64-apple-ios-sim xcodebuildExtras: [] frameworkName: build/MyFramework + codegenOutputDir: ``` @@ -121,6 +129,14 @@ The `directory` is the location of the iOS project, relative to the root of the `xcodebuildExtras` is a list of extra arguments passed directly to the `xcodebuild` command. +`codegenOutputDir` is the path under which Codegen stores its generated files. This is derived from the `package.json` file, and can almost always be left. + +To customize the `codegenOutputDir`, you should edit or add the entry at the path `codegenConfig`/`outputDir`/`ios` in `package.json`. + +```admonish warning +Note that for Android the `outputDir` value in `package.json` needs to have a matching entry under `dependency`/`platforms`/`android`/`cmakeListsPath` in `react-native.config.js`. For example, if you set the Android output directory in `package.json` to `android/tmp`, the `cmakeListsPath` value in `react-native.config.js` needs to be set to `tmp/jni/CMakeLists.txt`. +``` + ## `turboModule` This section configures the location of the Typescript and C++ files generated by the `generate turbo-module` command. diff --git a/integration/fixtures/compat/package.json b/integration/fixtures/compat/package.json new file mode 100644 index 00000000..40695e7d --- /dev/null +++ b/integration/fixtures/compat/package.json @@ -0,0 +1,8 @@ +{ + "codegenConfig": { + "outputDir": { + "ios": "ios/tmp", + "android": "android/tmp" + } + } +} diff --git a/integration/fixtures/compat/react-native.config.js b/integration/fixtures/compat/react-native.config.js new file mode 100644 index 00000000..ee0de016 --- /dev/null +++ b/integration/fixtures/compat/react-native.config.js @@ -0,0 +1,9 @@ +module.exports = { + dependency: { + platforms: { + android: { + cmakeListsPath: "tmp/jni/CMakeLists.txt", + }, + }, + }, +}; diff --git a/integration/fixtures/compat/ubrn.config.yaml b/integration/fixtures/compat/ubrn.config.yaml new file mode 100644 index 00000000..61ae5610 --- /dev/null +++ b/integration/fixtures/compat/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 index 0dbd0566..37a17b4a 100755 --- a/scripts/test-turbo-modules.sh +++ b/scripts/test-turbo-modules.sh @@ -11,10 +11,11 @@ reset_args() { RN_VERSION=latest PROJECT_SLUG=my-test-library FORCE_NEW_DIR=false - IOS_NAME=MyTestLibrary SKIP_IOS=true SKIP_ANDROID=true UBRN_CONFIG= + PACKAGE_JSON_MIXIN= + REACT_NATIVE_CONFIG= APP_TSX= } @@ -25,10 +26,11 @@ usage() { echo " -A, --android Build for Android." echo " -I, --ios Build for iOS." echo " -C, --ubrn-config Use a ubrn config file." + echo " -P, --packgage-json-mixin Merge another JSON file into package.json" + echo " -R, --react-native-config Use a react-native.config.js file" echo " -T, --app-tsx Use a App.tsx file." echo echo " -s, --slug PROJECT_SLUG Specify the project slug (default: my-test-library)." - echo " -i, --ios-name IOS_NAME Specify the iOS project name (default: MyTestLibrary)." echo echo " -u, --builder-bob-version VERSION Specify the version of builder-bob to use (default: latest)." echo " -r, --rn-version VERSION Specify the version of React Native to use (default: latest)." @@ -49,7 +51,6 @@ cleanup() { diagnostics() { echo "-- PROJECT_DIR = $PROJECT_DIR" echo "-- PROJECT_SLUG = $PROJECT_SLUG" - echo "-- IOS_NAME = $IOS_NAME" } error() { @@ -98,14 +99,18 @@ parse_cli_options() { PROJECT_SLUG="$2" shift ;; - -i|--ios-name) - IOS_NAME="$2" - shift - ;; -C|--ubrn-config) UBRN_CONFIG=$(join_paths "$PWD" "$2") shift ;; + -P|--packgage-json-mixin) + PACKAGE_JSON_MIXIN=$(join_paths "$PWD" "$2") + shift + ;; + -R|--react-native-config) + REACT_NATIVE_CONFIG=$(join_paths "$PWD" "$2") + shift + ;; -T|--app-tsx) APP_TSX=$(join_paths "$PWD" "$2") shift @@ -169,10 +174,6 @@ create_library() { rm -rf "$base" || error "Failed to remove existing directory $base" fi - local example_type - if [ "$BOB_VERSION" == "latest" ] ; then - example_type=vanilla - fi echo "-- Creating library $PROJECT_SLUG with create-react-native-library@$BOB_VERSION" npm_config_yes=true npx "create-react-native-library@$BOB_VERSION" \ --react-native-version "$RN_VERSION" \ @@ -184,7 +185,7 @@ create_library() { --repo-url "https://github.com/jhugman/$PROJECT_SLUG" \ --languages cpp \ --type module-new \ - --example $example_type \ + --example vanilla \ --local false \ "$base" exit_dir @@ -193,7 +194,7 @@ create_library() { install_dependencies() { enter_dir "$PROJECT_DIR" # touch yarn.lock - yarn || error "Failed to install dependencies" + yarn --no-immutable || error "Failed to install dependencies" # rm yarn.lock exit_dir } @@ -201,7 +202,7 @@ install_dependencies() { install_example_dependencies() { enter_dir "$PROJECT_DIR/example" # touch yarn.lock - yarn || error "Failed to install example dependencies" + yarn --no-immutable || error "Failed to install example dependencies" # rm yarn.lock # rm -Rf .yarn exit_dir @@ -303,6 +304,13 @@ generate_turbo_module_for_compiling() { clean_turbo_modules "$UBRN_BIN" checkout --config "$UBRN_CONFIG" cp "$UBRN_CONFIG" ./ubrn.config.yaml + if [ -f "$PACKAGE_JSON_MIXIN" ] ; then + jq -s '.[0] * .[1]' ./package.json "$PACKAGE_JSON_MIXIN" > ./package.json.new + mv ./package.json.new ./package.json + fi + if [ -f "$REACT_NATIVE_CONFIG" ] ; then + cp "$REACT_NATIVE_CONFIG" ./react-native.config.js + fi if [ -f "$APP_TSX" ] ; then cp "$APP_TSX" ./example/src/App.tsx fi @@ -329,8 +337,8 @@ build_android_example() { echo "-- Running ubrn build android" "$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" + enter_dir "$PROJECT_DIR/example" + yarn build:android || error "Failed to build Android example" exit_dir } @@ -342,19 +350,9 @@ build_ios_example() { 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[][] | select(.state == "Booted") | .udid') - if [ "$udid" == "null" ]; then - udid=$(xcrun simctl list --json devices | jq -r '.devices[][] | select(.isAvailable == true) | .udid' | head -n 1) - xcrun simctl boot "$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 + enter_dir "$PROJECT_DIR/example" + yarn build:ios --extra-params "ARCHS=$(uname -m)" || error "Failed to build iOS example" exit_dir } @@ -479,7 +477,6 @@ run_for_builder_bob() { --builder-bob-version "$builder_bob_version" \ --slug react-native-dummy-lib-for-ios \ --ios \ - --ios-name DummyLibForIos \ "$working_dir/react-native-dummy-lib-for-ios" fi @@ -502,7 +499,6 @@ run_for_builder_bob() { --builder-bob-version "$builder_bob_version" \ --ios \ --app-tsx "$app_tsx" \ - --ios-name ReactNativeDummyLibForIos \ --slug @my-org/react-native-dummy-lib-for-ios \ "$working_dir/@my-org/react-native-dummy-lib-for-ios" fi