Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -11,6 +11,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.model.ModelAutolinkingDependenciesJson
import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
Expand Down Expand Up @@ -38,6 +39,7 @@ import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.internal.jvm.Jvm

class ReactPlugin : Plugin<Project> {
Expand Down Expand Up @@ -107,7 +109,7 @@ class ReactPlugin : Plugin<Project> {
project.configureReactTasks(variant = variant, config = extension)
}
}
configureAutolinking(project, extension)
configureAutolinking(project, extension, rootExtension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
configureBuildTypesForApp(project)
Expand Down Expand Up @@ -268,16 +270,41 @@ class ReactPlugin : Plugin<Project> {
private fun configureAutolinking(
project: Project,
extension: ReactExtension,
rootExtension: PrivateReactExtension,
) {
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 generatedPureCxxSourceDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/source/codegen/pureCxx")

// 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")
val pureCxxDependencies =
getPureCxxCodegenDependencies(rootGeneratedAutolinkingFile.get().asFile)
val pureCxxCodegenTasks =
configurePureCxxDependenciesCodegen(
project,
extension,
rootExtension,
generatedPureCxxSourceDir,
pureCxxDependencies,
)
val pureCxxCmakeListsPaths =
pureCxxDependencies
.mapNotNull { dependency ->
val libraryName = dependency.platforms?.android?.libraryName ?: return@mapNotNull null
libraryName to
generatedPureCxxSourceDir
.get()
.file("$libraryName/jni/CMakeLists.txt")
.asFile
.absolutePath
}
.toMap()

// 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
Expand Down Expand Up @@ -311,6 +338,8 @@ class ReactPlugin : Plugin<Project> {
) { task ->
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
task.generatedOutputDirectory.set(generatedAutolinkingJniDir)
task.generatedPureCxxCmakeListsPaths.set(pureCxxCmakeListsPaths)
task.dependsOn(pureCxxCodegenTasks)
}
project.tasks
.named("preBuild", Task::class.java)
Expand All @@ -333,4 +362,95 @@ class ReactPlugin : Plugin<Project> {
}
}
}

private fun configurePureCxxDependenciesCodegen(
project: Project,
extension: ReactExtension,
rootExtension: PrivateReactExtension,
generatedPureCxxSourceDir: Provider<Directory>,
dependencies: List<ModelAutolinkingDependenciesJson>,
): List<TaskProvider<GenerateCodegenArtifactsTask>> {
Comment on lines +393 to +399
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here there is a bunch of duplicated code.

Can you extract the common logic from configureCodegen into a reusable helper, then call it here for pure C++ deps instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cortinico extracted duplicated task registration code to registerCodegenTasks

return dependencies.map { dependency ->
val android = dependency.platforms?.android!!
val libraryName = android.libraryName!!
val dependencyRoot = File(dependency.root)
val packageJson = File(dependencyRoot, "package.json")
val parsedPackageJson = JsonUtils.fromPackageJson(packageJson)
val jsSrcsDir = parsedPackageJson?.codegenConfig?.jsSrcsDir
val generatedSrcDir = generatedPureCxxSourceDir.map { it.dir(libraryName) }
val taskNameSuffix = taskNameSuffixForDependency(dependency)
val generateCodegenSchemaTask =
project.tasks.register(
"generate${taskNameSuffix}CodegenSchemaFromJavaScript",
GenerateCodegenSchemaTask::class.java,
) { task ->
task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
task.codegenDir.set(rootExtension.codegenDir)
task.generatedSrcDir.set(generatedSrcDir)
task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
if (jsSrcsDir != null) {
task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDir))
} else {
task.jsRootDir.set(dependencyRoot)
}
task.jsInputFiles.set(
project.fileTree(task.jsRootDir) { tree ->
tree.include("**/*.js")
tree.include("**/*.jsx")
tree.include("**/*.ts")
tree.include("**/*.tsx")

tree.exclude("node_modules/**/*")
tree.exclude("**/*.d.ts")
tree.exclude("**/build/**/*")
}
)
}

project.tasks.register(
"generate${taskNameSuffix}CodegenArtifactsFromSchema",
GenerateCodegenArtifactsTask::class.java,
) { task ->
task.dependsOn(generateCodegenSchemaTask)
task.reactNativeDir.set(rootExtension.reactNativeDir)
task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
task.generatedSrcDir.set(generatedSrcDir)
task.packageJsonFile.set(packageJson)
val codegenJavaPackageName = parsedPackageJson?.codegenConfig?.android?.javaPackageName
if (codegenJavaPackageName != null) {
task.codegenJavaPackageName.set(codegenJavaPackageName)
} else {
task.codegenJavaPackageName.set(extension.codegenJavaPackageName)
}
task.libraryName.set(libraryName)
task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
}
}
}

internal fun getPureCxxCodegenDependencies(
autolinkingFile: File
): List<ModelAutolinkingDependenciesJson> {
val model = JsonUtils.fromAutolinkingConfigJson(autolinkingFile)
return model
?.dependencies
?.values
?.filter { dependency ->
val android = dependency.platforms?.android

if (android?.isPureCxxDependency != true || android.libraryName == null) {
return@filter false
}

val packageJson = File(dependency.root, "package.json")
val codegenConfig = JsonUtils.fromPackageJson(packageJson)?.codegenConfig
codegenConfig != null && codegenConfig.includesGeneratedCode != true
} ?: emptyList()
}

private fun taskNameSuffixForDependency(dependency: ModelAutolinkingDependenciesJson): String =
dependency.nameCleansed
.split(Regex("[^A-Za-z0-9]+"))
.filter { it.isNotEmpty() }
.joinToString("") { it.replaceFirstChar { char -> char.titlecase() } }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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.MapProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
Expand All @@ -22,12 +24,15 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() {

init {
group = "react"
generatedPureCxxCmakeListsPaths.convention(emptyMap<String, String>())
}

@get:InputFile abstract val autolinkInputFile: RegularFileProperty

@get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty

@get:Input abstract val generatedPureCxxCmakeListsPaths: MapProperty<String, String>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a MapProperty<String, String> here?

Can we replace it with a single optional DirectoryProperty pointing to the codegen output (i.e. build/generated/source/codegen/pureCxx)?

The autolinking task can derive each dep's CMake path itself: when cmakeListsPath is null and isPureCxxDependency is true, just look at {pureCxxDir}/{libraryName}/jni/CMakeLists.txt.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cortinico updated to a DirectoryProperty


@TaskAction
fun taskAction() {
val model = JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile)
Expand Down Expand Up @@ -55,7 +60,7 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() {
packages.joinToString("\n") { dep ->
var addDirectoryString = ""
val libraryName = dep.libraryName
val cmakeListsPath = dep.cmakeListsPath
val cmakeListsPath = cmakeListsPathForDependency(dep)
val cxxModuleCMakeListsPath = dep.cxxModuleCMakeListsPath
if (libraryName != null && cmakeListsPath != null) {
// If user provided a custom cmakeListsPath, let's honor it.
Expand Down Expand Up @@ -96,6 +101,12 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() {
return CMAKE_TEMPLATE.replace("{{ libraryIncludes }}", libraryIncludes)
}

private fun cmakeListsPathForDependency(
dep: ModelAutolinkingDependenciesPlatformAndroidJson
): String? {
return dep.cmakeListsPath ?: dep.libraryName?.let { generatedPureCxxCmakeListsPaths.get()[it] }
}

internal fun generateCppFileContent(
packages: List<ModelAutolinkingDependenciesPlatformAndroidJson>
): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* 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 java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.intellij.lang.annotations.Language
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class ReactPluginTest {

@get:Rule val tempFolder = TemporaryFolder()

@Test
fun getPureCxxCodegenDependencies_filtersDependenciesCorrectly() {
val includesGeneratedCode = createPackage("includes-generated-code", true)
val withoutGeneratedCode = createPackage("without-generated-code", false)
val withoutIncludesGeneratedCode = createPackage("without-includes-generated-code", null)
val withoutCodegenConfig = createPackageWithoutCodegenConfig("without-codegen-config")
val missingNonPureCxxPackage = File(tempFolder.root, "missing-non-pure-cxx-package")

val autolinkingFile =
createAutolinkingFile(
"""
{
"reactNativeVersion": "1000.0.0",
"dependencies": {
"includes-generated-code": {
"root": "${includesGeneratedCode.invariantSeparatorsPath}",
"name": "includes-generated-code",
"platforms": {
"android": {
"sourceDir": "${includesGeneratedCode.invariantSeparatorsPath}/android",
"packageImportPath": "import com.facebook.react.IncludesGeneratedCodePackage;",
"packageInstance": "new IncludesGeneratedCodePackage()",
"buildTypes": [],
"libraryName": "IncludesGeneratedCode",
"isPureCxxDependency": true
}
}
},
"without-generated-code": {
"root": "${withoutGeneratedCode.invariantSeparatorsPath}",
"name": "without-generated-code",
"platforms": {
"android": {
"sourceDir": "${withoutGeneratedCode.invariantSeparatorsPath}/android",
"packageImportPath": "import com.facebook.react.WithoutGeneratedCodePackage;",
"packageInstance": "new WithoutGeneratedCodePackage()",
"buildTypes": [],
"libraryName": "WithoutGeneratedCode",
"isPureCxxDependency": true
}
}
},
"without-includes-generated-code": {
"root": "${withoutIncludesGeneratedCode.invariantSeparatorsPath}",
"name": "without-includes-generated-code",
"platforms": {
"android": {
"sourceDir": "${withoutIncludesGeneratedCode.invariantSeparatorsPath}/android",
"packageImportPath": "import com.facebook.react.WithoutIncludesGeneratedCodePackage;",
"packageInstance": "new WithoutIncludesGeneratedCodePackage()",
"buildTypes": [],
"libraryName": "WithoutIncludesGeneratedCode",
"isPureCxxDependency": true
}
}
},
"without-codegen-config": {
"root": "${withoutCodegenConfig.invariantSeparatorsPath}",
"name": "without-codegen-config",
"platforms": {
"android": {
"sourceDir": "${withoutCodegenConfig.invariantSeparatorsPath}/android",
"packageImportPath": "import com.facebook.react.WithoutCodegenConfigPackage;",
"packageInstance": "new WithoutCodegenConfigPackage()",
"buildTypes": [],
"libraryName": "WithoutCodegenConfig",
"isPureCxxDependency": true
}
}
},
"missing-non-pure-cxx-package": {
"root": "${missingNonPureCxxPackage.invariantSeparatorsPath}",
"name": "missing-non-pure-cxx-package",
"platforms": {
"android": {
"sourceDir": "${missingNonPureCxxPackage.invariantSeparatorsPath}/android",
"packageImportPath": "import com.facebook.react.MissingNonPureCxxPackage;",
"packageInstance": "new MissingNonPureCxxPackage()",
"buildTypes": [],
"libraryName": "MissingNonPureCxxPackage",
"isPureCxxDependency": false
}
}
}
}
}
"""
.trimIndent()
)

val result = ReactPlugin().getPureCxxCodegenDependencies(autolinkingFile)

assertThat(result.map { it.name })
.containsExactly("without-generated-code", "without-includes-generated-code")
}

private fun createPackage(name: String, includesGeneratedCode: Boolean? = null): File {
val folder = tempFolder.newFolder(name)
File(folder, "package.json").writeText(packageJson(includesGeneratedCode))
return folder
}

private fun createPackageWithoutCodegenConfig(name: String): File {
val folder = tempFolder.newFolder(name)
File(folder, "package.json").writeText(packageJson())
return folder
}

private fun createAutolinkingFile(@Language("JSON") input: String) =
tempFolder.newFile("autolinking.json").apply { writeText(input) }

private fun packageJson(includesGeneratedCode: Boolean?): String {
val includesGeneratedCodeLine =
includesGeneratedCode?.let { ""","includesGeneratedCode": $it""" } ?: ""
// language=JSON
return """
{
"version": "1.0.0",
"codegenConfig": {
"name": "TestSpec",
"type": "modules",
"jsSrcsDir": "src"$includesGeneratedCodeLine
}
}
"""
.trimIndent()
}

private fun packageJson(): String {
// language=JSON
return """
{
"version": "1.0.0"
}
"""
.trimIndent()
}
}
Loading
Loading