fix: Support RN 0.69 and use Hermes from source! 🎉 (#1186)

* wip

* wip

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update android/build.gradle

Co-authored-by: Tomek Zawadzki <tomekzawadzki98@gmail.com>

Co-authored-by: Tomek Zawadzki <tomekzawadzki98@gmail.com>
This commit is contained in:
Thibault Malbranche 2022-08-09 18:20:42 +02:00 committed by GitHub
parent 312b82b9f6
commit 205e542cb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 200 additions and 56 deletions

View File

@ -76,18 +76,33 @@ target_include_directories(
file (GLOB LIBRN_DIR "${BUILD_DIR}/react-native-0*/jni/${ANDROID_ABI}") file (GLOB LIBRN_DIR "${BUILD_DIR}/react-native-0*/jni/${ANDROID_ABI}")
if(${FOR_HERMES}) if(${FOR_HERMES})
file (GLOB LIBHERMES_DIR "${BUILD_DIR}/third-party-ndk/hermes/jni/${ANDROID_ABI}") string(APPEND CMAKE_CXX_FLAGS " -DJS_RUNTIME_HERMES=1")
# Use Hermes
find_library( if(${REACT_NATIVE_VERSION} LESS 69)
JS_ENGINE_LIB # From `hermes-engine` npm package
hermes target_include_directories(
PATHS ${LIBHERMES_DIR} ${PACKAGE_NAME}
NO_CMAKE_FIND_ROOT_PATH PRIVATE
"${JS_RUNTIME_DIR}/android/include"
)
else()
# Bundled Hermes from module `com.facebook.react:hermes-engine` or project `:ReactAndroid:hermes-engine`
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${JS_RUNTIME_DIR}/API"
"${JS_RUNTIME_DIR}/public"
)
endif()
target_link_libraries(
${PACKAGE_NAME}
"${BUILD_DIR}/third-party-ndk/hermes/jni/${ANDROID_ABI}/libhermes.so"
) )
# Use Reanimated Hermes
file (GLOB LIBREANIMATED_DIR "${BUILD_DIR}/react-native-reanimated-*-hermes.aar/jni/${ANDROID_ABI}") file (GLOB LIBREANIMATED_DIR "${BUILD_DIR}/react-native-reanimated-*-hermes.aar/jni/${ANDROID_ABI}")
else() else()
file (GLOB LIBJSC_DIR "${BUILD_DIR}/android-jsc*.aar/jni/${ANDROID_ABI}") file (GLOB LIBJSC_DIR "${BUILD_DIR}/android-jsc*.aar/jni/${ANDROID_ABI}")
# Use JSC # Use JSC
find_library( find_library(
JS_ENGINE_LIB JS_ENGINE_LIB
@ -95,6 +110,11 @@ else()
PATHS ${LIBRN_DIR} PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH NO_CMAKE_FIND_ROOT_PATH
) )
target_link_libraries(
${PACKAGE_NAME}
${JS_ENGINE_LIB}
)
# Use Reanimated JSC # Use Reanimated JSC
file (GLOB LIBREANIMATED_DIR "${BUILD_DIR}/react-native-reanimated-*-jsc.aar/jni/${ANDROID_ABI}") file (GLOB LIBREANIMATED_DIR "${BUILD_DIR}/react-native-reanimated-*-jsc.aar/jni/${ANDROID_ABI}")
endif() endif()
@ -105,12 +125,22 @@ find_library(
PATHS ${LIBRN_DIR} PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH NO_CMAKE_FIND_ROOT_PATH
) )
find_library(
FOLLY_JSON_LIB if(${REACT_NATIVE_VERSION} LESS 69)
find_library(
FOLLY_LIB
folly_json folly_json
PATHS ${LIBRN_DIR} PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH NO_CMAKE_FIND_ROOT_PATH
) )
else()
find_library(
FOLLY_LIB
folly_runtime
PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH
)
endif()
find_library( find_library(
REACT_NATIVE_JNI_LIB REACT_NATIVE_JNI_LIB
@ -118,6 +148,7 @@ find_library(
PATHS ${LIBRN_DIR} PATHS ${LIBRN_DIR}
NO_CMAKE_FIND_ROOT_PATH NO_CMAKE_FIND_ROOT_PATH
) )
if(${REACT_NATIVE_VERSION} LESS 66) if(${REACT_NATIVE_VERSION} LESS 66)
# JSI lib didn't exist on RN 0.65 and before. Simply omit it. # JSI lib didn't exist on RN 0.65 and before. Simply omit it.
set (JSI_LIB "") set (JSI_LIB "")
@ -144,16 +175,14 @@ find_library(
) )
# linking # linking
message(WARNING "VisionCamera linking: FOR_HERMES=${FOR_HERMES}") message(WARNING "VisionCamera linking: FOR_HERMES=${FOR_HERMES}")
target_link_libraries( target_link_libraries(
${PACKAGE_NAME} ${PACKAGE_NAME}
${LOG_LIB} ${LOG_LIB}
${JSI_LIB} ${JSI_LIB}
${JS_ENGINE_LIB} # <-- Hermes or JSC
${REANIMATED_LIB} ${REANIMATED_LIB}
${REACT_NATIVE_JNI_LIB} ${REACT_NATIVE_JNI_LIB}
${FBJNI_LIB} ${FBJNI_LIB}
${FOLLY_JSON_LIB} ${FOLLY_LIB}
android android
) )

View File

@ -16,13 +16,35 @@ static def findNodeModules(baseDir) {
} }
throw new GradleException("VisionCamera: Failed to find node_modules/ path!") throw new GradleException("VisionCamera: Failed to find node_modules/ path!")
} }
static def findNodeModulePath(baseDir, packageName) {
def basePath = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}
def nodeModules = findNodeModules(projectDir) def nodeModules = findNodeModules(projectDir)
logger.warn("VisionCamera: node_modules/ found at: ${nodeModules}") logger.warn("VisionCamera: node_modules/ found at: ${nodeModules}")
def reactNative = new File("$nodeModules/react-native") def reactNative = new File("$nodeModules/react-native")
def CMAKE_NODE_MODULES_DIR = project.getProjectDir().getParentFile().getParent()
def reactProperties = new Properties() def reactProperties = new Properties()
file("$nodeModules/react-native/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } file("$nodeModules/react-native/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
def REACT_NATIVE_FULL_VERSION = reactProperties.getProperty("VERSION_NAME")
def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME").split("\\.")[1].toInteger() def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME").split("\\.")[1].toInteger()
def FOR_HERMES = System.getenv("FOR_HERMES") == "True" def FOR_HERMES = System.getenv("FOR_HERMES") == "True"
@ -31,6 +53,17 @@ rootProject.getSubprojects().forEach({project ->
FOR_HERMES = project.ext.react.enableHermes FOR_HERMES = project.ext.react.enableHermes
} }
}) })
def jsRuntimeDir = {
if (FOR_HERMES) {
if (REACT_NATIVE_VERSION >= 69) {
return Paths.get(CMAKE_NODE_MODULES_DIR, "react-native", "sdks", "hermes")
} else {
return Paths.get(CMAKE_NODE_MODULES_DIR, "hermes-engine")
}
} else {
return Paths.get(CMAKE_NODE_MODULES_DIR, "react-native", "ReactCommon", "jsi")
}
}.call()
logger.warn("VisionCamera: Building with ${FOR_HERMES ? "Hermes" : "JSC"}...") logger.warn("VisionCamera: Building with ${FOR_HERMES ? "Hermes" : "JSC"}...")
buildscript { buildscript {
@ -67,6 +100,16 @@ def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['VisionCamera_' + name]).toInteger() return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['VisionCamera_' + name]).toInteger()
} }
def resolveBuildType() {
def buildType = "debug"
tasks.all({ task ->
if (task.name == "buildCMakeRelease") {
buildType = "release"
}
})
return buildType
}
// plugin.js file only exists since REA v2. // plugin.js file only exists since REA v2.
def hasReanimated2 = file("${nodeModules}/react-native-reanimated/plugin.js").exists() def hasReanimated2 = file("${nodeModules}/react-native-reanimated/plugin.js").exists()
def disableFrameProcessors = rootProject.ext.has("disableFrameProcessors") ? rootProject.ext.get("disableFrameProcessors").asBoolean() : false def disableFrameProcessors = rootProject.ext.has("disableFrameProcessors") ? rootProject.ext.get("disableFrameProcessors").asBoolean() : false
@ -99,7 +142,8 @@ android {
arguments '-DANDROID_STL=c++_shared', arguments '-DANDROID_STL=c++_shared',
"-DREACT_NATIVE_VERSION=${REACT_NATIVE_VERSION}", "-DREACT_NATIVE_VERSION=${REACT_NATIVE_VERSION}",
"-DNODE_MODULES_DIR=${nodeModules}", "-DNODE_MODULES_DIR=${nodeModules}",
"-DFOR_HERMES=${FOR_HERMES}" "-DFOR_HERMES=${FOR_HERMES}",
"-DJS_RUNTIME_DIR=${jsRuntimeDir}"
} }
} }
} }
@ -228,17 +272,14 @@ dependencies {
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
extractJNI("com.facebook.fbjni:fbjni:+") extractJNI("com.facebook.fbjni:fbjni:+")
def rnAAR = fileTree("${nodeModules}/react-native/android").matching({ it.include "**/**/*.aar" }).singleFile def rnAarMatcher = "**/react-native/**/*${resolveBuildType()}.aar"
if (REACT_NATIVE_VERSION < 69) {
rnAarMatcher = "**/**/*.aar"
}
def rnAAR = fileTree("$reactNative/android").matching({ it.include rnAarMatcher }).singleFile
def jscAAR = fileTree("${nodeModules}/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile def jscAAR = fileTree("${nodeModules}/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile
def inputFile = file("${nodeModules}/react-native/package.json")
def json = new JsonSlurper().parseText(inputFile.text)
def reactNativeVersion = json.version as String
def (major, minor, patch) = reactNativeVersion.tokenize('.')
def jsEngine = FOR_HERMES ? "hermes" : "jsc" def jsEngine = FOR_HERMES ? "hermes" : "jsc"
def reaAAR = "${nodeModules}/react-native-reanimated/android/react-native-reanimated-${minor}-${jsEngine}.aar" def reaAAR = "${nodeModules}/react-native-reanimated/android/react-native-reanimated-${REACT_NATIVE_VERSION}-${jsEngine}.aar"
extractJNI(files(rnAAR, jscAAR, reaAAR)) extractJNI(files(rnAAR, jscAAR, reaAAR))
} }
@ -285,7 +326,12 @@ if (ENABLE_FRAME_PROCESSORS) {
} }
task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) {
src("https://github.com/react-native-community/boost-for-react-native/releases/download/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz") def transformedVersion = BOOST_VERSION.replace("_", ".")
def srcUrl = "https://boostorg.jfrog.io/artifactory/main/release/${transformedVersion}/source/boost_${BOOST_VERSION}.tar.gz"
if (REACT_NATIVE_VERSION < 69) {
srcUrl = "https://github.com/react-native-community/boost-for-react-native/releases/download/v${transformedVersion}-0/boost_${BOOST_VERSION}.tar.gz"
}
src(srcUrl)
onlyIfNewer(true) onlyIfNewer(true)
overwrite(false) overwrite(false)
dest(boost_file) dest(boost_file)
@ -394,10 +440,78 @@ if (ENABLE_FRAME_PROCESSORS) {
prepareThirdPartyNdkHeaders.mustRunAfter createNativeDepsDirectories prepareThirdPartyNdkHeaders.mustRunAfter createNativeDepsDirectories
/*
COPY-PASTE from react-native-reanimated.
Vision Camera includes "hermes/hermes.h" header file in `NativeProxy.cpp`.
Previously, we used header files from `hermes-engine` package in `node_modules`.
Starting from React Native 0.69, Hermes is no longer distributed as package on NPM.
On the new architecture, Hermes is downloaded from GitHub and then compiled from sources.
However, on the old architecture, we need to download Hermes header files on our own
as well as unzip Hermes AAR in order to obtain `libhermes.so` shared library.
For more details, see https://reactnative.dev/architecture/bundled-hermes
or https://github.com/reactwg/react-native-new-architecture/discussions/4
*/
if (REACT_NATIVE_VERSION >= 69 && !isNewArchitectureEnabled()) {
// copied from `react-native/ReactAndroid/hermes-engine/build.gradle`
def customDownloadDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
def downloadDir = customDownloadDir ? new File(customDownloadDir) : new File(reactNative, "sdks/download")
// By default we are going to download and unzip hermes inside the /sdks/hermes folder
// but you can provide an override for where the hermes source code is located.
def hermesDir = System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR") ?: new File(reactNative, "sdks/hermes")
def hermesVersion = "main"
def hermesVersionFile = new File(reactNative, "sdks/.hermesversion")
if (hermesVersionFile.exists()) {
hermesVersion = hermesVersionFile.text
}
task downloadHermes(type: Download) {
src("https://github.com/facebook/hermes/tarball/${hermesVersion}")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadDir, "hermes.tar.gz"))
}
task unzipHermes(dependsOn: downloadHermes, type: Copy) {
from(tarTree(downloadHermes.dest)) {
eachFile { file ->
// We flatten the unzip as the tarball contains a `facebook-hermes-<SHA>`
// folder at the top level.
if (file.relativePath.segments.size() > 1) {
file.relativePath = new org.gradle.api.file.RelativePath(!file.isDirectory(), file.relativePath.segments.drop(1))
}
}
}
into(hermesDir)
}
}
task prepareHermes() { task prepareHermes() {
if (REACT_NATIVE_VERSION >= 69) {
if (!isNewArchitectureEnabled()) {
dependsOn(unzipHermes)
}
doLast { doLast {
def hermesPackagePath = file("${nodeModules}/hermes-engine") def hermesAAR = file("$reactNative/android/com/facebook/react/hermes-engine/$REACT_NATIVE_FULL_VERSION/hermes-engine-$REACT_NATIVE_FULL_VERSION-${resolveBuildType()}.aar") // e.g. hermes-engine-0.70.0-rc.1-debug.aar
if (!hermesPackagePath.exists()) { if (!hermesAAR.exists()) {
throw new GradleScriptException("Could not find hermes-engine AAR", null)
}
def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
copy {
from soFiles
from "$reactNative/ReactAndroid/src/main/jni/first-party/hermes/Android.mk"
into "$thirdPartyNdkDir/hermes"
}
}
} else {
doLast {
def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
if (!hermesPackagePath) {
throw new GradleScriptException("Could not find the hermes-engine npm package", null) throw new GradleScriptException("Could not find the hermes-engine npm package", null)
} }
@ -415,6 +529,7 @@ if (ENABLE_FRAME_PROCESSORS) {
} }
} }
} }
}
prepareHermes.mustRunAfter prepareThirdPartyNdkHeaders prepareHermes.mustRunAfter prepareThirdPartyNdkHeaders