import groovy.json.JsonSlurper import org.apache.tools.ant.filters.ReplaceTokens import java.nio.file.Paths def reactNative = new File("$projectDir/../node_modules/react-native") def FOR_HERMES = System.getenv("FOR_HERMES") == "True" rootProject.getSubprojects().forEach({project -> if (project.plugins.hasPlugin("com.android.application")) { FOR_HERMES = project.ext.react.enableHermes } }) /** * Finds the path of the installed npm package with the given name using Node's * module resolution algorithm, which searches "node_modules" directories up to * the file system root. This handles various cases, including: * * - Working in the open-source RN repo: * Gradle: /path/to/react-native/ReactAndroid * Node module: /path/to/react-native/node_modules/[package] * * - Installing RN as a dependency of an app and searching for hoisted * dependencies: * Gradle: /path/to/app/node_modules/react-native/ReactAndroid * Node module: /path/to/app/node_modules/[package] * * - Working in a larger repo (e.g., Facebook) that contains RN: * Gradle: /path/to/repo/path/to/react-native/ReactAndroid * Node module: /path/to/repo/node_modules/[package] * * The search begins at the given base directory (a File object). The returned * path is a string. */ 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 } buildscript { // Buildscript is evaluated before everything else so we can't use getExtOrDefault def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['VisionCamera_kotlinVersion'] repositories { google() mavenCentral() maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath 'com.android.tools.build:gradle:4.2.2' classpath 'de.undercouch:gradle-download-task:4.1.2' // noinspection DifferentKotlinGradleVersion classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" } } apply plugin: 'com.android.library' apply plugin: 'de.undercouch.download' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['VisionCamera_' + name] } def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['VisionCamera_' + name]).toInteger() } android { compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') buildToolsVersion getExtOrDefault('buildToolsVersion') ndkVersion getExtOrDefault('ndkVersion') defaultConfig { minSdkVersion 21 targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') externalNativeBuild { cmake { cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' arguments '-DANDROID_STL=c++_shared', "-DNODE_MODULES_DIR=${rootDir}/../node_modules", "-DFOR_HERMES=${FOR_HERMES}" } } } dexOptions { javaMaxHeapSize "4g" } externalNativeBuild { cmake { path "CMakeLists.txt" } } packagingOptions { excludes = ["**/libc++_shared.so", "**/libfbjni.so", "**/libjsi.so"] } buildTypes { release { minifyEnabled false } } lintOptions { disable 'GradleCompatible' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } configurations { extractHeaders extractJNI } } repositories { mavenCentral() google() def found = false def defaultDir = null def androidSourcesName = 'React Native sources' if (rootProject.ext.has('reactNativeAndroidRoot')) { defaultDir = rootProject.ext.get('reactNativeAndroidRoot') } else { defaultDir = new File( projectDir, '/../../../node_modules/react-native/android' ) } if (defaultDir.exists()) { maven { url defaultDir.toString() name androidSourcesName } logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}") found = true } else { def parentDir = rootProject.projectDir 1.upto(5, { if (found) return true parentDir = parentDir.parentFile def androidSourcesDir = new File( parentDir, 'node_modules/react-native' ) def androidPrebuiltBinaryDir = new File( parentDir, 'node_modules/react-native/android' ) if (androidPrebuiltBinaryDir.exists()) { maven { url androidPrebuiltBinaryDir.toString() name androidSourcesName } logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}") found = true } else if (androidSourcesDir.exists()) { maven { url androidSourcesDir.toString() name androidSourcesName } logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}") found = true } }) } if (!found) { throw new GradleException( "${project.name}: unable to locate React Native android sources. " + "Ensure you have you installed React Native as a dependency in your project and try again." ) } } def kotlin_version = getExtOrDefault('kotlinVersion') dependencies { // noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' implementation project(':react-native-reanimated') //noinspection GradleDynamicVersion extractHeaders("com.facebook.fbjni:fbjni:+:headers") //noinspection GradleDynamicVersion extractJNI("com.facebook.fbjni:fbjni:+") def rnAAR = fileTree("${rootDir}/../node_modules/react-native/android").matching({ it.include "**/**/*.aar" }).singleFile def jscAAR = fileTree("${rootDir}/../node_modules/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile def inputFile = new File(rootDir, '../node_modules/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 reaAAR = "${rootDir}/../node_modules/react-native-reanimated/android/react-native-reanimated-${minor}-${jsEngine}.aar" extractJNI(files(rnAAR, jscAAR, reaAAR)) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.5.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" implementation "androidx.camera:camera-core:1.1.0-alpha09" implementation "androidx.camera:camera-camera2:1.1.0-alpha09" implementation "androidx.camera:camera-lifecycle:1.1.0-alpha09" implementation "androidx.camera:camera-extensions:1.0.0-alpha29" implementation "androidx.camera:camera-view:1.0.0-alpha29" implementation "androidx.exifinterface:exifinterface:1.3.3" } // third-party-ndk deps headers // mostly a copy of https://github.com/software-mansion/react-native-reanimated/blob/master/android/build.gradle#L115 def downloadsDir = new File("$buildDir/downloads") def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") def thirdPartyVersionsFile = new File("${rootDir}/../node_modules/react-native/ReactAndroid/gradle.properties") def thirdPartyVersions = new Properties() thirdPartyVersions.load(new FileInputStream(thirdPartyVersionsFile)) def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] def boost_file = new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz") def DOUBLE_CONVERSION_VERSION = thirdPartyVersions["DOUBLE_CONVERSION_VERSION"] def double_conversion_file = new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz") def FOLLY_VERSION = thirdPartyVersions["FOLLY_VERSION"] def folly_file = new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz") def GLOG_VERSION = thirdPartyVersions["GLOG_VERSION"] def glog_file = new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz") task createNativeDepsDirectories { doLast { downloadsDir.mkdirs() thirdPartyNdkDir.mkdirs() } } 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") onlyIfNewer(true) overwrite(false) dest(boost_file) } task prepareBoost(dependsOn: downloadBoost, type: Copy) { from(tarTree(resources.gzip(downloadBoost.dest))) from("src/main/jni/third-party/boost/Android.mk") include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") includeEmptyDirs = false into("$thirdPartyNdkDir") // /boost_X_XX_X doLast { file("$thirdPartyNdkDir/boost_${BOOST_VERSION}").renameTo("$thirdPartyNdkDir/boost") } } task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(double_conversion_file) } task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) { from(tarTree(downloadDoubleConversion.dest)) from("src/main/jni/third-party/double-conversion/Android.mk") include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) includeEmptyDirs = false into("$thirdPartyNdkDir/double-conversion") } task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(folly_file) } task prepareFolly(dependsOn: downloadFolly, type: Copy) { from(tarTree(downloadFolly.dest)) from("src/main/jni/third-party/folly/Android.mk") include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } includeEmptyDirs = false into("$thirdPartyNdkDir/folly") } task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(glog_file) } task prepareGlog(dependsOn: downloadGlog, type: Copy) { from(tarTree(downloadGlog.dest)) from("src/main/jni/third-party/glog/") include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h") includeEmptyDirs = false filesMatching("**/*.h.in") { filter(ReplaceTokens, tokens: [ ac_cv_have_unistd_h : "1", ac_cv_have_stdint_h : "1", ac_cv_have_systypes_h : "1", ac_cv_have_inttypes_h : "1", ac_cv_have_libgflags : "0", ac_google_start_namespace : "namespace google {", ac_cv_have_uint16_t : "1", ac_cv_have_u_int16_t : "1", ac_cv_have___uint16 : "0", ac_google_end_namespace : "}", ac_cv_have___builtin_expect : "1", ac_google_namespace : "google", ac_cv___attribute___noinline : "__attribute__ ((noinline))", ac_cv___attribute___noreturn : "__attribute__ ((noreturn))", ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))" ]) it.path = (it.name - ".in") } into("$thirdPartyNdkDir/glog") doLast { copy { from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files) includeEmptyDirs = false into("$thirdPartyNdkDir/glog/exported/glog") } } } task prepareThirdPartyNdkHeaders { if (!boost_file.exists()) { dependsOn(prepareBoost) } if (!double_conversion_file.exists()) { dependsOn(prepareDoubleConversion) } if (!folly_file.exists()) { dependsOn(prepareFolly) } if (!glog_file.exists()) { dependsOn(prepareGlog) } } prepareThirdPartyNdkHeaders.mustRunAfter createNativeDepsDirectories task prepareHermes() { doLast { def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine") if (!hermesPackagePath) { throw new GradleScriptException("Could not find the hermes-engine npm package", null) } def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar") if (!hermesAAR.exists()) { throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.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" } } } prepareHermes.mustRunAfter prepareThirdPartyNdkHeaders task prepareJSC { doLast { def jscPackagePath = findNodeModulePath(projectDir, "jsc-android") if (!jscPackagePath) { throw new GradleScriptException("Could not find the jsc-android npm package", null) } def jscDist = file("$jscPackagePath/dist") if (!jscDist.exists()) { throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory", null) } def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" }) def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" }) copy { from(soFiles) from(headerFiles) from("$reactNative/ReactAndroid/src/main/jni/third-party/jsc/Android.mk") filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" }) includeEmptyDirs(false) into("$thirdPartyNdkDir/jsc") } } } prepareJSC.mustRunAfter prepareHermes task extractAARHeaders { doLast { configurations.extractHeaders.files.each { def file = it.absoluteFile copy { from zipTree(file) into "$buildDir/$file.name" include "**/*.h" } } } } extractAARHeaders.mustRunAfter prepareJSC task extractJNIFiles { doLast { configurations.extractJNI.files.each { def file = it.absoluteFile copy { from zipTree(file) into "$buildDir/$file.name" include "jni/**/*" } } } } extractJNIFiles.mustRunAfter extractAARHeaders // pre-native build pipeline tasks.whenTaskAdded { task -> if (!task.name.contains('Clean') && (task.name.contains('externalNative') || task.name.contains('CMake'))) { task.dependsOn(extractAARHeaders) task.dependsOn(extractJNIFiles) task.dependsOn(prepareJSC) task.dependsOn(prepareHermes) task.dependsOn(prepareThirdPartyNdkHeaders) } }