mirror of
https://github.com/facebook/react-native.git
synced 2024-11-21 21:27:46 +00:00
RNGP - Autolinking. Add support for linking projects. (#44799)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44799 This is the final part of core autolinking: 1. I split RNGP into an `app-plugin` and a `settings-plugin`. This was necessary as the Gradle modules need to be loaded inside the settings.gradle.kts. 2. I've introduced a Settings Plugin to take care of either invoking the `config` command from CLI or receiving a file in input. 3. I've removed the former `RunAutolinkingConfigTask` as now the command is invoked inside the settings plugin 4. I've added hashing computed based on the lockfiles so we won't be re-executing teh `config` command if the lockfiles are not changed. 5. I've updated RN-Tester to use the core autolinking rather than manual linking for the 2 libraries it's using. Changelog:linking [Internal] [Changed] - RNGP - Autolinking. Add support for linking projects Reviewed By: blakef Differential Revision: D58190363 fbshipit-source-id: 6ab8b36729e77ca715f50a4a00aa0ca4eb5b63b1
This commit is contained in:
parent
d8a0d30e70
commit
cf914e412d
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,7 +24,6 @@ project.xcworkspace
|
||||
|
||||
# Gradle
|
||||
/build/
|
||||
/packages/react-native-gradle-plugin/build/
|
||||
/packages/rn-tester/build
|
||||
/packages/rn-tester/android/app/.cxx/
|
||||
/packages/rn-tester/android/app/build/
|
||||
|
@ -56,6 +56,9 @@ react {
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
|
||||
/* Autolinking */
|
||||
autolinkLibrariesWithApp()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,6 +124,3 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This needs to use the new autolinking code in the gradle-plugin instead
|
||||
// apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
|
@ -8,7 +8,7 @@
|
||||
package com.helloworld
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.PackageList2
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.ReactNativeHost
|
||||
@ -23,7 +23,7 @@ class MainApplication : Application(), ReactApplication {
|
||||
override val reactNativeHost: ReactNativeHost =
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
PackageList2(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
|
@ -5,7 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// Autolinking has now moved into the React Native Gradle Plugin
|
||||
import com.facebook.react.ReactSettingsExtension
|
||||
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
|
||||
plugins { id("com.facebook.react.settings") }
|
||||
extensions.configure(ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
|
||||
|
||||
rootProject.name = 'HelloWorld'
|
||||
include ':app'
|
||||
// Autolinking has now moved into the React Native Gradle Plugin
|
||||
includeBuild('../../react-native-gradle-plugin')
|
||||
|
4
packages/react-native-gradle-plugin/.gitignore
vendored
Normal file
4
packages/react-native-gradle-plugin/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
build/
|
||||
app-plugin/build/
|
||||
settings-plugin/build/
|
||||
shared/build/
|
@ -5,80 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import org.gradle.api.internal.classpath.ModuleRegistry
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.gradle.configurationcache.extensions.serviceOf
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.jvm).apply(false)
|
||||
id("java-gradle-plugin")
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("react") {
|
||||
id = "com.facebook.react"
|
||||
implementationClass = "com.facebook.react.ReactPlugin"
|
||||
}
|
||||
create("reactrootproject") {
|
||||
id = "com.facebook.react.rootproject"
|
||||
implementationClass = "com.facebook.react.ReactRootProjectPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group = "com.facebook.react"
|
||||
|
||||
dependencies {
|
||||
implementation(gradleApi())
|
||||
|
||||
// The KGP/AGP version is defined by React Native Gradle plugin.
|
||||
// Therefore we specify an implementation dep rather than a compileOnly.
|
||||
implementation(libs.kotlin.gradle.plugin)
|
||||
implementation(libs.android.gradle.plugin)
|
||||
|
||||
implementation(libs.gson)
|
||||
implementation(libs.guava)
|
||||
implementation(libs.javapoet)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
testRuntimeOnly(
|
||||
files(
|
||||
serviceOf<ModuleRegistry>()
|
||||
.getModule("gradle-tooling-api-builders")
|
||||
.classpath
|
||||
.asFiles
|
||||
.first()))
|
||||
}
|
||||
|
||||
// We intentionally don't build for Java 17 as users will see a cryptic bytecode version
|
||||
// error first. Instead we produce a Java 11-compatible Gradle Plugin, so that AGP can print their
|
||||
// nice message showing that JDK 11 (or 17) is required first
|
||||
java { targetCompatibility = JavaVersion.VERSION_11 }
|
||||
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
apiVersion = "1.6"
|
||||
// See comment above on JDK 11 support
|
||||
jvmTarget = "11"
|
||||
allWarningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
testLogging {
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,9 @@
|
||||
"gradle",
|
||||
"gradlew",
|
||||
"gradlew.bat",
|
||||
"src/main",
|
||||
"README.md"
|
||||
"README.md",
|
||||
"react-native-gradle-plugin",
|
||||
"settings-plugin",
|
||||
"shared"
|
||||
]
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
# React Native Gradle Plugin
|
||||
|
||||
This plugin is used by React Native Apps to configure themselves.
|
||||
|
||||
NOTE: It's important that this folder is called `react-native-gradle-plugin` as it's used
|
||||
by users in their `build.gradle` file as follows:
|
||||
|
||||
```gradle
|
||||
buildscript {
|
||||
// ...
|
||||
dependencies {
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The name of the artifact is imposed by the folder name.
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 org.gradle.api.internal.classpath.ModuleRegistry
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.gradle.configurationcache.extensions.serviceOf
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
id("java-gradle-plugin")
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("react") {
|
||||
id = "com.facebook.react"
|
||||
implementationClass = "com.facebook.react.ReactPlugin"
|
||||
}
|
||||
create("reactrootproject") {
|
||||
id = "com.facebook.react.rootproject"
|
||||
implementationClass = "com.facebook.react.ReactRootProjectPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group = "com.facebook.react"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
|
||||
implementation(gradleApi())
|
||||
|
||||
// The KGP/AGP version is defined by React Native Gradle plugin.
|
||||
// Therefore we specify an implementation dep rather than a compileOnly.
|
||||
implementation(libs.kotlin.gradle.plugin)
|
||||
implementation(libs.android.gradle.plugin)
|
||||
|
||||
implementation(libs.gson)
|
||||
implementation(libs.guava)
|
||||
implementation(libs.javapoet)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
testRuntimeOnly(
|
||||
files(
|
||||
serviceOf<ModuleRegistry>()
|
||||
.getModule("gradle-tooling-api-builders")
|
||||
.classpath
|
||||
.asFiles
|
||||
.first()))
|
||||
}
|
||||
|
||||
// We intentionally don't build for Java 17 as users will see a cryptic bytecode version
|
||||
// error first. Instead we produce a Java 11-compatible Gradle Plugin, so that AGP can print their
|
||||
// nice message showing that JDK 11 (or 17) is required first
|
||||
java { targetCompatibility = JavaVersion.VERSION_11 }
|
||||
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
apiVersion = "1.6"
|
||||
// See comment above on JDK 11 support
|
||||
jvmTarget = "11"
|
||||
allWarningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
testLogging {
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
@ -7,16 +7,17 @@
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import com.facebook.react.utils.JsonUtils
|
||||
import com.facebook.react.utils.projectPathToLibraryName
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
|
||||
abstract class ReactExtension @Inject constructor(project: Project) {
|
||||
abstract class ReactExtension @Inject constructor(val project: Project) {
|
||||
|
||||
private val objects = project.objects
|
||||
|
||||
@ -149,34 +150,54 @@ abstract class ReactExtension @Inject constructor(project: Project) {
|
||||
val codegenJavaPackageName: Property<String> =
|
||||
objects.property(String::class.java).convention("com.facebook.fbreact.specs")
|
||||
|
||||
/** Auto-linking Config */
|
||||
/** Auto-linking Utils */
|
||||
|
||||
/**
|
||||
* Location of the JSON file used to configure autolinking. This file is the output of the
|
||||
* `@react-native-community/cli` config command.
|
||||
* Utility function to autolink libraries to the app.
|
||||
*
|
||||
* If not specified, RNGP will just invoke whatever you pass as [autolinkConfigCommand].
|
||||
* This function will read the autolinking configuration file and add Gradle dependencies to the
|
||||
* app. This function should be invoked inside the react {} block in the app's build.gradle and is
|
||||
* necessary for libraries to be linked correctly.
|
||||
*/
|
||||
val autolinkConfigFile: RegularFileProperty = objects.fileProperty()
|
||||
fun autolinkLibrariesWithApp() {
|
||||
val inputFile =
|
||||
project.rootProject.layout.buildDirectory
|
||||
.file("generated/autolinking/autolinking.json")
|
||||
.get()
|
||||
.asFile
|
||||
val dependenciesToApply = getGradleDependenciesToApply(inputFile)
|
||||
dependenciesToApply.forEach { (configuration, path) ->
|
||||
project.dependencies.add(configuration, project.dependencies.project(mapOf("path" to path)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The command to invoke as source of truth for the autolinking configuration. Default is `["npx",
|
||||
* "@react-native-community/cli", "config"]`.
|
||||
*/
|
||||
val autolinkConfigCommand: ListProperty<String> =
|
||||
objects
|
||||
.listProperty(String::class.java)
|
||||
.convention(listOf("npx", "@react-native-community/cli", "config"))
|
||||
|
||||
/**
|
||||
* Location of the lock files used to consider whether autolinking [autolinkConfigCommand] should
|
||||
* re-execute or not. If file collection is unchanged, the autolinking command will not be
|
||||
* re-executed.
|
||||
*
|
||||
* If not specified, RNGP will just look for both yarn.lock and package.lock in the [root] folder.
|
||||
*/
|
||||
val autolinkLockFiles: Property<FileCollection> =
|
||||
objects
|
||||
.property(FileCollection::class.java)
|
||||
.convention(root.files("../yarn.lock", "../package-lock.json"))
|
||||
companion object {
|
||||
/**
|
||||
* Util function to construct a list of Gradle Configuration <-> Project name pairs for
|
||||
* autolinking. Pairs looks like: "implementation" -> ":react-native_oss-library-example"
|
||||
*
|
||||
* They will be applied to the Gradle project for linking the libraries.
|
||||
*
|
||||
* @param inputFile The file to read the autolinking configuration from.
|
||||
* @return A list of Gradle Configuration <-> Project name pairs.
|
||||
*/
|
||||
internal fun getGradleDependenciesToApply(inputFile: File): MutableList<Pair<String, String>> {
|
||||
val model = JsonUtils.fromAutolinkingConfigJson(inputFile)
|
||||
val result = mutableListOf<Pair<String, String>>()
|
||||
model?.dependencies?.values?.forEach { deps ->
|
||||
val nameCleansed = deps.nameCleansed
|
||||
val dependencyConfiguration = deps.platforms?.android?.dependencyConfiguration
|
||||
val buildTypes = deps.platforms?.android?.buildTypes ?: emptyList()
|
||||
if (buildTypes.isEmpty()) {
|
||||
result.add((dependencyConfiguration ?: "implementation") to ":$nameCleansed")
|
||||
} else {
|
||||
buildTypes.forEach { buildType ->
|
||||
result.add(
|
||||
(dependencyConfiguration ?: "${buildType}Implementation") to ":$nameCleansed")
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask
|
||||
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
|
||||
import com.facebook.react.tasks.GenerateCodegenSchemaTask
|
||||
import com.facebook.react.tasks.GeneratePackageListTask
|
||||
import com.facebook.react.tasks.RunAutolinkingConfigTask
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
|
||||
@ -220,22 +219,15 @@ class ReactPlugin : Plugin<Project> {
|
||||
project: Project,
|
||||
extension: ReactExtension,
|
||||
) {
|
||||
val generatedAutolinkingDir: Provider<Directory> =
|
||||
project.layout.buildDirectory.dir("generated/autolinking")
|
||||
val generatedAutolinkingJavaDir: Provider<Directory> =
|
||||
project.layout.buildDirectory.dir("generated/autolinking/src/main/java")
|
||||
val generatedAutolinkingJniDir: Provider<Directory> =
|
||||
project.layout.buildDirectory.dir("generated/autolinking/src/main/jni")
|
||||
val configOutputFile = generatedAutolinkingDir.get().file("config-output.json")
|
||||
|
||||
val runAutolinkingConfigTask =
|
||||
project.tasks.register("runAutolinkingConfig", RunAutolinkingConfigTask::class.java) { task
|
||||
->
|
||||
task.autolinkConfigCommand.set(extension.autolinkConfigCommand)
|
||||
task.autolinkConfigFile.set(extension.autolinkConfigFile)
|
||||
task.autolinkOutputFile.set(configOutputFile)
|
||||
task.autolinkLockFiles.set(extension.autolinkLockFiles)
|
||||
}
|
||||
// The autolinking.json file is available in the root build folder as it's generated
|
||||
// by ReactSettingsPlugin.kt
|
||||
val rootGeneratedAutolinkingFile =
|
||||
project.rootProject.layout.buildDirectory.file("generated/autolinking/autolinking.json")
|
||||
|
||||
// We add a task called generateAutolinkingPackageList to do not clash with the existing task
|
||||
// called generatePackageList. This can to be renamed once we unlink the rn <-> cli
|
||||
@ -243,8 +235,7 @@ class ReactPlugin : Plugin<Project> {
|
||||
val generatePackageListTask =
|
||||
project.tasks.register(
|
||||
"generateAutolinkingPackageList", GeneratePackageListTask::class.java) { task ->
|
||||
task.dependsOn(runAutolinkingConfigTask)
|
||||
task.autolinkInputFile.set(configOutputFile)
|
||||
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
|
||||
task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
|
||||
}
|
||||
|
||||
@ -254,8 +245,7 @@ class ReactPlugin : Plugin<Project> {
|
||||
project.tasks.register(
|
||||
"generateAutolinkingNewArchitectureFiles",
|
||||
GenerateAutolinkingNewArchitecturesFileTask::class.java) { task ->
|
||||
task.dependsOn(runAutolinkingConfigTask)
|
||||
task.autolinkInputFile.set(configOutputFile)
|
||||
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
|
||||
task.generatedOutputDirectory.set(generatedAutolinkingJniDir)
|
||||
}
|
||||
project.tasks
|
@ -54,17 +54,20 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() {
|
||||
val libraryIncludes =
|
||||
packages.joinToString("\n") { dep ->
|
||||
var addDirectoryString = ""
|
||||
if (dep.libraryName != null && dep.cmakeListsPath != null) {
|
||||
val libraryName = dep.libraryName
|
||||
val cmakeListsPath = dep.cmakeListsPath
|
||||
val cxxModuleCMakeListsPath = dep.cxxModuleCMakeListsPath
|
||||
if (libraryName != null && cmakeListsPath != null) {
|
||||
// If user provided a custom cmakeListsPath, let's honor it.
|
||||
val nativeFolderPath = dep.cmakeListsPath.replace("CMakeLists.txt", "")
|
||||
val nativeFolderPath = cmakeListsPath.replace("CMakeLists.txt", "")
|
||||
addDirectoryString +=
|
||||
"add_subdirectory($nativeFolderPath ${dep.libraryName}_autolinked_build)"
|
||||
"add_subdirectory($nativeFolderPath ${libraryName}_autolinked_build)"
|
||||
}
|
||||
if (dep.cxxModuleCMakeListsPath != null) {
|
||||
if (cxxModuleCMakeListsPath != null) {
|
||||
// If user provided a custom cxxModuleCMakeListsPath, let's honor it.
|
||||
val nativeFolderPath = dep.cxxModuleCMakeListsPath.replace("CMakeLists.txt", "")
|
||||
val nativeFolderPath = cxxModuleCMakeListsPath.replace("CMakeLists.txt", "")
|
||||
addDirectoryString +=
|
||||
"\nadd_subdirectory($nativeFolderPath ${dep.libraryName}_cxxmodule_autolinked_build)"
|
||||
"\nadd_subdirectory($nativeFolderPath ${libraryName}_cxxmodule_autolinked_build)"
|
||||
}
|
||||
addDirectoryString
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import com.facebook.react.ReactExtension.Companion.getGradleDependenciesToApply
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class ReactExtensionTest {
|
||||
|
||||
@get:Rule val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun getGradleDependenciesToApply_withEmptyFile_returnsEmptyMap() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0"
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val deps = getGradleDependenciesToApply(validJsonFile)
|
||||
assertEquals(0, deps.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getGradleDependenciesToApply_withOneDependency_returnsValidDep() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0",
|
||||
"dependencies": {
|
||||
"@react-native/oss-library-example": {
|
||||
"root": "./node_modules/@react-native/oss-library-example",
|
||||
"name": "@react-native/oss-library-example",
|
||||
"platforms": {
|
||||
"android": {
|
||||
"sourceDir": "src/main/java",
|
||||
"packageImportPath": "com.facebook.react"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val deps = getGradleDependenciesToApply(validJsonFile)
|
||||
assertEquals(1, deps.size)
|
||||
assertTrue("implementation" to ":react-native_oss-library-example" in deps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getGradleDependenciesToApply_withDependencyConfiguration_returnsValidConfiguration() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0",
|
||||
"dependencies": {
|
||||
"@react-native/oss-library-example": {
|
||||
"root": "./node_modules/@react-native/oss-library-example",
|
||||
"name": "@react-native/oss-library-example",
|
||||
"platforms": {
|
||||
"android": {
|
||||
"sourceDir": "src/main/java",
|
||||
"packageImportPath": "com.facebook.react",
|
||||
"dependencyConfiguration": "compileOnly"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val deps = getGradleDependenciesToApply(validJsonFile)
|
||||
assertEquals(1, deps.size)
|
||||
assertTrue("compileOnly" to ":react-native_oss-library-example" in deps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getGradleDependenciesToApply_withBuildTypes_returnsValidConfiguration() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0",
|
||||
"dependencies": {
|
||||
"@react-native/oss-library-example": {
|
||||
"root": "./node_modules/@react-native/oss-library-example",
|
||||
"name": "@react-native/oss-library-example",
|
||||
"platforms": {
|
||||
"android": {
|
||||
"sourceDir": "src/main/java",
|
||||
"packageImportPath": "com.facebook.react",
|
||||
"buildTypes": ["debug", "release"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val deps = getGradleDependenciesToApply(validJsonFile)
|
||||
assertEquals(2, deps.size)
|
||||
assertTrue("debugImplementation" to ":react-native_oss-library-example" in deps)
|
||||
assertTrue("releaseImplementation" to ":react-native_oss-library-example" in deps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getGradleDependenciesToApply_withMultipleDependencies_returnsValidConfiguration() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0",
|
||||
"dependencies": {
|
||||
"@react-native/oss-library-example": {
|
||||
"root": "./node_modules/@react-native/oss-library-example",
|
||||
"name": "@react-native/oss-library-example",
|
||||
"platforms": {
|
||||
"android": {
|
||||
"sourceDir": "src/main/java",
|
||||
"packageImportPath": "com.facebook.react"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-native/another-library-for-testing": {
|
||||
"root": "./node_modules/@react-native/another-library-for-testing",
|
||||
"name": "@react-native/another-library-for-testing",
|
||||
"platforms": {
|
||||
"android": {
|
||||
"sourceDir": "src/main/java",
|
||||
"packageImportPath": "com.facebook.react"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val deps = getGradleDependenciesToApply(validJsonFile)
|
||||
assertEquals(2, deps.size)
|
||||
assertTrue("implementation" to ":react-native_oss-library-example" in deps)
|
||||
assertTrue("implementation" to ":react-native_another-library-for-testing" in deps)
|
||||
}
|
||||
|
||||
private fun createJsonFile(@Language("JSON") input: String) =
|
||||
tempFolder.newFile().apply { writeText(input) }
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 org.gradle.api.internal.classpath.ModuleRegistry
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.gradle.configurationcache.extensions.serviceOf
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
id("java-gradle-plugin")
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("react.settings") {
|
||||
id = "com.facebook.react.settings"
|
||||
implementationClass = "com.facebook.react.ReactSettingsPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group = "com.facebook.react"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
|
||||
implementation(gradleApi())
|
||||
implementation(libs.gson)
|
||||
implementation(libs.guava)
|
||||
implementation(libs.javapoet)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
testRuntimeOnly(
|
||||
files(
|
||||
serviceOf<ModuleRegistry>()
|
||||
.getModule("gradle-tooling-api-builders")
|
||||
.classpath
|
||||
.asFiles
|
||||
.first()))
|
||||
}
|
||||
|
||||
// We intentionally don't build for Java 17 as users will see a cryptic bytecode version
|
||||
// error first. Instead we produce a Java 11-compatible Gradle Plugin, so that AGP can print their
|
||||
// nice message showing that JDK 11 (or 17) is required first
|
||||
java { targetCompatibility = JavaVersion.VERSION_11 }
|
||||
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
apiVersion = "1.6"
|
||||
// See comment above on JDK 11 support
|
||||
jvmTarget = "11"
|
||||
allWarningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
testLogging {
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import com.facebook.react.utils.JsonUtils
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
abstract class ReactSettingsExtension @Inject constructor(val settings: Settings) {
|
||||
|
||||
private val outputFile =
|
||||
settings.layout.rootDirectory.file("build/generated/autolinking/autolinking.json").asFile
|
||||
private val outputFolder =
|
||||
settings.layout.rootDirectory.file("build/generated/autolinking/").asFile
|
||||
|
||||
/**
|
||||
* Utility function to autolink libraries using an external command as source of truth.
|
||||
*
|
||||
* This should be invoked inside the `settings.gradle` file, and will make sure the Gradle project
|
||||
* is loading all the discovered libraries.
|
||||
*
|
||||
* @param command The command to execute to get the autolinking configuration. Default is
|
||||
* `npx @react-native-community/cli config`.
|
||||
* @param workingDirectory The directory where the command should be executed.
|
||||
* @param lockFiles The list of lock files to check for changes (if lockfiles are not changed, the
|
||||
* command will not be executed).
|
||||
*/
|
||||
@JvmOverloads
|
||||
public fun autolinkLibrariesFromCommand(
|
||||
command: List<String> = listOf("npx", "@react-native-community/cli", "config"),
|
||||
workingDirectory: File? = settings.layout.rootDirectory.dir("../").asFile,
|
||||
lockFiles: FileCollection =
|
||||
settings.layout.rootDirectory.dir("../").files("yarn.lock", "package-lock.json")
|
||||
) {
|
||||
outputFile.parentFile.mkdirs()
|
||||
val lockFilesChanged = checkAndUpdateLockfiles(lockFiles, outputFolder)
|
||||
if (lockFilesChanged || outputFile.exists().not()) {
|
||||
ProcessBuilder(command)
|
||||
.directory(workingDirectory)
|
||||
.redirectOutput(ProcessBuilder.Redirect.to(outputFile))
|
||||
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||
.start()
|
||||
.waitFor(5, TimeUnit.MINUTES)
|
||||
}
|
||||
linkLibraries(getLibrariesToAutolink(outputFile))
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to autolink libraries using an external file as source of truth.
|
||||
*
|
||||
* The file should be a JSON file with the same structure as the one generated by the
|
||||
* `npx @react-native-community/cli config` command.
|
||||
*
|
||||
* @param autolinkConfigFile The file to read the autolinking configuration from.
|
||||
*/
|
||||
public fun autolinkLibrariesFromConfigFile(
|
||||
autolinkConfigFile: File,
|
||||
) {
|
||||
// We copy the file to the build directory so that the various Gradle tasks can access it.
|
||||
autolinkConfigFile.copyTo(outputFile, overwrite = true)
|
||||
linkLibraries(getLibrariesToAutolink(autolinkConfigFile))
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function so that for each tuple :project-name -> project-dir, it instructs Gradle to
|
||||
* lad this extra module.
|
||||
*/
|
||||
private fun linkLibraries(input: Map<String, File>) {
|
||||
input.forEach { (path, projectDir) ->
|
||||
settings.include(path)
|
||||
settings.project(path).projectDir = projectDir
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val md = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
/**
|
||||
* Utility function to check if the provided lockfiles have been updated or not. This function
|
||||
* will both check and update the lockfiles hashes if necessary.
|
||||
*
|
||||
* @param lockFiles The [FileCollection] of the lockfiles to check.
|
||||
* @param outputFolder The folder where the hashes will be stored.
|
||||
* @return `true` if the lockfiles have been updated, `false` otherwise.
|
||||
*/
|
||||
internal fun checkAndUpdateLockfiles(lockFiles: FileCollection, outputFolder: File): Boolean {
|
||||
var changed = false
|
||||
lockFiles.forEach { lockFile ->
|
||||
if (lockFile.exists()) {
|
||||
val sha = computeSha256(lockFile)
|
||||
val shaFile = File(outputFolder, "${lockFile.name}.sha")
|
||||
if (shaFile.exists().not() || shaFile.readText() != sha) {
|
||||
shaFile.writeText(sha)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
internal fun getLibrariesToAutolink(buildFile: File): Map<String, File> {
|
||||
val model = JsonUtils.fromAutolinkingConfigJson(buildFile)
|
||||
return model?.dependencies?.values?.associate { deps ->
|
||||
":${deps.nameCleansed}" to File(deps.platforms?.android?.sourceDir)
|
||||
} ?: emptyMap()
|
||||
}
|
||||
|
||||
internal fun computeSha256(lockFile: File) =
|
||||
String.format("%032x", BigInteger(1, md.digest(lockFile.readBytes())))
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
/**
|
||||
* This is the settings.gradle plugin for React Native.
|
||||
*
|
||||
* It just registers the [ReactSettingsExtension] extension, so that utility functions over there
|
||||
* can be called to support autolinking.
|
||||
*/
|
||||
class ReactSettingsPlugin : Plugin<Settings> {
|
||||
override fun apply(settings: Settings) {
|
||||
settings.extensions.create("reactSettings", ReactSettingsExtension::class.java, settings)
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import com.facebook.react.ReactSettingsExtension.Companion.checkAndUpdateLockfiles
|
||||
import com.facebook.react.ReactSettingsExtension.Companion.computeSha256
|
||||
import com.facebook.react.ReactSettingsExtension.Companion.getLibrariesToAutolink
|
||||
import groovy.test.GroovyTestCase.assertEquals
|
||||
import java.io.File
|
||||
import org.gradle.testfixtures.ProjectBuilder
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class ReactSettingsExtensionTest {
|
||||
|
||||
@get:Rule val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun computeSha256_worksCorrectly() {
|
||||
val validFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"value": "¯\\_(ツ)_/¯"
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
assertEquals(
|
||||
"838aa9a72a16fdd55b0d49b510a82e264a30f59333b5fdd97c7798a29146f6a8",
|
||||
computeSha256(validFile))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getLibrariesToAutolink_withEmptyFile_returnsEmptyMap() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0"
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val map = getLibrariesToAutolink(validJsonFile)
|
||||
assertEquals(0, map.keys.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getLibrariesToAutolink_withLibraryToAutolink_returnsValidMap() {
|
||||
val validJsonFile =
|
||||
createJsonFile(
|
||||
"""
|
||||
{
|
||||
"reactNativeVersion": "1000.0.0",
|
||||
"dependencies": {
|
||||
"@react-native/oss-library-example": {
|
||||
"root": "./node_modules/@react-native/oss-library-example",
|
||||
"name": "@react-native/oss-library-example",
|
||||
"platforms": {
|
||||
"ios": {
|
||||
"podspecPath": "./node_modules/@react-native/oss-library-example/OSSLibraryExample.podspec",
|
||||
"version": "0.0.1",
|
||||
"configurations": [],
|
||||
"scriptPhases": []
|
||||
},
|
||||
"android": {
|
||||
"sourceDir": "./node_modules/@react-native/oss-library-example/android",
|
||||
"packageImportPath": "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;",
|
||||
"packageInstance": "new OSSLibraryExamplePackage()",
|
||||
"buildTypes": ["staging", "debug", "release"],
|
||||
"libraryName": "OSSLibraryExampleSpec",
|
||||
"componentDescriptors": [
|
||||
"SampleNativeComponentComponentDescriptor"
|
||||
],
|
||||
"cmakeListsPath": "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt",
|
||||
"cxxModuleCMakeListsModuleName": null,
|
||||
"cxxModuleCMakeListsPath": null,
|
||||
"cxxModuleHeaderName": null,
|
||||
"dependencyConfiguration": "implementation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent())
|
||||
|
||||
val map = getLibrariesToAutolink(validJsonFile)
|
||||
assertEquals(1, map.keys.size)
|
||||
assertTrue(":react-native_oss-library-example" in map.keys)
|
||||
assertEquals(
|
||||
File("./node_modules/@react-native/oss-library-example/android"),
|
||||
map[":react-native_oss-library-example"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAndUpdateLockfiles_withNothingToCheck_returnsFalse() {
|
||||
val project = ProjectBuilder.builder().build()
|
||||
val noFiles = project.files()
|
||||
assertFalse(checkAndUpdateLockfiles(noFiles, tempFolder.root))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAndUpdateLockfiles_withOneLockfileNoHash_returnsTrue() {
|
||||
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
|
||||
val buildFolder = tempFolder.newFolder("build")
|
||||
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
|
||||
val lockfileCollection = project.files("yarn.lock")
|
||||
|
||||
assertTrue(checkAndUpdateLockfiles(lockfileCollection, buildFolder))
|
||||
assertTrue(File(buildFolder, "yarn.lock.sha").exists())
|
||||
assertEquals(
|
||||
"76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8",
|
||||
File(buildFolder, "yarn.lock.sha").readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAndUpdateLockfiles_withOneLockfileInvalidHash_returnsTrue() {
|
||||
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
|
||||
val buildFolder =
|
||||
tempFolder.newFolder("build").apply {
|
||||
File(this, "yarn.lock.sha").writeText("Just a stale hash")
|
||||
}
|
||||
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
|
||||
val lockfileCollection = project.files("yarn.lock")
|
||||
|
||||
assertTrue(checkAndUpdateLockfiles(lockfileCollection, buildFolder))
|
||||
assertTrue(File(buildFolder, "yarn.lock.sha").exists())
|
||||
assertEquals(
|
||||
"76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8",
|
||||
File(buildFolder, "yarn.lock.sha").readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAndUpdateLockfiles_withOneLockfileValidHash_returnsFalse() {
|
||||
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
|
||||
val buildFolder =
|
||||
tempFolder.newFolder("build").apply {
|
||||
File(this, "yarn.lock.sha")
|
||||
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
|
||||
}
|
||||
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
|
||||
val lockfileCollection = project.files("yarn.lock")
|
||||
|
||||
assertFalse(checkAndUpdateLockfiles(lockfileCollection, buildFolder))
|
||||
assertTrue(File(buildFolder, "yarn.lock.sha").exists())
|
||||
assertEquals(
|
||||
"76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8",
|
||||
File(buildFolder, "yarn.lock.sha").readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAndUpdateLockfiles_withMultipleLockfilesInvalidHash_returnsTrue() {
|
||||
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
|
||||
val buildFolder =
|
||||
tempFolder.newFolder("build").apply {
|
||||
File(this, "yarn.lock.sha").writeText("I'm an invalid hash")
|
||||
}
|
||||
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
|
||||
tempFolder.newFile("package-lock.json").apply { writeText("and I'm another lockfile") }
|
||||
val lockfileCollection = project.files("yarn.lock", "package-lock.json")
|
||||
|
||||
assertTrue(checkAndUpdateLockfiles(lockfileCollection, buildFolder))
|
||||
assertTrue(File(buildFolder, "yarn.lock.sha").exists())
|
||||
assertTrue(File(buildFolder, "package-lock.json.sha").exists())
|
||||
assertEquals(
|
||||
"76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8",
|
||||
File(buildFolder, "yarn.lock.sha").readText())
|
||||
assertEquals(
|
||||
"9be5bca432b81becf4f54451aea021add68376330581eaa93ab9a0b3e4e29a3b",
|
||||
File(buildFolder, "package-lock.json.sha").readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAndUpdateLockfiles_withMultipleLockfilesValidHash_returnsFalse() {
|
||||
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
|
||||
val buildFolder =
|
||||
tempFolder.newFolder("build").apply {
|
||||
File(this, "yarn.lock.sha")
|
||||
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
|
||||
File(this, "package-lock.json.sha")
|
||||
.writeText("9be5bca432b81becf4f54451aea021add68376330581eaa93ab9a0b3e4e29a3b")
|
||||
}
|
||||
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
|
||||
tempFolder.newFile("package-lock.json").apply { writeText("and I'm another lockfile") }
|
||||
val lockfileCollection = project.files("yarn.lock", "package-lock.json")
|
||||
|
||||
assertFalse(checkAndUpdateLockfiles(lockfileCollection, buildFolder))
|
||||
assertTrue(File(buildFolder, "yarn.lock.sha").exists())
|
||||
assertTrue(File(buildFolder, "package-lock.json.sha").exists())
|
||||
assertEquals(
|
||||
"76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8",
|
||||
File(buildFolder, "yarn.lock.sha").readText())
|
||||
assertEquals(
|
||||
"9be5bca432b81becf4f54451aea021add68376330581eaa93ab9a0b3e4e29a3b",
|
||||
File(buildFolder, "package-lock.json.sha").readText())
|
||||
}
|
||||
|
||||
private fun createJsonFile(@Language("JSON") input: String) =
|
||||
tempFolder.newFile().apply { writeText(input) }
|
||||
}
|
@ -15,4 +15,10 @@ pluginManagement {
|
||||
|
||||
plugins { id("org.gradle.toolchains.foojay-resolver-convention").version("0.5.0") }
|
||||
|
||||
rootProject.name = "react-native-gradle-plugin"
|
||||
include(
|
||||
":react-native-gradle-plugin",
|
||||
":settings-plugin",
|
||||
":shared",
|
||||
)
|
||||
|
||||
rootProject.name = "gradle-plugins-root"
|
||||
|
42
packages/react-native-gradle-plugin/shared/build.gradle.kts
Normal file
42
packages/react-native-gradle-plugin/shared/build.gradle.kts
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins { alias(libs.plugins.kotlin.jvm) }
|
||||
|
||||
repositories { mavenCentral() }
|
||||
|
||||
group = "com.facebook.react"
|
||||
|
||||
dependencies {
|
||||
implementation(libs.gson)
|
||||
implementation(libs.guava)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
java { targetCompatibility = JavaVersion.VERSION_11 }
|
||||
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
apiVersion = "1.6"
|
||||
jvmTarget = "11"
|
||||
allWarningsAsErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
testLogging {
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
@ -1,78 +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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.tasks
|
||||
|
||||
import com.facebook.react.utils.windowsAwareCommandLine
|
||||
import java.io.FileOutputStream
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
/**
|
||||
* A task that will run @react-native-community/cli config if necessary to generate the autolinking
|
||||
* configuration file.
|
||||
*/
|
||||
abstract class RunAutolinkingConfigTask : Exec() {
|
||||
|
||||
init {
|
||||
group = "react"
|
||||
}
|
||||
|
||||
@get:Input abstract val autolinkConfigCommand: ListProperty<String>
|
||||
|
||||
/*
|
||||
* We don't want to re-run config if the lockfiles haven't changed.
|
||||
* So we have the lockfiles as @InputFiles for this task.
|
||||
*/
|
||||
@get:InputFiles abstract val autolinkLockFiles: Property<FileCollection>
|
||||
|
||||
@get:InputFile @get:Optional abstract val autolinkConfigFile: RegularFileProperty
|
||||
|
||||
@get:OutputFile abstract val autolinkOutputFile: RegularFileProperty
|
||||
|
||||
override fun exec() {
|
||||
wipeOutputDir()
|
||||
setupCommandLine()
|
||||
super.exec()
|
||||
}
|
||||
|
||||
internal fun setupCommandLine() {
|
||||
if (!autolinkConfigFile.isPresent || !autolinkConfigFile.get().asFile.exists()) {
|
||||
setupConfigCommandLine()
|
||||
} else {
|
||||
setupConfigCopyCommandLine()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun wipeOutputDir() {
|
||||
autolinkOutputFile.asFile.get().apply {
|
||||
deleteRecursively()
|
||||
parentFile.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setupConfigCommandLine() {
|
||||
workingDir(project.projectDir)
|
||||
standardOutput = FileOutputStream(autolinkOutputFile.get().asFile)
|
||||
commandLine(
|
||||
windowsAwareCommandLine(
|
||||
*autolinkConfigCommand.get().toTypedArray(),
|
||||
))
|
||||
}
|
||||
|
||||
internal fun setupConfigCopyCommandLine() {
|
||||
workingDir(project.projectDir)
|
||||
commandLine(
|
||||
windowsAwareCommandLine(
|
||||
"cp",
|
||||
autolinkConfigFile.get().asFile.absolutePath,
|
||||
autolinkOutputFile.get().asFile.absolutePath))
|
||||
}
|
||||
}
|
@ -1,159 +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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.tasks
|
||||
|
||||
import com.facebook.react.tests.createProject
|
||||
import com.facebook.react.tests.createTestTask
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class RunAutolinkingConfigTaskTest {
|
||||
|
||||
@get:Rule val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun runAutolinkingConfigTask_groupIsSetCorrectly() {
|
||||
val task = createTestTask<RunAutolinkingConfigTask> {}
|
||||
assertEquals("react", task.group)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runAutolinkingConfigTask_staticInputs_areSetCorrectly() {
|
||||
val project = createProject()
|
||||
|
||||
val task =
|
||||
createTestTask<RunAutolinkingConfigTask> {
|
||||
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
|
||||
it.autolinkLockFiles.set(project.files("packager.lock", "another-packager.lock"))
|
||||
it.autolinkConfigFile.set(tempFolder.newFile("dependencies.json"))
|
||||
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
|
||||
}
|
||||
|
||||
assertEquals(3, task.inputs.files.files.size)
|
||||
task.autolinkLockFiles.get().files.forEach {
|
||||
assertTrue(
|
||||
it.name == "depedencies.json" ||
|
||||
it.name == "packager.lock" ||
|
||||
it.name == "another-packager.lock")
|
||||
}
|
||||
|
||||
assertTrue(task.inputs.properties.containsKey("autolinkConfigCommand"))
|
||||
assertEquals(1, task.outputs.files.files.size)
|
||||
assertEquals(File(tempFolder.root, "output.json"), task.outputs.files.singleFile)
|
||||
assertEquals(listOf("rm", "-rf", "/"), task.autolinkConfigCommand.get())
|
||||
|
||||
assertEquals(2, task.autolinkLockFiles.get().files.size)
|
||||
task.autolinkLockFiles.get().files.forEach {
|
||||
assertTrue(it.name == "packager.lock" || it.name == "another-packager.lock")
|
||||
}
|
||||
|
||||
assertEquals(File(tempFolder.root, "dependencies.json"), task.autolinkConfigFile.get().asFile)
|
||||
assertEquals(File(tempFolder.root, "output.json"), task.autolinkOutputFile.get().asFile)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wipeOutputDir_worksCorrectly() {
|
||||
val outputDir =
|
||||
tempFolder.newFolder("output").apply {
|
||||
File(this, "output.json").createNewFile()
|
||||
File(this, "NothingToSeeHere.java").createNewFile()
|
||||
}
|
||||
|
||||
val task = createTestTask<RunAutolinkingConfigTask> { it.autolinkOutputFile.set(outputDir) }
|
||||
task.wipeOutputDir()
|
||||
|
||||
assertFalse(outputDir.exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupConfigCommandLine_worksCorrectly() {
|
||||
val project = createProject()
|
||||
|
||||
val task =
|
||||
createTestTask<RunAutolinkingConfigTask>(project) {
|
||||
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
|
||||
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
|
||||
}
|
||||
task.setupConfigCommandLine()
|
||||
|
||||
assertEquals(project.projectDir, task.workingDir)
|
||||
assertTrue(task.standardOutput is FileOutputStream)
|
||||
assertEquals(listOf("rm", "-rf", "/"), task.commandLine)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupConfigCopyCommandLine_worksCorrectly() {
|
||||
val project = createProject()
|
||||
|
||||
val task =
|
||||
createTestTask<RunAutolinkingConfigTask>(project) {
|
||||
it.autolinkConfigFile.set(tempFolder.newFile("dependencies.json"))
|
||||
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
|
||||
}
|
||||
task.setupConfigCopyCommandLine()
|
||||
|
||||
assertEquals(project.projectDir, task.workingDir)
|
||||
assertTrue(task.standardOutput !is FileOutputStream)
|
||||
assertEquals("cp", task.commandLine[0])
|
||||
assertEquals(File(tempFolder.root, "dependencies.json").absolutePath, task.commandLine[1])
|
||||
assertEquals(File(tempFolder.root, "output.json").absolutePath, task.commandLine[2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupCommandLine_withoutAutolinkConfigFileConfigured_invokesCommand() {
|
||||
val project = createProject()
|
||||
|
||||
val task =
|
||||
createTestTask<RunAutolinkingConfigTask>(project) {
|
||||
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
|
||||
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
|
||||
}
|
||||
task.setupCommandLine()
|
||||
|
||||
assertEquals(listOf("rm", "-rf", "/"), task.commandLine)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupCommandLine_withoutMissingConfigFile_invokesCommand() {
|
||||
val project = createProject()
|
||||
|
||||
val task =
|
||||
createTestTask<RunAutolinkingConfigTask>(project) {
|
||||
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
|
||||
it.autolinkConfigFile.set(File(tempFolder.root, "dependencies.json"))
|
||||
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
|
||||
}
|
||||
task.setupCommandLine()
|
||||
|
||||
assertEquals(listOf("rm", "-rf", "/"), task.commandLine)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupCommandLine_withoutExistingConfigFile_invokesCp() {
|
||||
val project = createProject()
|
||||
val configFile = tempFolder.newFile("dependencies.json").apply { writeText("¯\\_(ツ)_/¯") }
|
||||
|
||||
val task =
|
||||
createTestTask<RunAutolinkingConfigTask>(project) {
|
||||
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
|
||||
it.autolinkConfigFile.set(configFile)
|
||||
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
|
||||
}
|
||||
task.setupCommandLine()
|
||||
|
||||
assertEquals(
|
||||
listOf("cp", configFile.absolutePath, File(tempFolder.root, "output.json").absolutePath),
|
||||
task.commandLine)
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
|
||||
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/ReactPopupMenuAndroidSpecs/*.cpp)
|
||||
|
||||
add_library(
|
||||
react_codegen_ReactPopupMenuAndroidSpecs
|
||||
SHARED
|
||||
${react_codegen_SRCS}
|
||||
)
|
||||
|
||||
target_include_directories(react_codegen_ReactPopupMenuAndroidSpecs PUBLIC . react/renderer/components/ReactPopupMenuAndroidSpecs)
|
||||
|
||||
target_link_libraries(
|
||||
react_codegen_ReactPopupMenuAndroidSpecs
|
||||
fbjni
|
||||
folly_runtime
|
||||
glog
|
||||
jsi
|
||||
react_codegen_rncore
|
||||
react_debug
|
||||
react_nativemodule_core
|
||||
react_render_componentregistry
|
||||
react_render_core
|
||||
react_render_debug
|
||||
react_render_graphics
|
||||
react_render_imagemanager
|
||||
react_render_mapbuffer
|
||||
react_utils
|
||||
rrc_image
|
||||
rrc_view
|
||||
turbomodulejsijni
|
||||
yoga
|
||||
)
|
||||
|
||||
target_compile_options(
|
||||
react_codegen_ReactPopupMenuAndroidSpecs
|
||||
PRIVATE
|
||||
-DLOG_TAG=\"ReactNative\"
|
||||
-fexceptions
|
||||
-frtti
|
||||
-std=c++20
|
||||
-Wall
|
||||
)
|
@ -0,0 +1,22 @@
|
||||
|
||||
/**
|
||||
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
||||
*
|
||||
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
||||
* once the code is regenerated.
|
||||
*
|
||||
* @generated by codegen project: GenerateModuleJniCpp.js
|
||||
*/
|
||||
|
||||
#include "ReactPopupMenuAndroidSpecs.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
|
||||
|
||||
std::shared_ptr<TurboModule> ReactPopupMenuAndroidSpecs_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) {
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
@ -0,0 +1,24 @@
|
||||
|
||||
/**
|
||||
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
||||
*
|
||||
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
||||
* once the code is regenerated.
|
||||
*
|
||||
* @generated by codegen project: GenerateModuleJniH.js
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ReactCommon/JavaTurboModule.h>
|
||||
#include <ReactCommon/TurboModule.h>
|
||||
#include <jsi/jsi.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
|
||||
|
||||
JSI_EXPORT
|
||||
std::shared_ptr<TurboModule> ReactPopupMenuAndroidSpecs_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms);
|
||||
|
||||
} // namespace facebook::react
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
dependency: {
|
||||
platforms: {
|
||||
android: {
|
||||
'cmakeListsPath': '../android/src/main/jni/CMakeLists.txt',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
16
packages/react-native-test-library/react-native.config.js
Normal file
16
packages/react-native-test-library/react-native.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
dependency: {
|
||||
platforms: {
|
||||
android: {
|
||||
'cmakeListsPath': '../android/src/main/jni/CMakeLists.txt',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -37,14 +37,14 @@ endif()
|
||||
|
||||
file(GLOB input_SRC CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
${BUILD_DIR}/generated/rncli/src/main/jni/*.cpp)
|
||||
${BUILD_DIR}/generated/autolinking/src/main/jni/*.cpp)
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED ${input_SRC})
|
||||
|
||||
target_include_directories(${CMAKE_PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${PROJECT_BUILD_DIR}/generated/rncli/src/main/jni)
|
||||
${PROJECT_BUILD_DIR}/generated/autolinking/src/main/jni)
|
||||
|
||||
target_compile_options(${CMAKE_PROJECT_NAME}
|
||||
PRIVATE
|
||||
@ -126,8 +126,8 @@ target_compile_options(common_flags INTERFACE ${folly_FLAGS})
|
||||
target_link_libraries(ReactAndroid::react_codegen_rncore INTERFACE common_flags)
|
||||
|
||||
# If project is on RN CLI v9, then we can use the following lines to link against the autolinked 3rd party libraries.
|
||||
if(EXISTS ${PROJECT_BUILD_DIR}/generated/rncli/src/main/jni/Android-rncli.cmake)
|
||||
include(${PROJECT_BUILD_DIR}/generated/rncli/src/main/jni/Android-rncli.cmake)
|
||||
if(EXISTS ${PROJECT_BUILD_DIR}/generated/autolinking/src/main/jni/Android-autolinking.cmake)
|
||||
include(${PROJECT_BUILD_DIR}/generated/autolinking/src/main/jni/Android-autolinking.cmake)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} ${AUTOLINKED_LIBRARIES})
|
||||
foreach(autolinked_library ${AUTOLINKED_LIBRARIES})
|
||||
target_link_libraries(${autolinked_library} common_flags)
|
||||
|
@ -49,6 +49,9 @@ react {
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
|
||||
/* Autolinking */
|
||||
autolinkLibrariesWithApp()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,5 +117,3 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.helloworld
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.PackageList2
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.ReactNativeHost
|
||||
@ -16,7 +16,7 @@ class MainApplication : Application(), ReactApplication {
|
||||
override val reactNativeHost: ReactNativeHost =
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
PackageList2(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user