Skip to content

Commit

Permalink
RNGP - Autolinking. Add support for linking projects. (#44799)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
cortinico authored and facebook-github-bot committed Jun 7, 2024
1 parent d8a0d30 commit cf914e4
Show file tree
Hide file tree
Showing 103 changed files with 983 additions and 389 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
6 changes: 3 additions & 3 deletions packages/helloworld/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

/**
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
}
Expand Down
7 changes: 6 additions & 1 deletion packages/helloworld/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 4 additions & 0 deletions packages/react-native-gradle-plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
app-plugin/build/
settings-plugin/build/
shared/build/
75 changes: 1 addition & 74 deletions packages/react-native-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
6 changes: 4 additions & 2 deletions packages/react-native-gradle-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"gradle",
"gradlew",
"gradlew.bat",
"src/main",
"README.md"
"README.md",
"react-native-gradle-plugin",
"settings-plugin",
"shared"
]
}
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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].
*/
val autolinkConfigFile: RegularFileProperty = objects.fileProperty()

/**
* 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"))
* 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.
*/
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)))
}
}

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
}
}
}
Loading

0 comments on commit cf914e4

Please sign in to comment.