From b0711f1d35e56e5cb089d9b174692c77c094695a Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 1 Nov 2021 05:56:47 -0700 Subject: [PATCH] Update ReactAndroid to use the AGP NDK Apis (#32443) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/32443 This diff removes all the custom Gradle machinery to build the native code and delegates to AGP the triggering of the `ndk-build` command. This means that the native build will be now invoked with the `:ReactAndroid:externalNativeBuild` task. An important thing to notice is that that task will always run, and will delegate to Make the compilation avoidance. If you invoke the task twice, the second time it will be significantly faster. On my machine this takes ~6/7 mins the first time, and 30 seconds the second time. There are some gotchas that are worth noting: * The native build will run on every build now. Given the complexity of our native build graph, even with an up-to-date build, Make will still take ~30 seconds on my machine to analyse all the targets and mention that there is no work to be done. I believe this could be impactful for local development experience. The mitigation I found was to apply an `abiFilter` to build only the ABI of the target device (e.g. arm64 for a real device and so on). This reduces the native build to ~10 seconds. * All the change to the `react-native-gradle-plugin` source will cause the Gradle tasks to be considered invalid. Therefore they will re-extract the header files inside the folders that are used by Make to compile, triggering a near-full rebuild. This can be a bit painful when building locally, if you plan to edit react-native-gradle-plugin and relaunch rn-tester (seems to be like an edge case scenario but worth pointing out). The mitigation here would be to invoke the tasks like ``` gw :packages:rn-tester:android:app:installHermesDebug -x prepareBoost -x prepareLibevent -x prepareGlog \ -x prepareJSC -x extractNativeDependencies -x generateCodegenArtifactsFromSchema \ -x generateCodegenSchemaFromJavaScript ``` Changelog: [Internal] [Changed] - Refactor Extract Headers and JNI from AARs to an internal task Reviewed By: ShikaSD Differential Revision: D31683721 fbshipit-source-id: fa85793c567796f4e04751e10503717a88cb0620 --- .circleci/config.yml | 2 +- .gitignore | 1 + ReactAndroid/build.gradle | 140 ++++-------------- .../src/main/jni/react/jni/Android.mk | 2 +- packages/rn-tester/android/app/build.gradle | 8 +- template/android/app/build.gradle | 5 + 6 files changed, 45 insertions(+), 113 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a39f4b288f..e5b6a8ddd2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -515,7 +515,7 @@ jobs: - run: name: Compile Native Libs for Unit and Integration Tests command: ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=$BUILD_THREADS - no_output_timeout: 6m + no_output_timeout: 30m # Build JavaScript Bundle for instrumentation tests - run: diff --git a/.gitignore b/.gitignore index c60f27e2a0..6d419dcf55 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ project.xcworkspace /packages/rn-tester/android/app/gradlew /packages/rn-tester/android/app/gradlew.bat /ReactAndroid/build/ +/ReactAndroid/.cxx/ /ReactAndroid/gradle/ /ReactAndroid/gradlew /ReactAndroid/gradlew.bat diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 779bdea62d..ae8ddc796f 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -212,39 +212,6 @@ def findNodeModulePath(baseDir, packageName) { return null } -def getNdkBuildName() { - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - return "ndk-build.cmd" - } else { - return "ndk-build" - } -} - -def findNdkBuildFullPath() { - // android.ndkDirectory should return project.android.ndkVersion ndkDirectory - def ndkDir = android.ndkDirectory ? android.ndkDirectory.absolutePath : null - if (ndkDir) { - return new File(ndkDir, getNdkBuildName()).getAbsolutePath() - } - - // we allow to provide full path to ndk-build tool - if (hasProperty("ndk.command")) { - return property("ndk.command") - } - // or just a path to the containing directory - if (hasProperty("ndk.path")) { - ndkDir = property("ndk.path") - return new File(ndkDir, getNdkBuildName()).getAbsolutePath() - } - - // @TODO ANDROID_NDK && ndk.dir is deprecated and will be removed in the future. - if (System.getenv("ANDROID_NDK") != null) { - ndkDir = System.getenv("ANDROID_NDK") - return new File(ndkDir, getNdkBuildName()).getAbsolutePath() - } - - return null -} def reactNativeDevServerPort() { def value = project.getProperties().get("reactNativeDevServerPort") @@ -264,82 +231,15 @@ def reactNativeArchitectures() { return value != null && isDebug ? value : "all" } -def getNdkBuildFullPath() { - def ndkBuildFullPath = findNdkBuildFullPath() - if (ndkBuildFullPath == null) { - throw new GradleScriptException( - "ndk-build binary cannot be found, check if you've set " + - "\$ANDROID_NDK environment variable correctly or if ndk.dir is " + - "setup in local.properties", - null) - } - if (!new File(ndkBuildFullPath).canExecute()) { - throw new GradleScriptException( - "ndk-build binary " + ndkBuildFullPath + " doesn't exist or isn't executable.\n" + - "Check that the \$ANDROID_NDK environment variable, or ndk.dir in local.properties, is set correctly.\n" + - "(On Windows, make sure you escape backslashes in local.properties or use forward slashes, e.g. C:\\\\ndk or C:/ndk rather than C:\\ndk)", - null) - } - return ndkBuildFullPath +def ndkBuildJobs() { + return project.findProperty("jobs") ?: Runtime.runtime.availableProcessors() } -def buildReactNdkLib = tasks.register("buildReactNdkLib", Exec) { - dependsOn(prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFmt, prepareFolly, prepareGlog, prepareLibevent, extractNativeDependencies) - dependsOn("generateCodegenArtifactsFromSchema"); - - inputs.dir("$projectDir/../ReactCommon") - inputs.dir("src/main/jni") - inputs.dir("src/main/java/com/facebook/react/turbomodule/core/jni") - inputs.dir("src/main/java/com/facebook/react/modules/blob") - outputs.dir("$buildDir/react-ndk/all") - def commandLineArgs = [ - getNdkBuildFullPath(), - "APP_ABI=${reactNativeArchitectures()}", - "NDK_DEBUG=" + (nativeBuildType.equalsIgnoreCase("debug") ? "1" : "0"), - "NDK_PROJECT_PATH=null", - "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk", - "NDK_OUT=" + temporaryDir, - "NDK_LIBS_OUT=$buildDir/react-ndk/all", - "THIRD_PARTY_NDK_DIR=$thirdPartyNdkDir", - "REACT_COMMON_DIR=$projectDir/../ReactCommon", - "REACT_GENERATED_SRC_DIR=$buildDir/generated/source", - "REACT_SRC_DIR=$projectDir/src/main/java/com/facebook/react", - "-C", file("src/main/jni/react/jni").absolutePath, - "--jobs", project.findProperty("jobs") ?: Runtime.runtime.availableProcessors() - ] - if (Os.isFamily(Os.FAMILY_MAC)) { - // This flag will suppress "fcntl(): Bad file descriptor" warnings on local builds. - commandLineArgs.add("--output-sync=none") - } - commandLine(commandLineArgs) -} - -def cleanReactNdkLib = tasks.register("cleanReactNdkLib", Exec) { - ignoreExitValue(true) - errorOutput(new ByteArrayOutputStream()) - commandLine(getNdkBuildFullPath(), - "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk", - "THIRD_PARTY_NDK_DIR=$thirdPartyNdkDir", - "REACT_COMMON_DIR=$projectDir/../ReactCommon", - "-C", file("src/main/jni/react/jni").absolutePath, - "clean") - doLast { - file(AAR_OUTPUT_URL).delete() - println("Deleted aar output dir at ${file(AAR_OUTPUT_URL)}") - } -} - -def packageReactNdkLibs = tasks.register("packageReactNdkLibs", Copy) { - dependsOn(buildReactNdkLib) - from("$buildDir/react-ndk/all") - into("$buildDir/react-ndk/exported") +tasks.register("packageReactNdkLibsForBuck", Copy) { + dependsOn("mergeDebugNativeLibs") + from("$buildDir/intermediates/merged_native_libs/debug/out/lib/") exclude("**/libjsc.so") exclude("**/libhermes.so") -} - -def packageReactNdkLibsForBuck = tasks.register("packageReactNdkLibsForBuck", Copy) { - dependsOn(packageReactNdkLibs) - from("$buildDir/react-ndk/exported") into("src/main/jni/prebuilt/lib") } @@ -387,11 +287,36 @@ android { testApplicationId("com.facebook.react.tests.gradle") testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner") + + externalNativeBuild { + ndkBuild { + arguments "APP_ABI=${reactNativeArchitectures()}", + "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk", + "THIRD_PARTY_NDK_DIR=$thirdPartyNdkDir", + "REACT_COMMON_DIR=$projectDir/../ReactCommon", + "REACT_GENERATED_SRC_DIR=$buildDir/generated/source", + "REACT_SRC_DIR=$projectDir/src/main/java/com/facebook/react", + "-j${ndkBuildJobs()}" + + if (Os.isFamily(Os.FAMILY_MAC)) { + // This flag will suppress "fcntl(): Bad file descriptor" warnings on local builds. + arguments "--output-sync=none" + } + } + } } + externalNativeBuild { + ndkBuild { + path "src/main/jni/react/jni/Android.mk" + } + } + + preBuild.dependsOn(prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFmt, prepareFolly, prepareGlog, prepareLibevent, extractNativeDependencies) + preBuild.dependsOn("generateCodegenArtifactsFromSchema") + sourceSets.main { jni.srcDirs = [] - jniLibs.srcDir("$buildDir/react-ndk/exported") res.srcDirs = ["src/main/res/devsupport", "src/main/res/shell", "src/main/res/views/modal", "src/main/res/views/uimanager"] java { srcDirs = ["src/main/java", "src/main/libraries/soloader/java", "src/main/jni/first-party/fb/jni/java"] @@ -400,9 +325,6 @@ android { } } - preBuild.dependsOn(packageReactNdkLibs) - clean.dependsOn(cleanReactNdkLib) - lintOptions { abortOnError(false) } diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk index af2077b5a6..46548e2537 100644 --- a/ReactAndroid/src/main/jni/react/jni/Android.mk +++ b/ReactAndroid/src/main/jni/react/jni/Android.mk @@ -89,7 +89,7 @@ LOCAL_STATIC_LIBRARIES := libreactnative libruntimeexecutor libcallinvokerholder LOCAL_MODULE := reactnativejni # Compile all local c++ files. -LOCAL_SRC_FILES := $(wildcard *.cpp) +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) ifeq ($(APP_OPTIM),debug) # Keep symbols by overriding the strip command invoked by ndk-build. diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index 0ec74b694e..a2ee432bdb 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -213,6 +213,10 @@ android { ] } } + packagingOptions { + pickFirst '**/libhermes.so' + pickFirst '**/libjsc.so' + } } configurations { @@ -287,9 +291,9 @@ if (enableCodegen) { def packageReactNdkLibs = tasks.register("packageReactNdkLibs", Copy) { // TODO: handle extracting .so from prebuilt :ReactAndroid. - dependsOn(":ReactAndroid:packageReactNdkLibs") + dependsOn(":ReactAndroid:packageReactNdkLibsForBuck") dependsOn("generateCodegenSchemaFromJavaScript") - from("$reactAndroidBuildDir/react-ndk/exported") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") into("$buildDir/react-ndk/exported") } diff --git a/template/android/app/build.gradle b/template/android/app/build.gradle index a461d9a900..022855a20b 100644 --- a/template/android/app/build.gradle +++ b/template/android/app/build.gradle @@ -186,6 +186,11 @@ android { } } + + packagingOptions { + pickFirst '**/libhermes.so' + pickFirst '**/libjsc.so' + } } dependencies {