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 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..5c089aa115c749 --- /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 buildDir = project.layout.buildDirectory.get().asFile +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") + +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/ + * - 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/ + * - 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/ + * + * 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") + } + +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() + + 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") + } + + lint { + abortOnError = false + targetSdk = libs.versions.targetSdk.get().toInt() + } + + 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. + jniLibs.excludes.add("**/libhermes.so") + jniLibs.excludes.add("**/libjsc.so") + } + + buildFeatures { + prefab = true + prefabPublishing = true + 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 } + targetSdk = libs.versions.targetSdk.get().toInt() + } +} + +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/react-native/ReactAndroid/hermes-engine/build.gradle b/packages/react-native/ReactAndroid/hermes-engine/build.gradle index 25916bf296351b..b18dc847196a2b 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") @@ -79,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 -> @@ -245,7 +237,7 @@ android { buildFeatures { prefab true - prefabPublishing !skipPrefabPublishing + prefabPublishing true } dependencies { 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/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..5ad05a044325fd --- /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.add("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") +} 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');