From 093c892e2fc4d22d166bb7f641901e3dbe5c8521 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 12 Dec 2023 10:22:32 -0800 Subject: [PATCH 1/4] Convert ReactAndroid and RN-Tester to Kotlin DSL (#41834) Summary: I'm updating other two `build.gradle` to `build.gradle.kts` files. The only functional change I made was to remove the function to check if major >= 1 and turn on New Architecture. This needs to be moved to RNGP as Kotlin doesn't have dynamic accessors to Object so we can't convert that function. Changelog: [Internal] [Changed] - Convert ReactAndroid and RN-Tester to Kotlin DSL Reviewed By: mdvacca Differential Revision: D51856356 --- .../react-native/ReactAndroid/build.gradle | 819 ------------------ .../ReactAndroid/build.gradle.kts | 787 +++++++++++++++++ packages/rn-tester/android/app/build.gradle | 226 ----- .../rn-tester/android/app/build.gradle.kts | 199 +++++ 4 files changed, 986 insertions(+), 1045 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/build.gradle create mode 100644 packages/react-native/ReactAndroid/build.gradle.kts delete mode 100644 packages/rn-tester/android/app/build.gradle create mode 100644 packages/rn-tester/android/app/build.gradle.kts diff --git a/packages/react-native/ReactAndroid/build.gradle b/packages/react-native/ReactAndroid/build.gradle deleted file mode 100644 index 3dbd23de72c928..00000000000000 --- a/packages/react-native/ReactAndroid/build.gradle +++ /dev/null @@ -1,819 +0,0 @@ -/* - * 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. - */ - -plugins { - id("maven-publish") - id("com.facebook.react") - alias(libs.plugins.android.library) - alias(libs.plugins.download) - alias(libs.plugins.kotlin.android) -} - -import com.facebook.react.tasks.internal.* -import com.facebook.react.tasks.internal.utils.* - -import java.nio.file.Paths - -import kotlin.Pair - -import de.undercouch.gradle.tasks.download.Download - -version = VERSION_NAME -group = "com.facebook.react" - -// We download various C++ open-source dependencies into downloads. -// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk. -// After that we build native code from src/main/jni with module path pointing at third-party-ndk. - -def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR") -def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads") -def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") -def reactNativeRootDir = projectDir.parent - -// We put the publishing version from gradle.properties inside ext. so other -// subprojects can access it as well. -ext.publishing_version = VERSION_NAME - -// This is the version of CMake we're requesting to the Android SDK to use. -// If missing it will be downloaded automatically. Only CMake versions shipped with the -// Android SDK are supported (you can find them listed in the SDK Manager of Android Studio). -def cmakeVersion = System.getenv("CMAKE_VERSION") ?: "3.22.1" -ext.cmake_version = cmakeVersion - -// You need to have following folders in this directory: -// - boost_1_83_0 -// - double-conversion-1.1.6 -// - folly-deprecate-dynamic-initializer -// - glog-0.3.5 -def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") - -// The Boost library is a very large download (>100MB). -// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable -// and the build will use that. -def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH") - -// By setting REACT_NATIVE_SKIP_PREFAB we can skip prefab publishing, to -// reduce the size of the React Native published .AAR. -def skipPrefabPublishing = System.getenv("REACT_NATIVE_SKIP_PREFAB") != null -def prefabHeadersDir = project.file("$buildDir/prefab-headers") - -// Native versions which are defined inside the version catalog (libs.versions.toml) -def BOOST_VERSION = libs.versions.boost.get() -def DOUBLE_CONVERSION_VERSION = libs.versions.doubleconversion.get() -def FMT_VERSION = libs.versions.fmt.get() -def FOLLY_VERSION = libs.versions.folly.get() -def GLOG_VERSION = libs.versions.glog.get() -def GTEST_VERSION = libs.versions.gtest.get() - -final def preparePrefab = tasks.register("preparePrefab", PreparePrefabHeadersTask) { - dependsOn(prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog) - dependsOn("generateCodegenArtifactsFromSchema") - // To export to a ReactNativePrefabProcessingEntities.kt once all - // libraries have been moved. We keep it here for now as it make easier to - // migrate one library at a time. - it.input.set( - [ - new PrefabPreprocessingEntry( - "react_render_debug", - new Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/") - ), - new PrefabPreprocessingEntry( - "turbomodulejsijni", - new Pair("src/main/jni/react/turbomodule", "") - ), - new PrefabPreprocessingEntry( - "runtimeexecutor", - new Pair("../ReactCommon/runtimeexecutor/", "") - ), - new PrefabPreprocessingEntry( - "react_codegen_rncore", - new Pair(new File(buildDir, "generated/source/codegen/jni/").absolutePath, "") - ), - new PrefabPreprocessingEntry( - "react_debug", - new Pair("../ReactCommon/react/debug/", "react/debug/") - ), - new PrefabPreprocessingEntry( - "react_render_componentregistry", - new Pair("../ReactCommon/react/renderer/componentregistry/", "react/renderer/componentregistry/") - ), - new PrefabPreprocessingEntry( - "react_newarchdefaults", - new Pair("src/main/jni/react/newarchdefaults", "") - ), - new PrefabPreprocessingEntry( - "react_cxxreactpackage", - new Pair("src/main/jni/react/runtime/cxxreactpackage", "") - ), - new PrefabPreprocessingEntry( - "react_render_animations", - new Pair("../ReactCommon/react/renderer/animations/", "react/renderer/animations/") - ), - new PrefabPreprocessingEntry( - "react_render_core", - new Pair("../ReactCommon/react/renderer/core/", "react/renderer/core/") - ), - new PrefabPreprocessingEntry( - "react_render_graphics", - [ - new Pair("../ReactCommon/react/renderer/graphics/", "react/renderer/graphics/"), - new Pair("../ReactCommon/react/renderer/graphics/platform/android/", ""), - ] - ), - new PrefabPreprocessingEntry( - "rrc_root", - new Pair("../ReactCommon/react/renderer/components/root/", "react/renderer/components/root/") - ), - new PrefabPreprocessingEntry( - "rrc_view", - [ - new Pair("../ReactCommon/react/renderer/components/view/", "react/renderer/components/view/"), - new Pair("../ReactCommon/react/renderer/components/view/platform/android/", ""), - ] - ), - new PrefabPreprocessingEntry( - "rrc_legacyviewmanagerinterop", - new Pair("../ReactCommon/react/renderer/components/legacyviewmanagerinterop/", "react/renderer/components/legacyviewmanagerinterop/") - ), - new PrefabPreprocessingEntry( - "jsi", - new Pair("../ReactCommon/jsi/", "") - ), - new PrefabPreprocessingEntry( - "glog", - new Pair(new File(buildDir, "third-party-ndk/glog/exported/").absolutePath, "") - ), - new PrefabPreprocessingEntry( - "fabricjni", - new Pair("src/main/jni/react/fabric", "react/fabric/") - ), - new PrefabPreprocessingEntry( - "react_render_mapbuffer", - new Pair("../ReactCommon/react/renderer/mapbuffer/", "react/renderer/mapbuffer/") - ), - new PrefabPreprocessingEntry( - "yoga", - [ - new Pair("../ReactCommon/yoga/", ""), - new Pair("src/main/jni/first-party/yogajni/jni", "") - ] - ), - new PrefabPreprocessingEntry( - "folly_runtime", - [ - new Pair(new File(buildDir, "third-party-ndk/fmt/include/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/folly/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/boost/boost_1_83_0/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/double-conversion/").absolutePath, ""), - ] - ), - new PrefabPreprocessingEntry( - "react_nativemodule_core", - [ - new Pair(new File(buildDir, "third-party-ndk/boost/boost_1_83_0/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/double-conversion/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/fmt/include/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/folly/").absolutePath, ""), - new Pair(new File(buildDir, "third-party-ndk/glog/exported/").absolutePath, ""), - new Pair("../ReactCommon/butter/", "butter/"), - new Pair("../ReactCommon/callinvoker/", ""), - new Pair("../ReactCommon/cxxreact/", "cxxreact/"), - new Pair("../ReactCommon/react/bridging/", "react/bridging/"), - new Pair("../ReactCommon/react/config/", "react/config/"), - new Pair("../ReactCommon/react/nativemodule/core/", ""), - new Pair("../ReactCommon/react/nativemodule/core/platform/android/", ""), - new Pair("../ReactCommon/react/renderer/componentregistry/", "react/renderer/componentregistry/"), - new Pair("../ReactCommon/react/renderer/components/root/", "react/renderer/components/root/"), - new Pair("../ReactCommon/react/renderer/core/", "react/renderer/core/"), - new Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/"), - new Pair("../ReactCommon/react/renderer/leakchecker/", "react/renderer/leakchecker/"), - new Pair("../ReactCommon/react/renderer/mapbuffer/", "react/renderer/mapbuffer/"), - new Pair("../ReactCommon/react/renderer/mounting/", "react/renderer/mounting/"), - new Pair("../ReactCommon/react/renderer/runtimescheduler/", "react/renderer/runtimescheduler/"), - new Pair("../ReactCommon/react/renderer/scheduler/", "react/renderer/scheduler/"), - new Pair("../ReactCommon/react/renderer/telemetry/", "react/renderer/telemetry/"), - new Pair("../ReactCommon/react/renderer/uimanager/", "react/renderer/uimanager/"), - new Pair("../ReactCommon/react/debug/", "react/debug/"), - new Pair("../ReactCommon/react/utils/", "react/utils/"), - new Pair("src/main/jni/react/jni", "react/jni/"), - ] - ), - new PrefabPreprocessingEntry( - "react_utils", - new Pair("../ReactCommon/react/utils/", "react/utils/"), - ), - new PrefabPreprocessingEntry( - "react_render_imagemanager", - [ - new Pair("../ReactCommon/react/renderer/imagemanager/", "react/renderer/imagemanager/"), - new Pair("../ReactCommon/react/renderer/imagemanager/platform/cxx/", ""), - ] - ), - new PrefabPreprocessingEntry( - "rrc_image", - new Pair("../ReactCommon/react/renderer/components/image/", "react/renderer/components/image/") - ), - // These prefab targets are used by Expo & Reanimated - new PrefabPreprocessingEntry( - "hermes_executor", - // "hermes_executor" is statically linking against "hermes_executor_common" - // and "hermes_inspector_modern". Here we expose only the headers that we know are needed. - new Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/") - ), - new PrefabPreprocessingEntry( - "jscexecutor", - // "jscexecutor" is statically linking against "jscruntime" - // Here we expose only the headers that we know are needed. - new Pair("../ReactCommon/jsc/", "jsc/") - ), - new PrefabPreprocessingEntry( - "react_render_uimanager", - new Pair("../ReactCommon/react/renderer/uimanager/", "react/renderer/uimanager/"), - ), - new PrefabPreprocessingEntry( - "react_render_scheduler", - new Pair("../ReactCommon/react/renderer/scheduler/", "react/renderer/scheduler/"), - ), - new PrefabPreprocessingEntry( - "react_render_mounting", - new Pair("../ReactCommon/react/renderer/mounting/", "react/renderer/mounting/"), - ), - new PrefabPreprocessingEntry( - "reactnativejni", - [ - new Pair("src/main/jni/react/jni", "react/jni/"), - new Pair("../ReactCommon/cxxreact/", "cxxreact/"), - ] - ), - new PrefabPreprocessingEntry( - "jsinspector", - new Pair("../ReactCommon/jsinspector-modern/", "jsinspector-modern/"), - ), - ] - ) - it.outputDir.set(prefabHeadersDir) -} - -task createNativeDepsDirectories { - downloadsDir.mkdirs() - thirdPartyNdkDir.mkdirs() -} - -task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION.replace("_", ".")}/source/boost_${BOOST_VERSION}.tar.gz") - onlyIfModified(true) - overwrite(false) - retries(5) - dest(new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz")) -} - -final def prepareBoost = tasks.register("prepareBoost", PrepareBoostTask) { - it.dependsOn(boostPath ? [] : [downloadBoost]) - it.boostPath.setFrom(boostPath ?: tarTree(downloadBoost.dest)) - it.boostVersion.set(BOOST_VERSION) - it.outputDir.set(new File(thirdPartyNdkDir, "boost")) -} - -task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") - onlyIfModified(true) - overwrite(false) - retries(5) - dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz")) -} - -task prepareDoubleConversion(dependsOn: dependenciesPath ? [] : [downloadDoubleConversion], type: Copy) { - from(dependenciesPath ?: tarTree(downloadDoubleConversion.dest)) - from("src/main/jni/third-party/double-conversion/") - include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "CMakeLists.txt") - 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") - onlyIfModified(true) - overwrite(false) - retries(5) - dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz")) -} - -task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy) { - from(dependenciesPath ?: tarTree(downloadFolly.dest)) - from("src/main/jni/third-party/folly/") - include("folly-${FOLLY_VERSION}/folly/**/*", "CMakeLists.txt") - eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } - includeEmptyDirs = false - into("$thirdPartyNdkDir/folly") -} - -task downloadFmt(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/fmtlib/fmt/archive/${FMT_VERSION}.tar.gz") - onlyIfModified(true) - overwrite(false) - retries(5) - dest(new File(downloadsDir, "fmt-${FMT_VERSION}.tar.gz")) -} - -task prepareFmt(dependsOn: dependenciesPath ? [] : [downloadFmt], type: Copy) { - from(dependenciesPath ?: tarTree(downloadFmt.dest)) - from("src/main/jni/third-party/fmt/") - include("fmt-${FMT_VERSION}/src/**/*", "fmt-${FMT_VERSION}/include/**/*", "CMakeLists.txt") - eachFile { fname -> fname.path = (fname.path - "fmt-${FMT_VERSION}/") } - includeEmptyDirs = false - into("$thirdPartyNdkDir/fmt") -} - -task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") - onlyIfModified(true) - overwrite(false) - retries(5) - dest(new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz")) -} - -task downloadGtest(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/googletest/archive/refs/tags/release-${GTEST_VERSION}.tar.gz") - onlyIfModified(true) - overwrite(false) - retries(5) - dest(new File(downloadsDir, "gtest.tar.gz")) -} - -task prepareGtest(dependsOn: dependenciesPath ? [] : [downloadGtest], type: Copy) { - from(dependenciesPath ?: tarTree(downloadGtest.dest)) - eachFile { fname -> { - fname.path = (fname.path - "googletest-release-${GTEST_VERSION}/") - } - } - into(new File(thirdPartyNdkDir,"googletest")) -} - -// Prepare glog sources to be compiled, this task will perform steps that normally should've been -// executed by automake. This way we can avoid dependencies on make/automake -final def prepareGlog = tasks.register("prepareGlog", PrepareGlogTask) { - it.dependsOn(dependenciesPath ? [] : [downloadGlog]) - it.glogPath.setFrom(dependenciesPath ?: tarTree(downloadGlog.dest)) - it.glogVersion.set(GLOG_VERSION) - it.outputDir.set(new File(thirdPartyNdkDir, "glog")) -} - -// Create Android native library module based on jsc from npm -tasks.register('prepareJSC', PrepareJSCTask) { - it.jscPackagePath.set(findNodeModulePath(projectDir, "jsc-android")) - it.outputDir = project.layout.buildDirectory.dir("third-party-ndk/jsc") -} - -task prepareKotlinBuildScriptModel { - // This task is run when Gradle Sync is running. - // We create it here so we can let it depend on preBuild inside the android{} -} - -// As ReactAndroid builds from source, the codegen needs to be built before it can be invoked. -// This is not the case for users of React Native, as we ship a compiled version of the codegen. -final def buildCodegenCLITask = tasks.register('buildCodegenCLI', BuildCodegenCLITask) { - it.codegenDir.set(file("$rootDir/node_modules/@react-native/codegen")) - it.bashWindowsHome.set(project.findProperty("react.internal.windowsBashPath")) - it.onlyIf { - // For build from source scenario, we don't need to build the codegen at all. - rootProject.name != "react-native-build-from-source" - } -} - -/** - * 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. - */ -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 reactNativeDevServerPort() { - def value = project.getProperties().get("reactNativeDevServerPort") - return value != null ? value : "8081" -} - -def reactNativeInspectorProxyPort() { - def value = project.getProperties().get("reactNativeInspectorProxyPort") - return value != null ? value : reactNativeDevServerPort() -} - -def reactNativeArchitectures() { - def value = project.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} - -def enableWarningsAsErrors() { - def value = project.getProperties().get("enableWarningsAsErrors") - return value != null ? value : false -} - -task packageReactNdkLibsForBuck(type: Copy) { - dependsOn("mergeDebugNativeLibs") - // Shared libraries (.so) are copied from the merged_native_libs folder instead - from("$buildDir/intermediates/merged_native_libs/debug/out/lib/") - exclude("**/libjsc.so") - exclude("**/libhermes.so") - into("src/main/jni/prebuilt/lib") -} - -task installArchives { - dependsOn("publishAllPublicationsToNpmRepository") -} - -repositories { - // Normally RNGP will set repositories for all modules, - // but when consumed from source, we need to re-declare - // those repositories as there is no app module there. - mavenCentral() - google() -} - -android { - compileSdk libs.versions.compileSdk.get().toInteger() - buildToolsVersion = libs.versions.buildTools.get() - namespace "com.facebook.react" - - // Used to override the NDK path/version on internal CI or by allowing - // users to customize the NDK path/version from their root project (e.g. for Apple Silicon support) - if (rootProject.hasProperty("ndkPath")) { - ndkPath rootProject.ext.ndkPath - } - if (rootProject.hasProperty("ndkVersion")) { - ndkVersion rootProject.ext.ndkVersion - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - // Using '-Xjvm-default=all' to generate default java methods for interfaces - freeCompilerArgs = ['-Xjvm-default=all'] - // Using -PenableWarningsAsErrors=true prop to enable allWarningsAsErrors - kotlinOptions.allWarningsAsErrors = enableWarningsAsErrors() - } - - defaultConfig { - minSdk = libs.versions.minSdk.get().toInteger() - targetSdk = libs.versions.targetSdk.get().toInteger() - versionCode(1) - versionName("1.0") - - consumerProguardFiles("proguard-rules.pro") - - buildConfigField("boolean", "IS_INTERNAL_BUILD", "false") - buildConfigField("int", "EXOPACKAGE_FLAGS", "0") - - resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort() - resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort() - - testApplicationId("com.facebook.react.tests.gradle") - testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner") - - externalNativeBuild { - cmake { - arguments "-DREACT_COMMON_DIR=${reactNativeRootDir}/ReactCommon", - "-DREACT_ANDROID_DIR=$projectDir", - "-DREACT_BUILD_DIR=$buildDir", - "-DANDROID_STL=c++_shared", - "-DANDROID_TOOLCHAIN=clang", - // Due to https://github.com/android/ndk/issues/1693 we're losing Android - // specific compilation flags. This can be removed once we moved to NDK 25/26 - "-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON" - - targets "jsijniprofiler", - "reactnativeblob", - "reactperfloggerjni", - "bridgeless", - "rninstance", - "hermesinstancejni", - "uimanagerjni", - "jscinstance", - // prefab targets - "reactnativejni", - "react_render_debug", - "turbomodulejsijni", - "runtimeexecutor", - "react_codegen_rncore", - "react_debug", - "react_utils", - "react_render_componentregistry", - "react_newarchdefaults", - "react_cxxreactpackage", - "react_render_animations", - "react_render_core", - "react_render_graphics", - "rrc_image", - "rrc_root", - "rrc_view", - "rrc_legacyviewmanagerinterop", - "jsi", - "glog", - "fabricjni", - "react_render_mapbuffer", - "yoga", - "folly_runtime", - "react_nativemodule_core", - "react_render_imagemanager", - "react_render_uimanager", - "react_render_scheduler", - "react_render_mounting", - "hermes_executor", - "jscexecutor", - "jsinspector" - } - } - ndk { - abiFilters(*reactNativeArchitectures()) - } - } - - externalNativeBuild { - cmake { - version cmakeVersion - path "src/main/jni/CMakeLists.txt" - } - } - - buildTypes { - debug { - externalNativeBuild { - cmake { - // We want to build Gtest suite only for the debug variant. - targets "reactnative_unittest" - } - } - } - } - - preBuild.dependsOn( - buildCodegenCLITask, - generateCodegenArtifactsFromSchema, - prepareBoost, - prepareDoubleConversion, - prepareFmt, - prepareFolly, - prepareGlog, - prepareGtest, - prepareJSC, - preparePrefab - ) - generateCodegenSchemaFromJavaScript.dependsOn(buildCodegenCLITask) - prepareKotlinBuildScriptModel.dependsOn( - preBuild, - ":packages:react-native:ReactAndroid:hermes-engine:preBuild", - ) - - sourceSets.main { - 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"] - exclude("com/facebook/annotationprocessors") - exclude("com/facebook/react/processing") - exclude("com/facebook/react/module/processing") - } - } - - lintOptions { - abortOnError(false) - } - - packagingOptions { - exclude("META-INF/NOTICE") - exclude("META-INF/LICENSE") - // We intentionally don't want to bundle any JS Runtime inside the Android AAR - // we produce. The reason behind this is that we want to allow users to pick the - // JS engine by specifying a dependency on either `hermes-engine` or `android-jsc` - // that will include the necessary .so files to load. - exclude("**/libhermes.so") - exclude("**/libjsc.so") - } - - buildFeatures { - prefab true - prefabPublishing !skipPrefabPublishing - buildConfig true - } - - prefab { - react_render_debug { - headers(new File(prefabHeadersDir, "react_render_debug").absolutePath) - } - turbomodulejsijni { - headers(new File(prefabHeadersDir, "turbomodulejsijni").absolutePath) - } - runtimeexecutor { - headers(new File(prefabHeadersDir, "runtimeexecutor").absolutePath) - } - react_codegen_rncore { - headers(new File(prefabHeadersDir, "react_codegen_rncore").absolutePath) - } - react_debug { - headers(new File(prefabHeadersDir, "react_debug").absolutePath) - } - react_utils { - headers(new File(prefabHeadersDir, "react_utils").absolutePath) - } - react_render_componentregistry { - headers(new File(prefabHeadersDir, "react_render_componentregistry").absolutePath) - } - react_newarchdefaults { - headers(new File(prefabHeadersDir, "react_newarchdefaults").absolutePath) - } - react_cxxreactpackage { - headers(new File(prefabHeadersDir, "react_cxxreactpackage").absolutePath) - } - react_render_animations { - headers(new File(prefabHeadersDir, "react_render_animations").absolutePath) - } - react_render_core { - headers(new File(prefabHeadersDir, "react_render_core").absolutePath) - } - react_render_graphics { - headers(new File(prefabHeadersDir, "react_render_graphics").absolutePath) - } - rrc_image { - headers(new File(prefabHeadersDir, "rrc_image").absolutePath) - } - rrc_root { - headers(new File(prefabHeadersDir, "rrc_root").absolutePath) - } - rrc_view { - headers(new File(prefabHeadersDir, "rrc_view").absolutePath) - } - rrc_legacyviewmanagerinterop { - headers(new File(prefabHeadersDir, "rrc_legacyviewmanagerinterop").absolutePath) - } - jsi { - headers(new File(prefabHeadersDir, "jsi").absolutePath) - } - glog { - headers(new File(prefabHeadersDir, "glog").absolutePath) - } - fabricjni { - headers(new File(prefabHeadersDir, "fabricjni").absolutePath) - } - react_render_mapbuffer { - headers(new File(prefabHeadersDir, "react_render_mapbuffer").absolutePath) - } - yoga { - headers(new File(prefabHeadersDir, "yoga").absolutePath) - } - folly_runtime { - headers(new File(prefabHeadersDir, "folly_runtime").absolutePath) - } - react_nativemodule_core { - headers(new File(prefabHeadersDir, "react_nativemodule_core").absolutePath) - } - react_render_imagemanager { - headers(new File(prefabHeadersDir, "react_render_imagemanager").absolutePath) - } - react_render_uimanager { - headers(new File(prefabHeadersDir, "react_render_uimanager").absolutePath) - } - react_render_scheduler { - headers(new File(prefabHeadersDir, "react_render_scheduler").absolutePath) - } - react_render_mounting { - headers(new File(prefabHeadersDir, "react_render_mounting").absolutePath) - } - reactnativejni { - headers(new File(prefabHeadersDir, "reactnativejni").absolutePath) - } - hermes_executor { - headers(new File(prefabHeadersDir, "hermes_executor").absolutePath) - } - jscexecutor { - headers(new File(prefabHeadersDir, "jscexecutor").absolutePath) - } - jsinspector { - headers(new File(prefabHeadersDir, "jsinspector").absolutePath) - } - } - - publishing { - multipleVariants { - withSourcesJar() - includeBuildTypeValues('debug', 'release') - } - } - - testOptions { - unitTests { - includeAndroidResources = true - } - } -} - -dependencies { - api(libs.androidx.appcompat) - api(libs.androidx.appcompat.resources) - api(libs.androidx.autofill) - api(libs.androidx.swiperefreshlayout) - api(libs.androidx.tracing) - - api(libs.fbjni) - api(libs.fresco) - api(libs.fresco.middleware) - api(libs.fresco.imagepipeline.okhttp3) - api(libs.fresco.ui.common) - api(libs.infer.annotation) - api(libs.soloader) - api(libs.yoga.proguard.annotations) - - api(libs.jsr305) - api(libs.okhttp3.urlconnection) - api(libs.okhttp3) - api(libs.okio) - compileOnly(libs.javax.annotation.api) - api(libs.javax.inject) - - // It's up to the consumer to decide if hermes should be included or not. - // Therefore hermes-engine is a compileOnly dependency. - compileOnly(project(":packages:react-native:ReactAndroid:hermes-engine")) - - testImplementation(libs.junit) - testImplementation(libs.assertj) - testImplementation(libs.mockito) - testImplementation(libs.robolectric) - testImplementation(libs.thoughtworks) - - androidTestImplementation(libs.androidx.test.runner) - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.mockito) -} - -react { - // TODO: The library name is chosen for parity with Fabric components & iOS - // This should be changed to a more generic name, e.g. `ReactCoreSpec`. - libraryName = "rncore" - jsRootDir = file("../Libraries") -} - -// For build from source, we need to override the privateReact extension. -// This is neeeded as the build-from-source won't have a com.android.application -// module to apply the plugin to, so it's codegenDir and reactNativeDir won't be evaluated. -if (rootProject.name == "react-native-build-from-source") { - privateReact { - codegenDir = file("$rootDir/../@react-native/codegen") - reactNativeDir = file("$rootDir") - } -} - -kotlin { - jvmToolchain(17) - - explicitApi() -} - -apply plugin: "org.jetbrains.kotlin.android" - -/* Publishing Configuration */ -apply from: "./publish.gradle" - -// We need to override the artifact ID as this project is called `ReactAndroid` but -// the maven coordinates are on `react-android`. -// Please note that the original coordinates, `react-native`, have been voided -// as they caused https://github.com/facebook/react-native/issues/35210 -publishing { - publications { - getByName("release") { - artifactId 'react-android' - } - } -} diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts new file mode 100644 index 00000000000000..bb035d25c75899 --- /dev/null +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -0,0 +1,787 @@ +/* + * 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. + */ + +import com.android.build.gradle.internal.tasks.factory.dependsOn +import com.facebook.react.internal.PrivateReactExtension +import com.facebook.react.tasks.internal.* +import com.facebook.react.tasks.internal.utils.* +import de.undercouch.gradle.tasks.download.Download +import java.nio.file.Paths + +plugins { + id("maven-publish") + id("com.facebook.react") + alias(libs.plugins.android.library) + alias(libs.plugins.download) + alias(libs.plugins.kotlin.android) +} + +version = project.findProperty("VERSION_NAME")?.toString()!! + +group = "com.facebook.react" + +// We download various C++ open-source dependencies into downloads. +// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk. +// After that we build native code from src/main/jni with module path pointing at third-party-ndk. + +val downloadsDir = + if (System.getenv("REACT_NATIVE_DOWNLOADS_DIR") != null) { + File(System.getenv("REACT_NATIVE_DOWNLOADS_DIR")) + } else { + File("$buildDir/downloads") + } +val thirdPartyNdkDir = File("$buildDir/third-party-ndk") +val reactNativeRootDir = projectDir.parent + +// We put the publishing version from gradle.properties inside ext. so other +// subprojects can access it as well. +extra["publishing_version"] = project.findProperty("VERSION_NAME")?.toString()!! + +// This is the version of CMake we're requesting to the Android SDK to use. +// If missing it will be downloaded automatically. Only CMake versions shipped with the +// Android SDK are supported (you can find them listed in the SDK Manager of Android Studio). +val cmakeVersion = System.getenv("CMAKE_VERSION") ?: "3.22.1" + +extra["cmake_version"] = cmakeVersion + +// You need to have following folders in this directory: +// - boost_1_83_0 +// - double-conversion-1.1.6 +// - folly-deprecate-dynamic-initializer +// - glog-0.3.5 +val dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") + +// The Boost library is a very large download (>100MB). +// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable +// and the build will use that. +val boostPathOverride = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH") + +// By setting REACT_NATIVE_SKIP_PREFAB we can skip prefab publishing, to +// reduce the size of the React Native published .AAR. +val skipPrefabPublishing = System.getenv("REACT_NATIVE_SKIP_PREFAB") != null +val prefabHeadersDir = project.file("$buildDir/prefab-headers") + +// Native versions which are defined inside the version catalog (libs.versions.toml) +val BOOST_VERSION = libs.versions.boost.get() +val DOUBLE_CONVERSION_VERSION = libs.versions.doubleconversion.get() +val FMT_VERSION = libs.versions.fmt.get() +val FOLLY_VERSION = libs.versions.folly.get() +val GLOG_VERSION = libs.versions.glog.get() +val GTEST_VERSION = libs.versions.gtest.get() + +val preparePrefab by + tasks.registering(PreparePrefabHeadersTask::class) { + dependsOn(prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog) + dependsOn("generateCodegenArtifactsFromSchema") + // To export to a ReactNativePrefabProcessingEntities.kt once all + // libraries have been moved. We keep it here for now as it make easier to + // migrate one library at a time. + input.set( + listOf( + PrefabPreprocessingEntry( + "react_render_debug", + Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/")), + PrefabPreprocessingEntry( + "turbomodulejsijni", Pair("src/main/jni/react/turbomodule", "")), + PrefabPreprocessingEntry( + "runtimeexecutor", Pair("../ReactCommon/runtimeexecutor/", "")), + PrefabPreprocessingEntry( + "react_codegen_rncore", + Pair(File(buildDir, "generated/source/codegen/jni/").absolutePath, "")), + PrefabPreprocessingEntry( + "react_debug", Pair("../ReactCommon/react/debug/", "react/debug/")), + PrefabPreprocessingEntry( + "react_render_componentregistry", + Pair( + "../ReactCommon/react/renderer/componentregistry/", + "react/renderer/componentregistry/")), + PrefabPreprocessingEntry( + "react_newarchdefaults", Pair("src/main/jni/react/newarchdefaults", "")), + PrefabPreprocessingEntry( + "react_cxxreactpackage", Pair("src/main/jni/react/runtime/cxxreactpackage", "")), + PrefabPreprocessingEntry( + "react_render_animations", + Pair("../ReactCommon/react/renderer/animations/", "react/renderer/animations/")), + PrefabPreprocessingEntry( + "react_render_core", + Pair("../ReactCommon/react/renderer/core/", "react/renderer/core/")), + PrefabPreprocessingEntry( + "react_render_graphics", + listOf( + Pair("../ReactCommon/react/renderer/graphics/", "react/renderer/graphics/"), + Pair("../ReactCommon/react/renderer/graphics/platform/android/", ""), + )), + PrefabPreprocessingEntry( + "rrc_root", + Pair( + "../ReactCommon/react/renderer/components/root/", + "react/renderer/components/root/")), + PrefabPreprocessingEntry( + "rrc_view", + listOf( + Pair( + "../ReactCommon/react/renderer/components/view/", + "react/renderer/components/view/"), + Pair("../ReactCommon/react/renderer/components/view/platform/android/", ""), + )), + PrefabPreprocessingEntry( + "rrc_legacyviewmanagerinterop", + Pair( + "../ReactCommon/react/renderer/components/legacyviewmanagerinterop/", + "react/renderer/components/legacyviewmanagerinterop/")), + PrefabPreprocessingEntry("jsi", Pair("../ReactCommon/jsi/", "")), + PrefabPreprocessingEntry( + "glog", Pair(File(buildDir, "third-party-ndk/glog/exported/").absolutePath, "")), + PrefabPreprocessingEntry( + "fabricjni", Pair("src/main/jni/react/fabric", "react/fabric/")), + PrefabPreprocessingEntry( + "react_render_mapbuffer", + Pair("../ReactCommon/react/renderer/mapbuffer/", "react/renderer/mapbuffer/")), + PrefabPreprocessingEntry( + "yoga", + listOf( + Pair("../ReactCommon/yoga/", ""), + Pair("src/main/jni/first-party/yogajni/jni", ""))), + PrefabPreprocessingEntry( + "folly_runtime", + listOf( + Pair(File(buildDir, "third-party-ndk/fmt/include/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/folly/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/boost/boost_1_83_0/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/double-conversion/").absolutePath, ""), + )), + PrefabPreprocessingEntry( + "react_nativemodule_core", + listOf( + Pair(File(buildDir, "third-party-ndk/boost/boost_1_83_0/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/double-conversion/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/fmt/include/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/folly/").absolutePath, ""), + Pair(File(buildDir, "third-party-ndk/glog/exported/").absolutePath, ""), + Pair("../ReactCommon/butter/", "butter/"), + Pair("../ReactCommon/callinvoker/", ""), + Pair("../ReactCommon/cxxreact/", "cxxreact/"), + Pair("../ReactCommon/react/bridging/", "react/bridging/"), + Pair("../ReactCommon/react/config/", "react/config/"), + Pair("../ReactCommon/react/nativemodule/core/", ""), + Pair("../ReactCommon/react/nativemodule/core/platform/android/", ""), + Pair( + "../ReactCommon/react/renderer/componentregistry/", + "react/renderer/componentregistry/"), + Pair( + "../ReactCommon/react/renderer/components/root/", + "react/renderer/components/root/"), + Pair("../ReactCommon/react/renderer/core/", "react/renderer/core/"), + Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/"), + Pair( + "../ReactCommon/react/renderer/leakchecker/", + "react/renderer/leakchecker/"), + Pair("../ReactCommon/react/renderer/mapbuffer/", "react/renderer/mapbuffer/"), + Pair("../ReactCommon/react/renderer/mounting/", "react/renderer/mounting/"), + Pair( + "../ReactCommon/react/renderer/runtimescheduler/", + "react/renderer/runtimescheduler/"), + Pair("../ReactCommon/react/renderer/scheduler/", "react/renderer/scheduler/"), + Pair("../ReactCommon/react/renderer/telemetry/", "react/renderer/telemetry/"), + Pair("../ReactCommon/react/renderer/uimanager/", "react/renderer/uimanager/"), + Pair("../ReactCommon/react/debug/", "react/debug/"), + Pair("../ReactCommon/react/utils/", "react/utils/"), + Pair("src/main/jni/react/jni", "react/jni/"), + )), + PrefabPreprocessingEntry( + "react_utils", + Pair("../ReactCommon/react/utils/", "react/utils/"), + ), + PrefabPreprocessingEntry( + "react_render_imagemanager", + listOf( + Pair( + "../ReactCommon/react/renderer/imagemanager/", + "react/renderer/imagemanager/"), + Pair("../ReactCommon/react/renderer/imagemanager/platform/cxx/", ""), + )), + PrefabPreprocessingEntry( + "rrc_image", + Pair( + "../ReactCommon/react/renderer/components/image/", + "react/renderer/components/image/")), + // These prefab targets are used by Expo & Reanimated + PrefabPreprocessingEntry( + "hermes_executor", + // "hermes_executor" is statically linking against "hermes_executor_common" + // and "hermes_inspector_modern". Here we expose only the headers that we know are + // needed. + Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/")), + PrefabPreprocessingEntry( + "jscexecutor", + // "jscexecutor" is statically linking against "jscruntime" + // Here we expose only the headers that we know are needed. + Pair("../ReactCommon/jsc/", "jsc/")), + PrefabPreprocessingEntry( + "react_render_uimanager", + Pair("../ReactCommon/react/renderer/uimanager/", "react/renderer/uimanager/"), + ), + PrefabPreprocessingEntry( + "react_render_scheduler", + Pair("../ReactCommon/react/renderer/scheduler/", "react/renderer/scheduler/"), + ), + PrefabPreprocessingEntry( + "react_render_mounting", + Pair("../ReactCommon/react/renderer/mounting/", "react/renderer/mounting/"), + ), + PrefabPreprocessingEntry( + "reactnativejni", + listOf( + Pair("src/main/jni/react/jni", "react/jni/"), + Pair("../ReactCommon/cxxreact/", "cxxreact/"), + )), + PrefabPreprocessingEntry( + "jsinspector", + Pair("../ReactCommon/jsinspector-modern/", "jsinspector-modern/"), + ), + )) + outputDir.set(prefabHeadersDir) + } + +val createNativeDepsDirectories by + tasks.registering { + downloadsDir.mkdirs() + thirdPartyNdkDir.mkdirs() + } + +val downloadBoost by + tasks.creating(Download::class) { + dependsOn(createNativeDepsDirectories) + src( + "https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION.replace("_", ".")}/source/boost_${BOOST_VERSION}.tar.gz") + onlyIfModified(true) + overwrite(false) + retries(5) + dest(File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz")) + } + +val prepareBoost by + tasks.registering(PrepareBoostTask::class) { + dependsOn(if (boostPathOverride != null) emptyList() else listOf(downloadBoost)) + boostPath.setFrom(if (boostPathOverride != null) boostPath else tarTree(downloadBoost.dest)) + boostVersion.set(BOOST_VERSION) + outputDir.set(File(thirdPartyNdkDir, "boost")) + } + +val downloadDoubleConversion by + tasks.creating(Download::class) { + dependsOn(createNativeDepsDirectories) + src( + "https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") + onlyIfModified(true) + overwrite(false) + retries(5) + dest(File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz")) + } + +val prepareDoubleConversion by + tasks.registering(Copy::class) { + dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadDoubleConversion)) + from(dependenciesPath ?: tarTree(downloadDoubleConversion.dest)) + from("src/main/jni/third-party/double-conversion/") + include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "CMakeLists.txt") + filesMatching("*/src/**/*") { this.path = "double-conversion/${this.name}" } + includeEmptyDirs = false + into("$thirdPartyNdkDir/double-conversion") + } + +val downloadFolly by + tasks.creating(Download::class) { + src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") + onlyIfModified(true) + overwrite(false) + retries(5) + dest(File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz")) + } + +val prepareFolly by + tasks.registering(Copy::class) { + dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadFolly)) + from(dependenciesPath ?: tarTree(downloadFolly.dest)) + from("src/main/jni/third-party/folly/") + include("folly-${FOLLY_VERSION}/folly/**/*", "CMakeLists.txt") + eachFile { this.path = this.path.removePrefix("folly-${FOLLY_VERSION}/") } + includeEmptyDirs = false + into("$thirdPartyNdkDir/folly") + } + +val downloadFmt by + tasks.creating(Download::class) { + dependsOn(createNativeDepsDirectories) + src("https://github.com/fmtlib/fmt/archive/${FMT_VERSION}.tar.gz") + onlyIfModified(true) + overwrite(false) + retries(5) + dest(File(downloadsDir, "fmt-${FMT_VERSION}.tar.gz")) + } + +val prepareFmt by + tasks.registering(Copy::class) { + dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadFmt)) + from(dependenciesPath ?: tarTree(downloadFmt.dest)) + from("src/main/jni/third-party/fmt/") + include("fmt-${FMT_VERSION}/src/**/*", "fmt-${FMT_VERSION}/include/**/*", "CMakeLists.txt") + eachFile { this.path = this.path.removePrefix("fmt-${FMT_VERSION}/") } + includeEmptyDirs = false + into("$thirdPartyNdkDir/fmt") + } + +val downloadGlog by + tasks.creating(Download::class) { + dependsOn(createNativeDepsDirectories) + src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") + onlyIfModified(true) + overwrite(false) + retries(5) + dest(File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz")) + } + +val downloadGtest by + tasks.creating(Download::class) { + dependsOn(createNativeDepsDirectories) + src("https://github.com/google/googletest/archive/refs/tags/release-${GTEST_VERSION}.tar.gz") + onlyIfModified(true) + overwrite(false) + retries(5) + dest(File(downloadsDir, "gtest.tar.gz")) + } + +val prepareGtest by + tasks.registering(Copy::class) { + dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadGtest)) + from(dependenciesPath ?: tarTree(downloadGtest.dest)) + eachFile { this.path = (this.path.removePrefix("googletest-release-${GTEST_VERSION}/")) } + into(File(thirdPartyNdkDir, "googletest")) + } + +// Prepare glog sources to be compiled, this task will perform steps that normally should've been +// executed by automake. This way we can avoid dependencies on make/automake +val prepareGlog by + tasks.registering(PrepareGlogTask::class) { + dependsOn(if (dependenciesPath != null) emptyList() else listOf(downloadGlog)) + glogPath.setFrom(dependenciesPath ?: tarTree(downloadGlog.dest)) + glogVersion.set(GLOG_VERSION) + outputDir.set(File(thirdPartyNdkDir, "glog")) + } + +// Create Android native library module based on jsc from npm +val prepareJSC by + tasks.registering(PrepareJSCTask::class) { + jscPackagePath.set(findNodeModulePath(projectDir, "jsc-android")) + outputDir = project.layout.buildDirectory.dir("third-party-ndk/jsc") + } + +val prepareKotlinBuildScriptModel by + tasks.registering { + // This task is run when Gradle Sync is running. + // We create it here so we can let it depend on preBuild inside the android{} + } + +// As ReactAndroid builds from source, the codegen needs to be built before it can be invoked. +// This is not the case for users of React Native, as we ship a compiled version of the codegen. +val buildCodegenCLI by + tasks.registering(BuildCodegenCLITask::class) { + codegenDir.set(file("$rootDir/node_modules/@react-native/codegen")) + bashWindowsHome.set(project.findProperty("react.internal.windowsBashPath").toString()) + onlyIf { + // For build from source scenario, we don't need to build the codegen at all. + rootProject.name != "react-native-build-from-source" + } + } + +/** + * 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. + */ +fun findNodeModulePath(baseDir: File, packageName: String): String? { + var basePath: java.nio.file.Path? = baseDir.toPath().normalize() + // Node's module resolution algorithm searches up to the root directory, + // after which the base path will be null + while (basePath != null) { + val candidatePath = Paths.get(basePath.toString(), "node_modules", packageName) + if (candidatePath.toFile().exists()) { + return candidatePath.toString() + } + basePath = basePath.parent + } + return null +} + +fun reactNativeDevServerPort(): String { + val value = project.properties["reactNativeDevServerPort"] + return value?.toString() ?: "8081" +} + +fun reactNativeInspectorProxyPort(): String { + val value = project.properties["reactNativeInspectorProxyPort"] + return value?.toString() ?: reactNativeDevServerPort() +} + +fun reactNativeArchitectures(): List { + val value = project.properties["reactNativeArchitectures"] + return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a") +} + +fun enableWarningsAsErrors(): Boolean { + val value = project.properties["enableWarningsAsErrors"] + return value?.toString()?.toBoolean() ?: false +} + +val packageReactNdkLibsForBuck by + tasks.registering(Copy::class) { + dependsOn("mergeDebugNativeLibs") + // Shared libraries (.so) are copied from the merged_native_libs folder instead + from("$buildDir/intermediates/merged_native_libs/debug/out/lib/") + exclude("**/libjsc.so") + exclude("**/libhermes.so") + into("src/main/jni/prebuilt/lib") + } + +val installArchives by tasks.registering { dependsOn("publishAllPublicationsToNpmRepository") } + +repositories { + // Normally RNGP will set repositories for all modules, + // but when consumed from source, we need to re-declare + // those repositories as there is no app module there. + mavenCentral() + google() +} + +android { + compileSdk = libs.versions.compileSdk.get().toInt() + buildToolsVersion = libs.versions.buildTools.get() + namespace = "com.facebook.react" + + // Used to override the NDK path/version on internal CI or by allowing + // users to customize the NDK path/version from their root project (e.g. for Apple Silicon + // support) + if (rootProject.hasProperty("ndkPath") && rootProject.properties["ndkPath"] != null) { + ndkPath = rootProject.properties["ndkPath"].toString() + } + if (rootProject.hasProperty("ndkVersion") && rootProject.properties["ndkVersion"] != null) { + ndkVersion = rootProject.properties["ndkVersion"].toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + // Using '-Xjvm-default=all' to generate default java methods for interfaces + freeCompilerArgs = listOf("-Xjvm-default=all") + // Using -PenableWarningsAsErrors=true prop to enable allWarningsAsErrors + kotlinOptions.allWarningsAsErrors = enableWarningsAsErrors() + } + + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + + consumerProguardFiles("proguard-rules.pro") + + buildConfigField("boolean", "IS_INTERNAL_BUILD", "false") + buildConfigField("int", "EXOPACKAGE_FLAGS", "0") + + resValue("integer", "react_native_dev_server_port", reactNativeDevServerPort()) + resValue("integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()) + + testApplicationId = "com.facebook.react.tests.gradle" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + externalNativeBuild { + cmake { + arguments( + "-DREACT_COMMON_DIR=${reactNativeRootDir}/ReactCommon", + "-DREACT_ANDROID_DIR=$projectDir", + "-DREACT_BUILD_DIR=$buildDir", + "-DANDROID_STL=c++_shared", + "-DANDROID_TOOLCHAIN=clang", + // Due to https://github.com/android/ndk/issues/1693 we're losing Android + // specific compilation flags. This can be removed once we moved to NDK 25/26 + "-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON") + + targets( + "jsijniprofiler", + "reactnativeblob", + "reactperfloggerjni", + "bridgeless", + "rninstance", + "hermesinstancejni", + "uimanagerjni", + "jscinstance", + // prefab targets + "reactnativejni", + "react_render_debug", + "turbomodulejsijni", + "runtimeexecutor", + "react_codegen_rncore", + "react_debug", + "react_utils", + "react_render_componentregistry", + "react_newarchdefaults", + "react_cxxreactpackage", + "react_render_animations", + "react_render_core", + "react_render_graphics", + "rrc_image", + "rrc_root", + "rrc_view", + "rrc_legacyviewmanagerinterop", + "jsi", + "glog", + "fabricjni", + "react_render_mapbuffer", + "yoga", + "folly_runtime", + "react_nativemodule_core", + "react_render_imagemanager", + "react_render_uimanager", + "react_render_scheduler", + "react_render_mounting", + "hermes_executor", + "jscexecutor", + "jsinspector") + } + } + ndk { abiFilters.addAll(reactNativeArchitectures()) } + } + + externalNativeBuild { + cmake { + version = cmakeVersion + path("src/main/jni/CMakeLists.txt") + } + } + + buildTypes { + debug { + externalNativeBuild { + cmake { + // We want to build Gtest suite only for the debug variant. + targets("reactnative_unittest") + } + } + } + } + + tasks + .getByName("preBuild") + .dependsOn( + buildCodegenCLI, + "generateCodegenArtifactsFromSchema", + prepareBoost, + prepareDoubleConversion, + prepareFmt, + prepareFolly, + prepareGlog, + prepareGtest, + prepareJSC, + preparePrefab) + tasks.getByName("generateCodegenSchemaFromJavaScript").dependsOn(buildCodegenCLI) + prepareKotlinBuildScriptModel.dependsOn("preBuild") + prepareKotlinBuildScriptModel.dependsOn( + ":packages:react-native:ReactAndroid:hermes-engine:preBuild") + + sourceSets.getByName("main") { + res.srcDirs( + listOf( + "src/main/res/devsupport", + "src/main/res/shell", + "src/main/res/views/modal", + "src/main/res/views/uimanager")) + java.srcDirs( + listOf( + "src/main/java", + "src/main/libraries/soloader/java", + "src/main/jni/first-party/fb/jni/java")) + java.exclude("com/facebook/annotationprocessors") + java.exclude("com/facebook/react/processing") + java.exclude("com/facebook/react/module/processing") + } + + lintOptions { isAbortOnError = false } + + packagingOptions { + exclude("META-INF/NOTICE") + exclude("META-INF/LICENSE") + // We intentionally don't want to bundle any JS Runtime inside the Android AAR + // we produce. The reason behind this is that we want to allow users to pick the + // JS engine by specifying a dependency on either `hermes-engine` or `android-jsc` + // that will include the necessary .so files to load. + exclude("**/libhermes.so") + exclude("**/libjsc.so") + } + + buildFeatures { + prefab = true + prefabPublishing = !skipPrefabPublishing + buildConfig = true + } + + prefab { + create("react_render_debug") { + headers = File(prefabHeadersDir, "react_render_debug").absolutePath + } + create("turbomodulejsijni") { + headers = File(prefabHeadersDir, "turbomodulejsijni").absolutePath + } + create("runtimeexecutor") { headers = File(prefabHeadersDir, "runtimeexecutor").absolutePath } + create("react_codegen_rncore") { + headers = File(prefabHeadersDir, "react_codegen_rncore").absolutePath + } + create("react_debug") { headers = File(prefabHeadersDir, "react_debug").absolutePath } + create("react_utils") { headers = File(prefabHeadersDir, "react_utils").absolutePath } + create("react_render_componentregistry") { + headers = File(prefabHeadersDir, "react_render_componentregistry").absolutePath + } + create("react_newarchdefaults") { + headers = File(prefabHeadersDir, "react_newarchdefaults").absolutePath + } + create("react_cxxreactpackage") { + headers = File(prefabHeadersDir, "react_cxxreactpackage").absolutePath + } + create("react_render_animations") { + headers = File(prefabHeadersDir, "react_render_animations").absolutePath + } + create("react_render_core") { + headers = File(prefabHeadersDir, "react_render_core").absolutePath + } + create("react_render_graphics") { + headers = File(prefabHeadersDir, "react_render_graphics").absolutePath + } + create("rrc_image") { headers = File(prefabHeadersDir, "rrc_image").absolutePath } + create("rrc_root") { headers = File(prefabHeadersDir, "rrc_root").absolutePath } + create("rrc_view") { headers = File(prefabHeadersDir, "rrc_view").absolutePath } + create("rrc_legacyviewmanagerinterop") { + headers = File(prefabHeadersDir, "rrc_legacyviewmanagerinterop").absolutePath + } + create("jsi") { headers = File(prefabHeadersDir, "jsi").absolutePath } + create("glog") { headers = File(prefabHeadersDir, "glog").absolutePath } + create("fabricjni") { headers = File(prefabHeadersDir, "fabricjni").absolutePath } + create("react_render_mapbuffer") { + headers = File(prefabHeadersDir, "react_render_mapbuffer").absolutePath + } + create("yoga") { headers = File(prefabHeadersDir, "yoga").absolutePath } + create("folly_runtime") { headers = File(prefabHeadersDir, "folly_runtime").absolutePath } + create("react_nativemodule_core") { + headers = File(prefabHeadersDir, "react_nativemodule_core").absolutePath + } + create("react_render_imagemanager") { + headers = File(prefabHeadersDir, "react_render_imagemanager").absolutePath + } + create("react_render_uimanager") { + headers = File(prefabHeadersDir, "react_render_uimanager").absolutePath + } + create("react_render_scheduler") { + headers = File(prefabHeadersDir, "react_render_scheduler").absolutePath + } + create("react_render_mounting") { + headers = File(prefabHeadersDir, "react_render_mounting").absolutePath + } + create("reactnativejni") { headers = File(prefabHeadersDir, "reactnativejni").absolutePath } + create("hermes_executor") { headers = File(prefabHeadersDir, "hermes_executor").absolutePath } + create("jscexecutor") { headers = File(prefabHeadersDir, "jscexecutor").absolutePath } + create("jsinspector") { headers = File(prefabHeadersDir, "jsinspector").absolutePath } + } + + publishing { + multipleVariants { + withSourcesJar() + includeBuildTypeValues("debug", "release") + } + } + + testOptions { unitTests { isIncludeAndroidResources = true } } +} + +dependencies { + api(libs.androidx.appcompat) + api(libs.androidx.appcompat.resources) + api(libs.androidx.autofill) + api(libs.androidx.swiperefreshlayout) + api(libs.androidx.tracing) + + api(libs.fbjni) + api(libs.fresco) + api(libs.fresco.middleware) + api(libs.fresco.imagepipeline.okhttp3) + api(libs.fresco.ui.common) + api(libs.infer.annotation) + api(libs.soloader) + api(libs.yoga.proguard.annotations) + + api(libs.jsr305) + api(libs.okhttp3.urlconnection) + api(libs.okhttp3) + api(libs.okio) + compileOnly(libs.javax.annotation.api) + api(libs.javax.inject) + + // It's up to the consumer to decide if hermes should be included or not. + // Therefore hermes-engine is a compileOnly dependency. + compileOnly(project(":packages:react-native:ReactAndroid:hermes-engine")) + + testImplementation(libs.junit) + testImplementation(libs.assertj) + testImplementation(libs.mockito) + testImplementation(libs.robolectric) + testImplementation(libs.thoughtworks) + + androidTestImplementation(libs.androidx.test.runner) + androidTestImplementation(libs.androidx.test.rules) + androidTestImplementation(libs.mockito) +} + +react { + // TODO: The library name is chosen for parity with Fabric components & iOS + // This should be changed to a more generic name, e.g. `ReactCoreSpec`. + libraryName = "rncore" + jsRootDir = file("../Libraries") +} + +// For build from source, we need to override the privateReact extension. +// This is needed as the build-from-source won't have a com.android.application +// module to apply the plugin to, so it's codegenDir and reactNativeDir won't be evaluated. +if (rootProject.name == "react-native-build-from-source") { + rootProject.extensions.getByType(PrivateReactExtension::class.java).apply { + codegenDir = file("$rootDir/../@react-native/codegen") + reactNativeDir = file("$rootDir") + } +} + +kotlin { + jvmToolchain(17) + explicitApi() +} + +/* Publishing Configuration */ +apply(from = "./publish.gradle") + +// We need to override the artifact ID as this project is called `ReactAndroid` but +// the maven coordinates are on `react-android`. +// Please note that the original coordinates, `react-native`, have been voided +// as they caused https://github.com/facebook/react-native/issues/35210 +publishing { + publications { getByName("release", MavenPublication::class) { artifactId = "react-android" } } +} diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle deleted file mode 100644 index 7108269683cfd1..00000000000000 --- a/packages/rn-tester/android/app/build.gradle +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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. - */ - -import groovy.json.JsonSlurper - -plugins { - id("com.facebook.react") - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) -} - -def reactNativeVersionRequireNewArchEnabled(reactNativeDirPath) { - def reactNativePackageJson = "$reactNativeDirPath/package.json" - def slurper = new JsonSlurper() - def jsonData = slurper.parse(new File(reactNativePackageJson)) - def rnVersion = jsonData.version - def regexPattern = /^(\d+)\.(\d+)\.(\d+)(?:-(\w+(?:[-.]\d+)?))?$/ - - - if (rnVersion =~ regexPattern) { - def result = (rnVersion =~ regexPattern).findAll().first() - - def major = result[1].toInteger() - if (major > 0 && major < 1000) { - return true - } - } - return false -} - -def reactNativeDirPath = "$rootDir/packages/react-native" -def isNewArchEnabled = project.property("newArchEnabled") == "true" || reactNativeVersionRequireNewArchEnabled(reactNativeDirPath) -/** - * This is the configuration block to customize your React Native Android app. - * By default you don't need to apply any configuration, just uncomment the lines you need. - */ -react { - /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '..' - root = file("../../") - // The folder where the react-native NPM package is. Default is ../node_modules/react-native - reactNativeDir = file(reactNativeDirPath) - // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen - codegenDir = file("$rootDir/node_modules/@react-native/codegen") - // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js - cliFile = file("$reactNativeDirPath/cli.js") - - /* Variants */ - // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. - // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - debuggableVariants = ["hermesDebug", "jscDebug"] - - /* Bundling */ - // A list containing the node command and its flags. Default is just 'node'. - // nodeExecutableAndArgs = ["node"] - // - // The command to run when bundling. By default is 'bundle' - // bundleCommand = "ram-bundle" - // - // The path to the CLI configuration file. Default is empty. - // bundleConfig = file(../rn-cli.config.js) - // - // The name of the generated asset file containing your JS bundle - bundleAssetName = "RNTesterApp.android.bundle" - // - // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' - entryFile = file("../../js/RNTesterApp.android.js") - // - // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle - // extraPackagerArgs = [] - - /* Hermes Commands */ - // The hermes compiler command to run. By default it is 'hermesc' - hermesCommand = "$reactNativeDirPath/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" - enableHermesOnlyInVariants = ["hermesDebug", "hermesRelease"] -} - -/** - * Run Proguard to shrink the Java bytecode in release builds. - */ -def enableProguardInReleaseBuilds = true - -/** - * The preferred build flavor of JavaScriptCore (JSC) - * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` - */ -def jscFlavor = 'org.webkit:android-jsc:+' - -/** - * This allows to customized the CMake version used for compiling RN Tester. - */ -def cmakeVersion = project(":packages:react-native:ReactAndroid").cmake_version - -/** - * Architectures to build native code for. - */ -def reactNativeArchitectures() { - def value = project.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} - -repositories { - maven { - url rootProject.file("node_modules/jsc-android/dist") - } -} - -android { - compileSdk libs.versions.compileSdk.get().toInteger() - buildToolsVersion = libs.versions.buildTools.get() - namespace "com.facebook.react.uiapp" - - // Used to override the NDK path/version on internal CI or by allowing - // users to customize the NDK path/version from their root project (e.g. for Apple Silicon support) - if (rootProject.hasProperty("ndkPath")) { - ndkPath rootProject.ext.ndkPath - } - if (rootProject.hasProperty("ndkVersion")) { - ndkVersion rootProject.ext.ndkVersion - } - - flavorDimensions "vm" - productFlavors { - hermes { - dimension "vm" - buildConfigField("boolean", "IS_HERMES_ENABLED_IN_FLAVOR", "true") - } - jsc { - dimension "vm" - buildConfigField("boolean", "IS_HERMES_ENABLED_IN_FLAVOR", "false") - } - } - - defaultConfig { - applicationId "com.facebook.react.uiapp" - minSdk = libs.versions.minSdk.get().toInteger() - targetSdk = libs.versions.targetSdk.get().toInteger() - versionCode 1 - versionName "1.0" - testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - buildConfigField("String", "JS_MAIN_MODULE_NAME", "\"js/RNTesterApp.android\"") - buildConfigField("String", "BUNDLE_ASSET_NAME", "\"RNTesterApp.android.bundle\"") - } - externalNativeBuild { - cmake { - version cmakeVersion - } - } - splits { - abi { - enable true - universalApk false - reset() - include (*reactNativeArchitectures()) - } - } - buildTypes { - release { - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile('proguard-android.txt') - signingConfig signingConfigs.debug - } - } - sourceSets.main { - java { - // SampleTurboModule. - srcDirs += [ - "$reactNativeDirPath/ReactCommon/react/nativemodule/samples/platform/android", - ] - } - } -} - -dependencies { - // Build React Native from source - implementation project(':packages:react-native:ReactAndroid') - - // Consume Hermes as built from source only for the Hermes variant. - hermesImplementation(project(":packages:react-native:ReactAndroid:hermes-engine")) - - jscImplementation jscFlavor - - testImplementation(libs.junit) -} - -android { - externalNativeBuild { - cmake { - // RN Tester is doing custom linking of C++ libraries therefore needs - // a dedicated CMakeLists.txt file. - if (isNewArchEnabled) { - path("src/main/jni/CMakeLists.txt") - } - } - } -} - -afterEvaluate { - if (project.findProperty("react.internal.useHermesNightly") == null || project.findProperty("react.internal.useHermesNightly").toString() == "false") { - // As we're consuming Hermes from source, we want to make sure - // `hermesc` is built before we actually invoke the `emit*HermesResource` task - createBundleHermesReleaseJsAndAssets.dependsOn(":packages:react-native:ReactAndroid:hermes-engine:buildHermesC") - } - - // As we're building 4 native flavors in parallel, there is clash on the `.cxx/Debug` and - // `.cxx/Release` folder where the CMake intermediates are stored. - // We fixing this by instructing Gradle to always mergeLibs after they've been built. - if (isNewArchEnabled) { - mergeHermesDebugNativeLibs.mustRunAfter(externalNativeBuildJscDebug) - mergeHermesReleaseNativeLibs.mustRunAfter(externalNativeBuildJscRelease) - mergeJscDebugNativeLibs.mustRunAfter(externalNativeBuildHermesDebug) - mergeJscReleaseNativeLibs.mustRunAfter(externalNativeBuildHermesRelease) - } - - // As RN-Tester consumes the codegen from source, we need to make sure the codegen exists before - // we can actually invoke it. It's built by the ReactAndroid:buildCodegenCLI task. - generateCodegenSchemaFromJavaScript.dependsOn(":packages:react-native:ReactAndroid:buildCodegenCLI") -} diff --git a/packages/rn-tester/android/app/build.gradle.kts b/packages/rn-tester/android/app/build.gradle.kts new file mode 100644 index 00000000000000..e9c7440be5e54c --- /dev/null +++ b/packages/rn-tester/android/app/build.gradle.kts @@ -0,0 +1,199 @@ +/* + * 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. + */ + +plugins { + id("com.facebook.react") + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +val reactNativeDirPath = "$rootDir/packages/react-native" +val isNewArchEnabled = project.property("newArchEnabled") == "true" + +/** + * This is the configuration block to customize your React Native Android app. By default you don't + * need to apply any configuration, just uncomment the lines you need. + */ +react { + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '..' + root = file("../../") + // The folder where the react-native NPM package is. Default is ../node_modules/react-native + reactNativeDir = file(reactNativeDirPath) + // The folder where the react-native Codegen package is. Default is + // ../node_modules/@react-native/codegen + codegenDir = file("$rootDir/node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is + // ../node_modules/react-native/cli.js + cliFile = file("$reactNativeDirPath/cli.js") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + debuggableVariants = listOf("hermesDebug", "jscDebug") + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + // + // The command to run when bundling. By default is 'bundle' + // bundleCommand = "ram-bundle" + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + bundleAssetName = "RNTesterApp.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + entryFile = file("../../js/RNTesterApp.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + hermesCommand = "$reactNativeDirPath/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" + enableHermesOnlyInVariants = listOf("hermesDebug", "hermesRelease") +} + +/** Run Proguard to shrink the Java bytecode in release builds. */ +val enableProguardInReleaseBuilds = true + +/** + * The preferred build flavor of JavaScriptCore (JSC) For example, to use the international variant, + * you can use: `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + */ +val jscFlavor = "org.webkit:android-jsc:+" + +/** This allows to customized the CMake version used for compiling RN Tester. */ +val cmakeVersion = + project(":packages:react-native:ReactAndroid").properties["cmake_version"].toString() + +/** Architectures to build native code for. */ +fun reactNativeArchitectures(): List { + val value = project.properties["reactNativeArchitectures"] + return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a") +} + +repositories { maven { url = rootProject.file("node_modules/jsc-android/dist").toURI() } } + +android { + compileSdk = libs.versions.compileSdk.get().toInt() + buildToolsVersion = libs.versions.buildTools.get() + namespace = "com.facebook.react.uiapp" + + // Used to override the NDK path/version on internal CI or by allowing + // users to customize the NDK path/version from their root project (e.g. for Apple Silicon + // support) + if (rootProject.hasProperty("ndkPath") && rootProject.properties["ndkPath"] != null) { + ndkPath = rootProject.properties["ndkPath"].toString() + } + if (rootProject.hasProperty("ndkVersion") && rootProject.properties["ndkVersion"] != null) { + ndkVersion = rootProject.properties["ndkVersion"].toString() + } + + flavorDimensions("vm") + productFlavors { + create("hermes") { + dimension = "vm" + buildConfigField("boolean", "IS_HERMES_ENABLED_IN_FLAVOR", "true") + } + create("jsc") { + dimension = "vm" + buildConfigField("boolean", "IS_HERMES_ENABLED_IN_FLAVOR", "false") + } + } + + defaultConfig { + applicationId = "com.facebook.react.uiapp" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + testBuildType = + System.getProperty( + "testBuildType", "debug") // This will later be used to control the test apk build type + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "JS_MAIN_MODULE_NAME", "\"js/RNTesterApp.android\"") + buildConfigField("String", "BUNDLE_ASSET_NAME", "\"RNTesterApp.android.bundle\"") + } + externalNativeBuild { cmake { version = cmakeVersion } } + splits { + abi { + isEnable = true + isUniversalApk = false + reset() + include(*reactNativeArchitectures().toTypedArray()) + } + } + buildTypes { + release { + isMinifyEnabled = enableProguardInReleaseBuilds + proguardFiles(getDefaultProguardFile("proguard-android.txt")) + signingConfig = signingConfigs.getByName("debug") + } + } + sourceSets.named("main") { + // SampleTurboModule. + java.srcDirs( + "$reactNativeDirPath/ReactCommon/react/nativemodule/samples/platform/android", + ) + } +} + +dependencies { + // Build React Native from source + implementation(project(":packages:react-native:ReactAndroid")) + + // Consume Hermes as built from source only for the Hermes variant. + "hermesImplementation"(project(":packages:react-native:ReactAndroid:hermes-engine")) + "jscImplementation"(jscFlavor) + + testImplementation(libs.junit) +} + +android { + externalNativeBuild { + cmake { + // RN Tester is doing custom linking of C++ libraries therefore needs + // a dedicated CMakeLists.txt file. + if (isNewArchEnabled) { + path("src/main/jni/CMakeLists.txt") + } + } + } +} + +afterEvaluate { + if (project.findProperty("react.internal.useHermesNightly") == null || + project.findProperty("react.internal.useHermesNightly").toString() == "false") { + // As we're consuming Hermes from source, we want to make sure + // `hermesc` is built before we actually invoke the `emit*HermesResource` task + tasks + .getByName("createBundleHermesReleaseJsAndAssets") + .dependsOn(":packages:react-native:ReactAndroid:hermes-engine:buildHermesC") + } + + // As we're building 4 native flavors in parallel, there is clash on the `.cxx/Debug` and + // `.cxx/Release` folder where the CMake intermediates are stored. + // We fixing this by instructing Gradle to always mergeLibs after they've been built. + if (isNewArchEnabled) { + tasks.getByName("mergeHermesDebugNativeLibs").mustRunAfter("externalNativeBuildJscDebug") + tasks.getByName("mergeHermesReleaseNativeLibs").mustRunAfter("externalNativeBuildJscRelease") + tasks.getByName("mergeJscDebugNativeLibs").mustRunAfter("externalNativeBuildHermesDebug") + tasks.getByName("mergeJscReleaseNativeLibs").mustRunAfter("externalNativeBuildHermesRelease") + } + + // As RN-Tester consumes the codegen from source, we need to make sure the codegen exists before + // we can actually invoke it. It's built by the ReactAndroid:buildCodegenCLI task. + tasks + .getByName("generateCodegenSchemaFromJavaScript") + .dependsOn(":packages:react-native:ReactAndroid:buildCodegenCLI") +} From 29f8201da461843649d48cefda254017822fc577 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 12 Dec 2023 10:22:32 -0800 Subject: [PATCH 2/4] Remove REACT_NATIVE_SKIP_PREFAB as unused (#41833) Summary: I'm removing those environment variables as they're legacy and not used anymore: - REACT_NATIVE_SKIP_PREFAB - REACT_NATIVE_HERMES_SKIP_PREFAB Changelog: [Internal] [Changed] - Remove REACT_NATIVE_SKIP_PREFAB as unused Reviewed By: dmytrorykun Differential Revision: D51890227 --- packages/react-native/ReactAndroid/build.gradle.kts | 5 +---- .../react-native/ReactAndroid/hermes-engine/build.gradle | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index bb035d25c75899..e41507c1c795fb 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -60,9 +60,6 @@ val dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") // and the build will use that. val boostPathOverride = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH") -// By setting REACT_NATIVE_SKIP_PREFAB we can skip prefab publishing, to -// reduce the size of the React Native published .AAR. -val skipPrefabPublishing = System.getenv("REACT_NATIVE_SKIP_PREFAB") != null val prefabHeadersDir = project.file("$buildDir/prefab-headers") // Native versions which are defined inside the version catalog (libs.versions.toml) @@ -635,7 +632,7 @@ android { buildFeatures { prefab = true - prefabPublishing = !skipPrefabPublishing + prefabPublishing = true buildConfig = true } diff --git a/packages/react-native/ReactAndroid/hermes-engine/build.gradle b/packages/react-native/ReactAndroid/hermes-engine/build.gradle index 25916bf296351b..f008a5dd8abfca 100644 --- a/packages/react-native/ReactAndroid/hermes-engine/build.gradle +++ b/packages/react-native/ReactAndroid/hermes-engine/build.gradle @@ -63,10 +63,6 @@ if (hermesVersionFile.exists()) { def ndkBuildJobs = Runtime.runtime.availableProcessors().toString() def prefabHeadersDir = new File("$buildDir/prefab-headers") -// By setting REACT_NATIVE_HERMES_SKIP_PREFAB you can skip prefab publishing, to -// reduce the size of the Hermes published .AAR. -def skipPrefabPublishing = System.getenv("REACT_NATIVE_HERMES_SKIP_PREFAB") != null - // We inject the JSI directory used inside the Hermes build with the -DJSI_DIR config. def jsiDir = new File(reactNativeRootDir, "ReactCommon/jsi") @@ -245,7 +241,7 @@ android { buildFeatures { prefab true - prefabPublishing !skipPrefabPublishing + prefabPublishing true } dependencies { From 1e9d1e09e0110849e6527a7e3a77061dcc8a028b Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 12 Dec 2023 10:22:32 -0800 Subject: [PATCH 3/4] Remove the installArchives task (#41832) Summary: I'm removing the `installArchives` task and all the setup to publish the Maven Local inside the NPM package as we're not using this entirely and we won't be able to use it anyway (as the Maven Local is too big to fit an NPM package). Changelog: [Internal] [Changed] - Remove the installArchives task Reviewed By: GijsWeterings Differential Revision: D51890224 --- build.gradle.kts | 9 -------- .../ReactAndroid/DevExperience.md | 23 ------------------- .../ReactAndroid/build.gradle.kts | 2 -- .../ReactAndroid/hermes-engine/build.gradle | 4 ---- .../react-native/ReactAndroid/publish.gradle | 5 ---- scripts/run-ci-e2e-tests.js | 2 +- 6 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/DevExperience.md diff --git a/build.gradle.kts b/build.gradle.kts index 3baf29f0595588..de043f8d8bb7d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,15 +90,6 @@ tasks.register("build") { dependsOn(gradle.includedBuild("react-native-gradle-plugin").task(":build")) } -tasks.register("publishAllInsideNpmPackage") { - description = - "Publish all the artifacts to be available inside the NPM package in the `android` folder." - // Due to size constraints of NPM, we publish only react-native and hermes-engine inside - // the NPM package. - dependsOn(":packages:react-native:ReactAndroid:installArchives") - dependsOn(":packages:react-native:ReactAndroid:hermes-engine:installArchives") -} - tasks.register("publishAllToMavenTempLocal") { description = "Publish all the artifacts to be available inside a Maven Local repository on /tmp." dependsOn(":packages:react-native:ReactAndroid:publishAllPublicationsToMavenTempLocalRepository") diff --git a/packages/react-native/ReactAndroid/DevExperience.md b/packages/react-native/ReactAndroid/DevExperience.md deleted file mode 100644 index c0d259daae20ca..00000000000000 --- a/packages/react-native/ReactAndroid/DevExperience.md +++ /dev/null @@ -1,23 +0,0 @@ -Here's how to test the whole dev experience end-to-end. This will be eventually merged into the [Getting Started guide](https://reactnative.dev/docs/getting-started). - -Assuming you have the [Android SDK](https://developer.android.com/sdk/installing/index.html) installed, run `android` to open the Android SDK Manager. - -Make sure you have the following installed: - -- Android SDK version 23 -- SDK build tools version 23 -- Android Support Repository 17 (for Android Support Library) - -Follow steps on https://github.com/react-native-community/cli/blob/master/CONTRIBUTING.md, but be sure to bump the version of react-native in package.json to some version > 0.9 (latest published npm version) or set up proxying properly for react-native - -- From the react-native-android repo: - - `./gradlew :ReactAndroid:installArchives` - - *Assuming you already have android-jsc installed to local maven repo, no steps included here* -- `react-native init ProjectName` -- Open up your Android emulator (Genymotion is recommended) -- `cd ProjectName` -- `react-native run-android` - -In case the app crashed: - -- Run `adb logcat` and try to find a Java exception diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index e41507c1c795fb..ee942e0d132c18 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -454,8 +454,6 @@ val packageReactNdkLibsForBuck by into("src/main/jni/prebuilt/lib") } -val installArchives by tasks.registering { dependsOn("publishAllPublicationsToNpmRepository") } - repositories { // Normally RNGP will set repositories for all modules, // but when consumed from source, we need to re-declare diff --git a/packages/react-native/ReactAndroid/hermes-engine/build.gradle b/packages/react-native/ReactAndroid/hermes-engine/build.gradle index f008a5dd8abfca..b18dc847196a2b 100644 --- a/packages/react-native/ReactAndroid/hermes-engine/build.gradle +++ b/packages/react-native/ReactAndroid/hermes-engine/build.gradle @@ -75,10 +75,6 @@ task downloadHermes(type: Download) { dest(new File(downloadsDir, "hermes.tar.gz")) } -task installArchives { - dependsOn("publishAllPublicationsToNpmRepository") -} - task unzipHermes(dependsOn: downloadHermes, type: Copy) { from(tarTree(downloadHermes.dest)) { eachFile { file -> diff --git a/packages/react-native/ReactAndroid/publish.gradle b/packages/react-native/ReactAndroid/publish.gradle index b249f48ffdcd0b..32287a7c81392c 100644 --- a/packages/react-native/ReactAndroid/publish.gradle +++ b/packages/react-native/ReactAndroid/publish.gradle @@ -13,7 +13,6 @@ def signingKey = findProperty("SIGNING_KEY") def signingPwd = findProperty("SIGNING_PWD") def reactAndroidProjectDir = project(':packages:react-native:ReactAndroid').projectDir -def androidOutputUrl = "file://${reactAndroidProjectDir}/../android" def mavenTempLocalUrl = "file:///tmp/maven-local" publishing { @@ -65,10 +64,6 @@ publishing { } repositories { - maven { - name = "npm" - url = androidOutputUrl - } maven { name = "mavenTempLocal" url = mavenTempLocalUrl diff --git a/scripts/run-ci-e2e-tests.js b/scripts/run-ci-e2e-tests.js index 7c28b4c6f3c198..d8c7e0a07c93cc 100644 --- a/scripts/run-ci-e2e-tests.js +++ b/scripts/run-ci-e2e-tests.js @@ -53,7 +53,7 @@ try { describe('Compile Android binaries'); if ( exec( - './gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"', + './gradlew publishAllToMavenTempLocal -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"', ).code ) { echo('Failed to compile Android binaries'); From 96afb454a911ce2eda7ce99fa085c7ca8cf9b54b Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 12 Dec 2023 10:22:32 -0800 Subject: [PATCH 4/4] Resolve several Gradle build warning (#41831) Summary: The build is currently firing several build warnings due to the Groovy -> Kotlin migration. I've fixed them all over here. Changelog: [Internal] [Changed] - Resolve several Gradle build warning Reviewed By: mdvacca Differential Revision: D51890225 --- .../ReactAndroid/build.gradle.kts | 29 +++++++++++-------- .../rn-tester/android/app/build.gradle.kts | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index ee942e0d132c18..5c089aa115c749 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -27,7 +27,7 @@ group = "com.facebook.react" // We download various C++ open-source dependencies into downloads. // We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk. // After that we build native code from src/main/jni with module path pointing at third-party-ndk. - +val buildDir = project.layout.buildDirectory.get().asFile val downloadsDir = if (System.getenv("REACT_NATIVE_DOWNLOADS_DIR") != null) { File(System.getenv("REACT_NATIVE_DOWNLOADS_DIR")) @@ -400,13 +400,13 @@ val buildCodegenCLI by * 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] + * /path/to/react-native/node_modules/ * - 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] + * /path/to/app/node_modules/ * - 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] + * /path/to/repo/node_modules/ * * The search begins at the given base directory (a File object). The returned path is a string. */ @@ -491,7 +491,6 @@ android { defaultConfig { minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.targetSdk.get().toInt() consumerProguardFiles("proguard-rules.pro") @@ -615,17 +614,20 @@ android { java.exclude("com/facebook/react/module/processing") } - lintOptions { isAbortOnError = false } + lint { + abortOnError = false + targetSdk = libs.versions.targetSdk.get().toInt() + } - packagingOptions { - exclude("META-INF/NOTICE") - exclude("META-INF/LICENSE") + packaging { + resources.excludes.add("META-INF/NOTICE") + resources.excludes.add("META-INF/LICENSE") // We intentionally don't want to bundle any JS Runtime inside the Android AAR // we produce. The reason behind this is that we want to allow users to pick the // JS engine by specifying a dependency on either `hermes-engine` or `android-jsc` // that will include the necessary .so files to load. - exclude("**/libhermes.so") - exclude("**/libjsc.so") + jniLibs.excludes.add("**/libhermes.so") + jniLibs.excludes.add("**/libjsc.so") } buildFeatures { @@ -707,7 +709,10 @@ android { } } - testOptions { unitTests { isIncludeAndroidResources = true } } + testOptions { + unitTests { isIncludeAndroidResources = true } + targetSdk = libs.versions.targetSdk.get().toInt() + } } dependencies { diff --git a/packages/rn-tester/android/app/build.gradle.kts b/packages/rn-tester/android/app/build.gradle.kts index e9c7440be5e54c..5ad05a044325fd 100644 --- a/packages/rn-tester/android/app/build.gradle.kts +++ b/packages/rn-tester/android/app/build.gradle.kts @@ -99,7 +99,7 @@ android { ndkVersion = rootProject.properties["ndkVersion"].toString() } - flavorDimensions("vm") + flavorDimensions.add("vm") productFlavors { create("hermes") { dimension = "vm"