Skip to content

feat: add C++ JSI interface and Executorch dependency #184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: v0.4.0-rc1
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.13)
project(RnExecutorch)

set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 20)

include("${REACT_NATIVE_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake")
add_compile_options(${folly_FLAGS})

string(APPEND CMAKE_CXX_FLAGS " -DRCT_NEW_ARCH_ENABLED")

set(ANDROID_CPP_DIR "${CMAKE_SOURCE_DIR}/src/main/cpp")
set(COMMON_CPP_DIR "${CMAKE_SOURCE_DIR}/../common")
set(ET_LIB_DIR "${CMAKE_SOURCE_DIR}/../third-party/android/libs")
set(ET_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/../third-party/include")

add_subdirectory("${ANDROID_CPP_DIR}")
79 changes: 69 additions & 10 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.apache.tools.ant.taskdefs.condition.Os

buildscript {
ext {
agp_version = '8.4.2'
Expand All @@ -21,19 +23,20 @@ buildscript {

def reactNativeArchitectures() {
def value = rootProject.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
// react-native-executorch supports only these architectures. This is due to
// Executorch not supporting anything else.
def defaultArchitectures = ["x86_64", "arm64-v8a"]
if(!value) {
return defaultArchitectures
}
def architectures = value.split(",")
return architectures.findAll { it in defaultArchitectures }
}

apply plugin: "com.android.library"
apply plugin: "kotlin-android"
apply plugin: "com.facebook.react"

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["RnExecutorch_" + name]
Expand All @@ -52,6 +55,38 @@ def supportsNamespace() {
return (major == 7 && minor >= 3) || major >= 8
}

def safeAppExtGet(prop, fallback) {
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
}

def toPlatformFileString(String path) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
path = path.replace(File.separatorChar, '/' as char)
}
return path
}

def resolveReactNativeDirectory() {
def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)

if (reactNativeLocation !== null) {
return file(reactNativeLocation)
}

// Fallback to node resolver for custom directory structures like monorepos.
def reactNativePackage = file(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim())
if(reactNativePackage.exists()) {
return reactNativePackage.parentFile
}

throw new GradleException(
"[RnExecutorch] Unable to resolve react-native location in node_modules. You should project extension property (in `app/build.gradle`) `REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
)
}

def reactNativeRootDir = resolveReactNativeDirectory()

android {
if (supportsNamespace()) {
namespace "com.swmansion.rnexecutorch"
Expand All @@ -63,12 +98,34 @@ android {
}
}

buildFeatures {
prefab true
prefabPublishing true
buildConfig true
}

compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")

defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString())
externalNativeBuild {
cmake {
cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
abiFilters (*reactNativeArchitectures())
arguments "-DANDROID_STL=c++_shared",
"-DREACT_NATIVE_DIR=${toPlatformFileString(reactNativeRootDir.path)}"
"-DBUILD_DIR=${project.buildDir}"
"-DANDROID_TOOLCHAIN=clang"
}
}
}


externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

buildTypes {
Expand Down Expand Up @@ -101,9 +158,11 @@ dependencies {
//noinspection GradleDynamicVersion
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation "com.facebook.react:react-android:+"
implementation "com.facebook.react:react-native:+"
implementation 'com.facebook.fbjni:fbjni:0.6.0'
implementation 'org.opencv:opencv:4.10.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation(files("libs/executorch.aar"))
implementation(files("../third-party/android/libs/executorch.aar"))
implementation 'org.opencv:opencv:4.10.0'
implementation("com.squareup.okhttp3:okhttp:4.9.2")
}
Binary file removed android/libs/executorch.aar
Binary file not shown.
44 changes: 44 additions & 0 deletions android/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.13)

file(GLOB_RECURSE ANDROID_CPP_SOURCES CONFIGURE_DEPENDS "${ANDROID_CPP_DIR}/*.cpp")
file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.cpp" "${COMMON_CPP_DIR}/*.c")

add_library(react-native-executorch SHARED ${ANDROID_CPP_SOURCES} ${COMMON_CPP_SOURCES})

find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)

target_include_directories(
react-native-executorch
PUBLIC
"${COMMON_CPP_DIR}"
"${ANDROID_CPP_DIR}"
"${ET_INCLUDE_DIR}"
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/react/turbomodule"
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
"${BUILD_DIR}/generated/source/codegen/jni/react/renderer/components/RnExecutorchSpec"
)

set(LINK_LIBRARIES
ReactAndroid::jsi
fbjni::fbjni
android
log
)

set(RN_VERSION_LINK_LIBRARIES
ReactAndroid::reactnative
)

add_library(executorch SHARED IMPORTED)

set_target_properties(executorch PROPERTIES
IMPORTED_LOCATION "${ET_LIB_DIR}/${ANDROID_ABI}/libexecutorch.so")

target_link_libraries(
react-native-executorch
${LINK_LIBRARIES}
${RN_VERSION_LINK_LIBRARIES}
executorch
)
35 changes: 35 additions & 0 deletions android/src/main/cpp/ETInstallerModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "ETInstallerModule.h"
#include "RnExecutorchInstaller.h"

namespace rnexecutorch {

using namespace facebook::jni;

ETInstallerModule::ETInstallerModule(
jni::alias_ref<ETInstallerModule::jhybridobject> &jThis,
jsi::Runtime *jsiRuntime,
const std::shared_ptr<facebook::react::CallInvoker> &jsCallInvoker)
: javaPart_(make_global(jThis)), jsiRuntime_(jsiRuntime),
jsCallInvoker_(jsCallInvoker) {}

jni::local_ref<ETInstallerModule::jhybriddata> ETInstallerModule::initHybrid(
jni::alias_ref<jhybridobject> jThis, jlong jsContext,
jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>
jsCallInvokerHolder) {
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();
auto rnRuntime = reinterpret_cast<jsi::Runtime *>(jsContext);
return makeCxxInstance(jThis, rnRuntime, jsCallInvoker);
}

void ETInstallerModule::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", ETInstallerModule::initHybrid),
makeNativeMethod("injectJSIBindings",
ETInstallerModule::injectJSIBindings),
});
}

void ETInstallerModule::injectJSIBindings() {
RnExecutorchInstaller::injectJSIBindings(jsiRuntime_, jsCallInvoker_);
}
} // namespace rnexecutorch
43 changes: 43 additions & 0 deletions android/src/main/cpp/ETInstallerModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <ReactCommon/CallInvokerHolder.h>
#include <fbjni/fbjni.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>

#include <memory>
#include <utility>

namespace rnexecutorch {

using namespace facebook;
using namespace react;

class ETInstallerModule : public jni::HybridClass<ETInstallerModule> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/rnexecutorch/ETInstaller;";

static jni::local_ref<ETInstallerModule::jhybriddata>
initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext,
jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>
jsCallInvokerHolder);

static void registerNatives();

void injectJSIBindings();

private:
friend HybridBase;

jni::global_ref<ETInstallerModule::javaobject> javaPart_;
jsi::Runtime *jsiRuntime_;
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker_;

explicit ETInstallerModule(
jni::alias_ref<ETInstallerModule::jhybridobject> &jThis,
jsi::Runtime *jsiRuntime,
const std::shared_ptr<facebook::react::CallInvoker> &jsCallInvoker);
};

} // namespace rnexecutorch
10 changes: 10 additions & 0 deletions android/src/main/cpp/OnLoad.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <ETInstallerModule.h>

#include <fbjni/fbjni.h>

using namespace rnexecutorch;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(
vm, [] { ETInstallerModule::registerNatives(); });
}
44 changes: 44 additions & 0 deletions android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.swmansion.rnexecutorch

import com.facebook.jni.HybridData
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.common.annotations.FrameworkAPI
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl

@OptIn(FrameworkAPI::class)
@ReactModule(name = ETInstaller.NAME)
class ETInstaller(
reactContext: ReactApplicationContext,
) : NativeETInstallerSpec(reactContext) {
companion object {
const val NAME = NativeETInstallerSpec.NAME
}

private val mHybridData: HybridData

external fun initHybrid(
jsContext: Long,
callInvoker: CallInvokerHolderImpl,
): HybridData

private external fun injectJSIBindings()

init {
try {
System.loadLibrary("executorch")
System.loadLibrary("react-native-executorch")
val jsCallInvokerHolder = reactContext.jsCallInvokerHolder as CallInvokerHolderImpl
mHybridData = initHybrid(reactContext.javaScriptContextHolder!!.get(), jsCallInvokerHolder)
} catch (exception: UnsatisfiedLinkError) {
throw RuntimeException("Could not load native module Install", exception)
}
}

@ReactMethod(isBlockingSynchronousMethod = true)
override fun install(): Boolean {
injectJSIBindings()
return true
}
}
Loading