Skip to content

Commit

Permalink
create-react-native-library@latest: Modify turbo-modules generation (j…
Browse files Browse the repository at this point in the history
…hugman#131)

Fixes jhugman#107

Now working with `[email protected]`

This adds kotlin detection and uses Kotlin if needed, or stays with
Java: this adds two Kotlin files and an alternative `build.gradle` file.

It also adds more checks to the test-turbo-modules.sh script, tightening
up podspec and Objective C files.

Finally we add documentation to getting-started.md and
contributing-turbo-modules.md.
  • Loading branch information
jhugman authored Oct 23, 2024
1 parent 7f0a333 commit 8dc9981
Show file tree
Hide file tree
Showing 11 changed files with 489 additions and 119 deletions.
104 changes: 100 additions & 4 deletions crates/ubrn_cli/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anyhow::Result;
use askama::DynTemplate;
use camino::{Utf8Path, Utf8PathBuf};
use clap::Args;
use std::{collections::BTreeMap, rc::Rc};
use std::{cell::OnceCell, collections::BTreeMap, rc::Rc};

use ubrn_bindgen::ModuleMetadata;
use ubrn_common::{mk_dir, CrateMetadata};
Expand Down Expand Up @@ -71,6 +71,7 @@ pub(crate) struct TemplateConfig {
pub(crate) project: ProjectConfig,
pub(crate) rust_crate: CrateMetadata,
pub(crate) modules: Vec<ModuleMetadata>,
pub(crate) uses_kotlin: OnceCell<bool>,
}

impl TemplateConfig {
Expand All @@ -85,6 +86,7 @@ impl TemplateConfig {
project,
rust_crate,
modules,
uses_kotlin: OnceCell::new(),
}
}
}
Expand Down Expand Up @@ -156,13 +158,34 @@ macro_rules! templated_file {
}

mod files {
use super::platforms_match;
use super::RenderedFile;
use super::TemplateConfig;
use crate::Platform;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use std::rc::Rc;

impl TemplateConfig {
fn uses_kotlin(self: &Rc<Self>) -> bool {
*self.uses_kotlin.get_or_init(|| {
let project_root = self.project.project_root();
let gradle_file = BuildGradle::new(self.clone()).path(project_root);
if gradle_file.exists() {
let file = std::fs::read_to_string(gradle_file)
.expect("Cannot read build.gradle file");
file.contains("kotlin")
} else {
// assume that if the user blew away the gradle file,
// then we should remake it as one with kotlin.
true
}
})
}
}

// The set of files to be generated. These are filtered depending on what platform we're
// building for, and based upon the template config.
pub(super) fn get_files(config: Rc<TemplateConfig>) -> Vec<Rc<dyn RenderedFile>> {
vec![
// typescript
Expand All @@ -173,12 +196,17 @@ mod files {
// Codegen (for installer)
NativeCodegenTs::rc_new(config.clone()),
// Android
JavaModule::rc_new(config.clone()),
JavaPackage::rc_new(config.clone()),
BuildGradle::rc_new(config.clone()),
CMakeLists::rc_new(config.clone()),
CppAdapter::rc_new(config.clone()),
AndroidManifest::rc_new(config.clone()),
// Android with Java
JavaModule::rc_new(config.clone()),
JavaPackage::rc_new(config.clone()),
BuildGradle::rc_new(config.clone()),
// Android with Kotlin
KtModule::rc_new(config.clone()),
KtPackage::rc_new(config.clone()),
KtBuildGradle::rc_new(config.clone()),
// iOS
ModuleTemplateH::rc_new(config.clone()),
ModuleTemplateMm::rc_new(config.clone()),
Expand All @@ -197,6 +225,12 @@ mod files {
.codegen_package_dir(project_root)
.join(filename)
}
fn filter_by(&self, platform: &Option<Platform>) -> bool {
platforms_match(platform, &self.platform()) && !self.config.uses_kotlin()
}
fn platform(&self) -> Option<Platform> {
Some(Platform::Android)
}
}

templated_file!(JavaPackage, "PackageTemplate.java");
Expand All @@ -210,6 +244,9 @@ mod files {
.codegen_package_dir(project_root)
.join(filename)
}
fn filter_by(&self, platform: &Option<Platform>) -> bool {
platforms_match(platform, &self.platform()) && !self.config.uses_kotlin()
}
fn platform(&self) -> Option<Platform> {
Some(Platform::Android)
}
Expand All @@ -225,6 +262,65 @@ mod files {
.directory(project_root)
.join(filename)
}
fn filter_by(&self, platform: &Option<Platform>) -> bool {
platforms_match(platform, &self.platform()) && !self.config.uses_kotlin()
}
fn platform(&self) -> Option<Platform> {
Some(Platform::Android)
}
}

templated_file!(KtModule, "ModuleTemplate.kt");
impl RenderedFile for KtModule {
fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf {
let name = self.config.project.module_cpp();
let filename = format!("{name}Module.kt");
self.config
.project
.android
.codegen_package_dir(project_root)
.join(filename)
}
fn filter_by(&self, platform: &Option<Platform>) -> bool {
platforms_match(platform, &self.platform()) && self.config.uses_kotlin()
}
fn platform(&self) -> Option<Platform> {
Some(Platform::Android)
}
}

templated_file!(KtPackage, "PackageTemplate.kt");
impl RenderedFile for KtPackage {
fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf {
let name = self.config.project.module_cpp();
let filename = format!("{name}Package.kt");
self.config
.project
.android
.codegen_package_dir(project_root)
.join(filename)
}
fn filter_by(&self, platform: &Option<Platform>) -> bool {
platforms_match(platform, &self.platform()) && self.config.uses_kotlin()
}
fn platform(&self) -> Option<Platform> {
Some(Platform::Android)
}
}

templated_file!(KtBuildGradle, "build.kt.gradle");
impl RenderedFile for KtBuildGradle {
fn path(&self, project_root: &Utf8Path) -> Utf8PathBuf {
let filename = "build.gradle";
self.config
.project
.android
.directory(project_root)
.join(filename)
}
fn filter_by(&self, platform: &Option<Platform>) -> bool {
platforms_match(platform, &self.platform()) && self.config.uses_kotlin()
}
fn platform(&self) -> Option<Platform> {
Some(Platform::Android)
}
Expand Down
46 changes: 46 additions & 0 deletions crates/ubrn_cli/src/codegen/templates/ModuleTemplate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{%- let android = self.config.project.android.clone() %}
{%- let name = self.config.project.module_cpp() %}
{%- let module_class_name = name|fmt("{}Module") -%}
// Generated by uniffi-bindgen-react-native
package {{ android.package_name() }}

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder

@ReactModule(name = {{ module_class_name }}.NAME)
class {{ module_class_name }}(reactContext: ReactApplicationContext) :
{{ self.config.project.codegen_filename() }}Spec(reactContext) {

override fun getName(): String {
return NAME
}

// Two native methods implemented in cpp-adapter.cpp, and ultimately
// {{ self.config.project.cpp_filename() }}.cpp

external fun nativeInstallRustCrate(runtimePointer: Long, callInvoker: CallInvokerHolder): Boolean
external fun nativeCleanupRustCrate(runtimePointer: Long): Boolean

override fun installRustCrate(): Boolean {
val context = this.reactApplicationContext
return nativeInstallRustCrate(
context.javaScriptContextHolder!!.get(),
context.jsCallInvokerHolder!!
)
}

override fun cleanupRustCrate(): Boolean {
return nativeCleanupRustCrate(
this.reactApplicationContext.javaScriptContextHolder!!.get()
)
}

companion object {
const val NAME = "{{ name }}"

init {
System.loadLibrary("{{ self.config.project.cpp_filename() }}")
}
}
}
2 changes: 1 addition & 1 deletion crates/ubrn_cli/src/codegen/templates/ModuleTemplate.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{%- let module_name = self.config.project.module_cpp() %}
{%- let spec_jsi = self.config.project.tm.spec_name()|fmt("{}JSI") %}
{%- let spec_jsi = self.config.project.codegen_filename()|fmt("{}SpecJSI") %}
{%- let ns = self.config.project.cpp_namespace() %}
{%- let uniffi_ns = "uniffi_generated" %}
{%- let fn_prefix = "__hostFunction_{}"|format(module_name) -%}
Expand Down
37 changes: 37 additions & 0 deletions crates/ubrn_cli/src/codegen/templates/PackageTemplate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{%- 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
package {{ self.config.project.android.package_name() }}

import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import java.util.HashMap

class {{ package_class_name }} : TurboReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == {{ module_class_name }}.NAME) {
{{ module_class_name }}(reactContext)
} else {
null
}
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
moduleInfos[{{ module_class_name }}.NAME] = ReactModuleInfo(
{{ module_class_name }}.NAME,
{{ module_class_name }}.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
moduleInfos
}
}
}
Loading

0 comments on commit 8dc9981

Please sign in to comment.