Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react

import com.facebook.react.model.ModelAutolinkingDependenciesPlatformAndroidJson
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.projectPathToLibraryName
import java.io.File
Expand Down Expand Up @@ -201,19 +202,29 @@ abstract class ReactExtension @Inject constructor(val project: Project) {
?.filterNot { it.platforms?.android?.isPureCxxDependency == true }
?.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",
)
}
resolveGradleConfigurations(checkNotNull(deps.platforms?.android)).forEach {
configuration ->
result.add(configuration to ":$nameCleansed")
}
}
return result
}

/**
* Resolves the Gradle configuration names a dependency is added to, honoring
* `dependencyConfiguration` overrides and `buildTypes` scoping.
*/
internal fun resolveGradleConfigurations(
platformAndroid: ModelAutolinkingDependenciesPlatformAndroidJson
): List<String> {
val dependencyConfiguration = platformAndroid.dependencyConfiguration
// Declared non-null in the model, but JSON deserialization can leave it unset.
val buildTypes: List<String>? = platformAndroid.buildTypes
return if (buildTypes.isNullOrEmpty()) {
listOf(dependencyConfiguration ?: "implementation")
} else {
buildTypes.map { dependencyConfiguration ?: "${it}Implementation" }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import kotlin.system.exitProcess
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
Expand Down Expand Up @@ -349,8 +350,11 @@ class ReactPlugin : Plugin<Project> {
extension: ReactExtension,
rootExtension: PrivateReactExtension,
) {
// This folder intentionally doesn't reuse the previous generated/autolinking/src/main/java
// path: PackageList.java used to be generated there, so stale outputs from older React Native
// versions must never become part of the sources again.
val generatedAutolinkingJavaDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/autolinking/src/main/java")
project.layout.buildDirectory.dir("generated/autolinking/src/common/java")
val generatedAutolinkingJniDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/autolinking/src/main/jni")
val generatedPureCxxSourceDir: Provider<Directory> =
Expand All @@ -373,14 +377,14 @@ class ReactPlugin : Plugin<Project> {

// 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
// dependency.
// dependency. This is only a lifecycle task aggregating the per-variant instances
// registered inside onVariants below: PackageList.java is generated per variant so that
// dependencies restricted to specific build types are only listed in the variants that
// have them on the compile classpath.
val generatePackageListTask =
project.tasks.register(
"generateAutolinkingPackageList",
GeneratePackageListTask::class.java,
) { task ->
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
project.tasks.register("generateAutolinkingPackageList") { task ->
task.group = "react"
task.description = "Generates the PackageList.java file for every variant"
}

// We add a task called generateReactNativeEntryPoint to generate the React Native entry point.
Expand Down Expand Up @@ -418,13 +422,33 @@ class ReactPlugin : Plugin<Project> {
.named("preBuild", Task::class.java)
.dependsOn(generatePackageListTask, generateEntryPointTask)

// We tell Android Gradle Plugin that inside /build/generated/autolinking/src/main/java there
// are sources to be compiled as well.
// We tell Android Gradle Plugin that inside /build/generated/autolinking/src/common/java
// there are sources to be compiled as well. The entry point file is variant-agnostic and
// stays there, while the per-variant PackageList.java lives in its own source directory,
// added only to the variant it was generated for.
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).apply {
onVariants(selector().all()) { variant ->
variant.sources.java?.addStaticSourceDirectory(
generatedAutolinkingJavaDir.get().asFile.absolutePath,
)

val variantGeneratedAutolinkingJavaDir =
project.layout.buildDirectory.dir("generated/autolinking/src/${variant.name}/java")
val generateVariantPackageListTask =
project.tasks.register(
"generateAutolinkingPackageList${variant.name.replaceFirstChar(Char::uppercaseChar)}",
GeneratePackageListTask::class.java,
) { task ->
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
task.generatedOutputDirectory.set(variantGeneratedAutolinkingJavaDir)
project.configurations.findByName("${variant.name}CompileClasspath")?.let {
task.applicableConfigurations.set(compileClasspathConfigurationNames(it))
}
}
generatePackageListTask.configure { it.dependsOn(generateVariantPackageListTask) }
variant.sources.java?.addStaticSourceDirectory(
variantGeneratedAutolinkingJavaDir.get().asFile.absolutePath,
)
}
}
}
Expand Down Expand Up @@ -497,4 +521,24 @@ class ReactPlugin : Plugin<Project> {
.map { char -> if (char.isLetterOrDigit()) char.toString() else "_${char.code}_" }
.joinToString("")
.replaceFirstChar { char -> char.titlecase() }

/**
* Returns the names of [configuration] plus every configuration it transitively extends from,
* i.e. the Gradle configurations whose dependencies land on this compile classpath.
*/
private fun compileClasspathConfigurationNames(configuration: Configuration): Set<String> {
val names = mutableSetOf<String>()
val queue = ArrayDeque<Configuration>()
queue.add(configuration)
while (queue.isNotEmpty()) {
val current = queue.removeFirst()
names.add(current.name)
current.extendsFrom.forEach { parent ->
if (parent.name !in names) {
queue.add(parent)
}
}
}
return names
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@

package com.facebook.react.tasks

import com.facebook.react.ReactExtension
import com.facebook.react.model.ModelAutolinkingConfigJson
import com.facebook.react.model.ModelAutolinkingDependenciesPlatformAndroidJson
import com.facebook.react.utils.JsonUtils
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction

Expand All @@ -28,6 +32,16 @@ abstract class GeneratePackageListTask : DefaultTask() {

@get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty

/**
* The names of the Gradle configurations on the compile classpath of the variant this task is
* generating for: the variant's `<variant>CompileClasspath` configuration plus everything it
* transitively extends from (e.g. `implementation`, `api`, `debugImplementation`,
* `<flavor>Implementation`). A dependency is only listed when at least one of the configurations
* it resolves to is present in this set, so the generated file only references packages that are
* actually on the variant's compile classpath. When unset, all dependencies are included.
*/
@get:Optional @get:Input abstract val applicableConfigurations: SetProperty<String>

@TaskAction
fun taskAction() {
val model =
Expand All @@ -48,7 +62,7 @@ abstract class GeneratePackageListTask : DefaultTask() {
"RNGP - Autolinking: Could not find project.android.packageName in react-native config output! Could not autolink packages without this field.",
)

val androidPackages = filterAndroidPackages(model)
val androidPackages = filterAndroidPackages(model, applicableConfigurations.orNull)
val packageClassInstance = composePackageInstance(packageName, androidPackages)
val generatedFileContents = composeFileContent(packageClassInstance)

Expand Down Expand Up @@ -106,12 +120,24 @@ abstract class GeneratePackageListTask : DefaultTask() {

internal fun filterAndroidPackages(
model: ModelAutolinkingConfigJson?,
applicableConfigurations: Set<String>? = null,
): Map<String, ModelAutolinkingDependenciesPlatformAndroidJson> {
val packages = model?.dependencies?.values ?: emptyList()
return packages
.filter { it.platforms?.android != null }
// The pure C++ dependencies won't have a .java/.kt file to import
.filterNot { it.platforms?.android?.isPureCxxDependency == true }
// Each dependency resolves to one or more Gradle configurations (e.g. `implementation`,
// `debugImplementation` or a `dependencyConfiguration` override). It is only listed for
// a variant whose compile classpath contains one of those configurations — otherwise
// the generated file would reference a package that is not on the classpath.
.filter {
applicableConfigurations == null ||
ReactExtension.resolveGradleConfigurations(checkNotNull(it.platforms?.android)).any {
configuration ->
configuration in applicableConfigurations
}
}
.associate { it.name to checkNotNull(it.platforms?.android) }
}

Expand Down
Loading
Loading