Skip to content

chore: add android implementations #2

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

Merged
merged 4 commits into from
Feb 13, 2025
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
File renamed without changes.
Empty file removed android/.gitkeep
Empty file.
74 changes: 74 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#
# Copyright (c) react-native-community
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#

cmake_minimum_required(VERSION 3.13)
project(jscruntimefactory)

set(CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 20)

string(APPEND CMAKE_CXX_FLAGS
" -fexceptions"
" -frtti"
" -O3"
" -Wno-unused-lambda-capture"
" -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}"
" -DREACT_NATIVE_PATCH_VERSION=${REACT_NATIVE_PATCH_VERSION}"
)

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

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")

set(PACKAGE_NAME "jscruntimefactory")
set(SRC_DIR "${CMAKE_SOURCE_DIR}/src/main/cpp")

set(COMMON_DIR "${CMAKE_SOURCE_DIR}/../common")
file(GLOB COMMON_SOURCES "${COMMON_DIR}/*.cpp")

add_library(
${PACKAGE_NAME}
SHARED
${COMMON_SOURCES}
"${SRC_DIR}/JSCExecutorFactory.cpp"
"${SRC_DIR}/OnLoad.cpp"
)

# includes

target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${COMMON_DIR}"
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactCommon/jsiexecutor"
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni"
)

# find libraries

find_library(
LOG_LIB
log
)
find_package(fbjni REQUIRED CONFIG)
find_package(jsc-android REQUIRED CONFIG)
find_package(ReactAndroid REQUIRED CONFIG)

# link to shared libraries

target_link_libraries(
${PACKAGE_NAME}
${LOG_LIB}
android
fbjni::fbjni
jsc-android::jsc
ReactAndroid::jsi
ReactAndroid::reactnative
)
146 changes: 146 additions & 0 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright (c) react-native-community
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import com.android.build.gradle.internal.tasks.factory.dependsOn
import groovy.json.JsonSlurper
import org.apache.tools.ant.taskdefs.condition.Os
import java.util.Properties

val reactNativeDir = findNodePackageDir("react-native")
val reactNativeManifest = file("${reactNativeDir}/package.json")
val reactNativeManifestAsJson = JsonSlurper().parseText(reactNativeManifest.readText()) as Map<*, *>
val reactNativeVersion = reactNativeManifestAsJson["version"] as String
val (major, minor, patch) = reactNativeVersion.split(".")
val rnMinorVersion = minor.toInt()
val rnPatchVersion = patch.toInt()
val prefabHeadersDir = file("${layout.buildDirectory.get()}/prefab-headers")

if (findProperty("hermesEnabled") == "true") {
throw GradleException("Please disable Hermes because Hermes will transform the JavaScript bundle as bytecode bundle.\n")
}

val reactProperties = Properties().apply {
file("${reactNativeDir}/ReactAndroid/gradle.properties").inputStream().use { load(it) }
}

plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "io.github.reactnativecommunity.javascriptcore"
compileSdk = safeExtGet("compileSdkVersion", 35)

if (rootProject.hasProperty("ndkPath")) {
ndkPath = rootProject.extra["ndkPath"].toString()
}
if (rootProject.hasProperty("ndkVersion")) {
ndkVersion = rootProject.extra["ndkVersion"].toString()
}

defaultConfig {
minSdk = safeExtGet("minSdkVersion", 24)

@Suppress("UnstableApiUsage")
externalNativeBuild.cmake {
arguments += listOf(
"-DANDROID_STL=c++_shared",
"-DREACT_NATIVE_DIR=${reactNativeDir.toPlatformString()}",
"-DREACT_NATIVE_MINOR_VERSION=${rnMinorVersion}",
"-DREACT_NATIVE_PATCH_VERSION=${rnPatchVersion}",
)
targets("jscruntimefactory")
abiFilters(*reactNativeArchitectures().toTypedArray())
}
}

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

lint {
abortOnError = false
targetSdk = safeExtGet("targetSdkVersion", 35)
}

packaging {
// Uncomment to keep debug symbols
// doNotStrip.add("**/*.so")

jniLibs {
excludes.add("**/libc++_shared.so")
excludes.add("**/libfbjni.so")
excludes.add("**/libjsi.so")
excludes.add("**/libreactnative.so")
pickFirsts.add("**/libjscruntimefactory.so")
}
}

buildFeatures {
prefab = true
prefabPublishing = true
}

prefab {
register("jscruntimefactory") {
headers = file(prefabHeadersDir).absolutePath
}
}
}

dependencies {
implementation("com.facebook.yoga:proguard-annotations:1.19.0")
compileOnly("com.facebook.fbjni:fbjni:0.7.0")
implementation("com.facebook.react:react-android")

//noinspection GradleDynamicVersion
implementation("io.github.react-native-community:jsc-android:2026004.+")
}

val createPrefabHeadersDir by
tasks.registering {
prefabHeadersDir.mkdirs()
}

tasks.named("preBuild").dependsOn(createPrefabHeadersDir)

private fun findNodePackageDir(packageName: String, absolute: Boolean = true): File {
val nodeCommand = listOf("node", "--print", "require.resolve('${packageName}/package.json')")
val proc = ProcessBuilder(nodeCommand)
.directory(rootDir)
.start()
val error = proc.errorStream.bufferedReader().readText()
if (error.isNotEmpty()) {
val nodeCommandString = nodeCommand.joinToString(" ")
throw GradleException(
"findNodePackageDir() execution failed - nodeCommand[$nodeCommandString]\n$error"
)
}
val dir = File(proc.inputStream.bufferedReader().readText().trim()).parentFile
return if (absolute) dir.absoluteFile else dir
}

private fun File.toPlatformString(): String {
var result = path.toString()
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
result = result.replace(File.separatorChar, '/')
}
return result
}

private fun reactNativeArchitectures(): List<String> {
val value = findProperty("reactNativeArchitectures")
return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
}

private fun <T> safeExtGet(prop: String, fallback: T): T {
@Suppress("UNCHECKED_CAST")
return rootProject.extra[prop] as? T ?: fallback
}
48 changes: 48 additions & 0 deletions android/src/main/cpp/JSCExecutorFactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "JSCExecutorFactory.h"

#include <fbjni/fbjni.h>
#include <react/jni/JReactMarker.h>
#include <react/jni/JSLogging.h>

namespace facebook::react {

namespace {

class JSCExecutorFactory : public JSExecutorFactory {
public:
std::unique_ptr<JSExecutor>
createJSExecutor(std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override {
auto installBindings = [](jsi::Runtime &runtime) {
react::Logger androidLogger =
static_cast<void (*)(const std::string &, unsigned int)>(
&reactAndroidLoggingHook);
react::bindNativeLogger(runtime, androidLogger);
};
return std::make_unique<JSIExecutor>(jsc::makeJSCRuntime(), delegate,
JSIExecutor::defaultTimeoutInvoker,
installBindings);
}
};

} // namespace

// static
jni::local_ref<JSCExecutorHolder::jhybriddata>
JSCExecutorHolder::initHybrid(jni::alias_ref<jclass>, ReadableNativeMap *) {
// This is kind of a weird place for stuff, but there's no other
// good place for initialization which is specific to JSC on
// Android.
JReactMarker::setLogPerfMarkerIfNeeded();
// TODO mhorowitz T28461666 fill in some missing nice to have glue
return makeCxxInstance(std::make_unique<JSCExecutorFactory>());
}

} // namespace facebook::react
41 changes: 41 additions & 0 deletions android/src/main/cpp/JSCExecutorFactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <fbjni/fbjni.h>
#include <jsireact/JSIExecutor.h>
#include <react/jni/JavaScriptExecutorHolder.h>
#include <react/jni/ReadableNativeMap.h>

#include "JSCRuntime.h"

namespace facebook::react {

// This is not like JSCJavaScriptExecutor, which calls JSC directly. This uses
// JSIExecutor with JSCRuntime.
class JSCExecutorHolder
: public jni::HybridClass<JSCExecutorHolder, JavaScriptExecutorHolder> {
public:
static constexpr auto kJavaDescriptor =
"Lio/github/reactnativecommunity/javascriptcore/JSCExecutor;";

static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jclass>,
ReadableNativeMap *);

static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JSCExecutorHolder::initHybrid),
});
}

private:
friend HybridBase;
using HybridBase::HybridBase;
};

} // namespace facebook::react
45 changes: 45 additions & 0 deletions android/src/main/cpp/JSCRuntimeFactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/runtime/JSRuntimeFactory.h>
#include <react/runtime/jni/JJSRuntimeFactory.h>

#include "JSCRuntime.h"

namespace facebook::react {

class JSCRuntimeFactory
: public jni::HybridClass<JSCRuntimeFactory, JJSRuntimeFactory> {
public:
static constexpr auto kJavaDescriptor =
"Lio/github/reactnativecommunity/javascriptcore/JSCRuntimeFactory;";

static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>) {
return makeCxxInstance();
}

static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JSCRuntimeFactory::initHybrid),
});
}

std::unique_ptr<JSRuntime>
createJSRuntime(std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept {
return std::make_unique<JSIRuntimeHolder>(jsc::makeJSCRuntime());
}

private:
friend HybridBase;
using HybridBase::HybridBase;
};

} // namespace facebook::react
18 changes: 18 additions & 0 deletions android/src/main/cpp/OnLoad.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <fbjni/fbjni.h>

#include "JSCExecutorFactory.h"
#include "JSCRuntimeFactory.h"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(vm, [] {
facebook::react::JSCExecutorHolder::registerNatives();
facebook::react::JSCRuntimeFactory::registerNatives();
});
}
Loading