From 3dc7df32640b27c6340a91be10e46bbab56ecc1d Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Fri, 22 May 2026 12:53:04 +0200 Subject: [PATCH 1/4] Handle autolinking for pure C++ turbo modules without `includesGeneratedCode` Currently, pure C++ modules require `includesGeneratedCode: true`, and the library to be shipping codegen files to work. This is inconsistent with regular turbo modules that work with both setups. The issue is in 2 places: 1. CLI returns hardcoded default for C++ modules 2. Autolinking logic doesn't run codegen for C++ modules PR removing hardcoded default from CLI: https://github.com/react-native-community/cli/pull/2799 This change updates the autolinking logic to detect pure C++ libraries without `includesGeneratedCode: true`, run codegen for them and use correct path for CMakeLists based on the codegen output. --- .../kotlin/com/facebook/react/ReactPlugin.kt | 122 +++++++++++++- ...rateAutolinkingNewArchitecturesFileTask.kt | 13 +- .../com/facebook/react/ReactPluginTest.kt | 158 ++++++++++++++++++ ...AutolinkingNewArchitecturesFileTaskTest.kt | 75 +++++++++ 4 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index f3c0982c7e0f..b5a8a5d183d9 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -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 @@ -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 { @@ -107,7 +109,7 @@ class ReactPlugin : Plugin { project.configureReactTasks(variant = variant, config = extension) } } - configureAutolinking(project, extension) + configureAutolinking(project, extension, rootExtension) configureCodegen(project, extension, rootExtension, isLibrary = false) configureResources(project, extension) configureBuildTypesForApp(project) @@ -268,16 +270,41 @@ class ReactPlugin : Plugin { private fun configureAutolinking( project: Project, extension: ReactExtension, + rootExtension: PrivateReactExtension, ) { val generatedAutolinkingJavaDir: Provider = project.layout.buildDirectory.dir("generated/autolinking/src/main/java") val generatedAutolinkingJniDir: Provider = project.layout.buildDirectory.dir("generated/autolinking/src/main/jni") + val generatedPureCxxSourceDir: Provider = + 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 @@ -311,6 +338,8 @@ class ReactPlugin : Plugin { ) { task -> task.autolinkInputFile.set(rootGeneratedAutolinkingFile) task.generatedOutputDirectory.set(generatedAutolinkingJniDir) + task.generatedPureCxxCmakeListsPaths.set(pureCxxCmakeListsPaths) + task.dependsOn(pureCxxCodegenTasks) } project.tasks .named("preBuild", Task::class.java) @@ -333,4 +362,95 @@ class ReactPlugin : Plugin { } } } + + private fun configurePureCxxDependenciesCodegen( + project: Project, + extension: ReactExtension, + rootExtension: PrivateReactExtension, + generatedPureCxxSourceDir: Provider, + dependencies: List, + ): List> { + 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 { + 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() } } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt index 0a3e311dbd98..2f576d606958 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt @@ -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 @@ -22,12 +24,15 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { init { group = "react" + generatedPureCxxCmakeListsPaths.convention(emptyMap()) } @get:InputFile abstract val autolinkInputFile: RegularFileProperty @get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty + @get:Input abstract val generatedPureCxxCmakeListsPaths: MapProperty + @TaskAction fun taskAction() { val model = JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile) @@ -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. @@ -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 ): String { diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt new file mode 100644 index 000000000000..0b8bca337daf --- /dev/null +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt @@ -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() + } +} diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt index 6db0c9ecd2c3..dccc13a7a9dd 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt @@ -41,6 +41,7 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) + assertThat(task.generatedPureCxxCmakeListsPaths.get()).isEmpty() } @Test @@ -178,6 +179,80 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { ) } + @Test + fun generateCmakeFileContent_withGeneratedPureCxxPath_usesItWhenCmakeListsPathIsMissing() { + val task = + createTestTask { + it.generatedPureCxxCmakeListsPaths.set( + mapOf("aPackage" to "./generated/pureCxx/aPackage/jni/CMakeLists.txt") + ) + } + + val output = + task.generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + ) + ) + ) + + assertThat(output) + .contains( + """ + if(EXISTS "./generated/pureCxx/aPackage/jni/") + add_subdirectory("./generated/pureCxx/aPackage/jni/" aPackage_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES react_codegen_aPackage) + else() + message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: ./generated/pureCxx/aPackage/jni/") + endif() + """ + .trimIndent() + ) + } + + @Test + fun generateCmakeFileContent_withGeneratedPureCxxPath_preservesCmakeListsPath() { + val task = + createTestTask { + it.generatedPureCxxCmakeListsPaths.set( + mapOf("aPackage" to "./generated/pureCxx/aPackage/jni/CMakeLists.txt") + ) + } + + val output = + task.generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + cmakeListsPath = "./a/directory/CMakeLists.txt", + ) + ) + ) + + assertThat(output) + .contains( + """ + if(EXISTS "./a/directory/") + add_subdirectory("./a/directory/" aPackage_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES react_codegen_aPackage) + else() + message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: ./a/directory/") + endif() + """ + .trimIndent() + ) + assertThat(output).doesNotContain("./generated/pureCxx/aPackage/jni/") + } + @Test fun generateCppFileContent_withNoPackages_returnsEmpty() { val output = From 1899f344db2fbcf847e03d4791d45727ce7e4a46 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Fri, 22 May 2026 19:36:36 +0200 Subject: [PATCH 2/4] Extract task registration and other simplification --- .../kotlin/com/facebook/react/ReactPlugin.kt | 252 +++++++++--------- ...rateAutolinkingNewArchitecturesFileTask.kt | 28 +- ...AutolinkingNewArchitecturesFileTaskTest.kt | 75 +++++- 3 files changed, 211 insertions(+), 144 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index b5a8a5d183d9..5fa68c617a85 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -175,75 +175,38 @@ class ReactPlugin : Plugin { localExtension.jsRootDir.convention(localExtension.root) } - // We create the task to produce schema from JS files. - val generateCodegenSchemaTask = - project.tasks.register( - "generateCodegenSchemaFromJavaScript", - GenerateCodegenSchemaTask::class.java, - ) { it -> - it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) - it.codegenDir.set(rootExtension.codegenDir) - it.generatedSrcDir.set(generatedSrcDir) - it.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) - - // We're reading the package.json at configuration time to properly feed - // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the - // parsePackageJson should be invoked inside this lambda. - val packageJson = findPackageJsonFile(project, rootExtension.root) - val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } - - val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir - val includesGeneratedCode = - parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false - if (jsSrcsDirInPackageJson != null) { - it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson)) - } else { - it.jsRootDir.set(localExtension.jsRootDir) - } - it.jsInputFiles.set( - project.fileTree(it.jsRootDir) { tree -> - tree.include("**/*.js") - tree.include("**/*.jsx") - tree.include("**/*.ts") - tree.include("**/*.tsx") + // We're reading the package.json at configuration time to properly feed + // the `jsRootDir` @Input property of the schema task & the onlyIf for both codegen tasks. + val packageJson = findPackageJsonFile(project, rootExtension.root) + val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } - tree.exclude("node_modules/**/*") - tree.exclude("**/*.d.ts") - // We want to exclude the build directory, to don't pick them up for execution - // avoidance. - tree.exclude("**/build/**/*") - } - ) - - val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) - it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode } - } + val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir + val includesGeneratedCode = parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false + val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) + val shouldRunCodegen = { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode } - // We create the task to generate Java code from schema. + // We create the tasks to produce schema from JS files and generate artifacts from schema. val generateCodegenArtifactsTask = - project.tasks.register( - "generateCodegenArtifactsFromSchema", - GenerateCodegenArtifactsTask::class.java, - ) { task -> - task.dependsOn(generateCodegenSchemaTask) - task.reactNativeDir.set(rootExtension.reactNativeDir) - task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) - task.generatedSrcDir.set(generatedSrcDir) - task.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root)) - task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName) - task.libraryName.set(localExtension.libraryName) - task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) - - // Please note that appNeedsCodegen is triggering a read of the package.json at - // configuration time as we need to feed the onlyIf condition of this task. - // Therefore, the appNeedsCodegen needs to be invoked inside this lambda. - val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) - val packageJson = findPackageJsonFile(project, rootExtension.root) - val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } - val includesGeneratedCode = - parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false - task.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode } - } + registerCodegenTasks( + project = project, + rootExtension = rootExtension, + generatedSrcDir = generatedSrcDir, + packageJsonFile = packageJson, + schemaTaskName = "generateCodegenSchemaFromJavaScript", + artifactsTaskName = "generateCodegenArtifactsFromSchema", + configureJsRoot = { task -> + if (jsSrcsDirInPackageJson != null) { + task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson)) + } else { + task.jsRootDir.set(localExtension.jsRootDir) + } + }, + configureCodegenArtifacts = { task -> + task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName) + task.libraryName.set(localExtension.libraryName) + }, + onlyIf = shouldRunCodegen, + ) // We update the android configuration to include the generated sources. // This equivalent to this DSL: @@ -266,6 +229,68 @@ class ReactPlugin : Plugin { project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) } + private fun registerCodegenTasks( + project: Project, + rootExtension: PrivateReactExtension, + generatedSrcDir: Provider, + packageJsonFile: File?, + schemaTaskName: String, + artifactsTaskName: String, + configureJsRoot: (GenerateCodegenSchemaTask) -> Unit, + configureCodegenArtifacts: (GenerateCodegenArtifactsTask) -> Unit, + onlyIf: () -> Boolean = { true }, + ): TaskProvider { + // We create the task to produce schema from JS files. + val generateCodegenSchemaTask = + project.tasks.register( + schemaTaskName, + 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) + + configureJsRoot(task) + + 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") + // We want to exclude the build directory, to don't pick them up for execution + // avoidance. + tree.exclude("**/build/**/*") + } + ) + task.onlyIf { onlyIf() } + } + + // We create the task to generate Java code from schema. + return project.tasks.register( + artifactsTaskName, + GenerateCodegenArtifactsTask::class.java, + ) { task -> + task.dependsOn(generateCodegenSchemaTask) + task.reactNativeDir.set(rootExtension.reactNativeDir) + task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) + task.generatedSrcDir.set(generatedSrcDir) + task.packageJsonFile.set(packageJsonFile) + task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) + + configureCodegenArtifacts(task) + + // The caller decides whether codegen should run. For app/library projects this depends on + // package.json and includesGeneratedCode. Pure C++ dependencies are filtered before task + // registration, so their generated tasks can always run. + task.onlyIf { onlyIf() } + } + } + /** This function sets up Autolinking for App users */ private fun configureAutolinking( project: Project, @@ -293,18 +318,6 @@ class ReactPlugin : Plugin { 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 @@ -338,7 +351,11 @@ class ReactPlugin : Plugin { ) { task -> task.autolinkInputFile.set(rootGeneratedAutolinkingFile) task.generatedOutputDirectory.set(generatedAutolinkingJniDir) - task.generatedPureCxxCmakeListsPaths.set(pureCxxCmakeListsPaths) + + if (pureCxxDependencies.isNotEmpty()) { + task.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDir) + } + task.dependsOn(pureCxxCodegenTasks) } project.tasks @@ -370,61 +387,42 @@ class ReactPlugin : Plugin { generatedPureCxxSourceDir: Provider, dependencies: List, ): List> { - return dependencies.map { dependency -> - val android = dependency.platforms?.android!! - val libraryName = android.libraryName!! + // Pure C++ dependencies are not included as Gradle subprojects, so configureCodegen won't run + // for them. The app owns these generated codegen artifacts and links them from autolinking. + return dependencies.mapNotNull { dependency -> + val android = dependency.platforms?.android ?: return@mapNotNull null + val libraryName = android.libraryName ?: return@mapNotNull null 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) + + registerCodegenTasks( + project = project, + rootExtension = rootExtension, + generatedSrcDir = generatedSrcDir, + packageJsonFile = packageJson, + schemaTaskName = "generate${taskNameSuffix}CodegenSchemaFromJavaScript", + artifactsTaskName = "generate${taskNameSuffix}CodegenArtifactsFromSchema", + configureJsRoot = { task -> 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) - } + }, + configureCodegenArtifacts = { task -> + val codegenJavaPackageName = parsedPackageJson?.codegenConfig?.android?.javaPackageName + if (codegenJavaPackageName != null) { + task.codegenJavaPackageName.set(codegenJavaPackageName) + } else { + task.codegenJavaPackageName.set(extension.codegenJavaPackageName) + } + task.libraryName.set(libraryName) + }, + ) } } @@ -449,8 +447,8 @@ class ReactPlugin : Plugin { } private fun taskNameSuffixForDependency(dependency: ModelAutolinkingDependenciesJson): String = - dependency.nameCleansed - .split(Regex("[^A-Za-z0-9]+")) - .filter { it.isNotEmpty() } - .joinToString("") { it.replaceFirstChar { char -> char.titlecase() } } + dependency.name + .map { char -> if (char.isLetterOrDigit()) char.toString() else "_${char.code}_" } + .joinToString("") + .replaceFirstChar { char -> char.titlecase() } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt index 2f576d606958..1b8954229385 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt @@ -14,9 +14,9 @@ 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.InputDirectory import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction @@ -24,14 +24,13 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { init { group = "react" - generatedPureCxxCmakeListsPaths.convention(emptyMap()) } @get:InputFile abstract val autolinkInputFile: RegularFileProperty @get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty - @get:Input abstract val generatedPureCxxCmakeListsPaths: MapProperty + @get:Optional @get:InputDirectory abstract val generatedPureCxxSourceDirectory: DirectoryProperty @TaskAction fun taskAction() { @@ -63,7 +62,8 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { val cmakeListsPath = cmakeListsPathForDependency(dep) val cxxModuleCMakeListsPath = dep.cxxModuleCMakeListsPath if (libraryName != null && cmakeListsPath != null) { - // If user provided a custom cmakeListsPath, let's honor it. + // If user provided a custom cmakeListsPath, let's honor it. Otherwise, pure C++ + // dependencies use the app-owned generated codegen directory. val nativeFolderPath = sanitizeCmakeListsPath(cmakeListsPath) addDirectoryString += """ @@ -104,7 +104,23 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { private fun cmakeListsPathForDependency( dep: ModelAutolinkingDependenciesPlatformAndroidJson ): String? { - return dep.cmakeListsPath ?: dep.libraryName?.let { generatedPureCxxCmakeListsPaths.get()[it] } + if (dep.cmakeListsPath != null) { + return dep.cmakeListsPath + } + + if ( + dep.isPureCxxDependency != true || + dep.libraryName == null || + !generatedPureCxxSourceDirectory.isPresent + ) { + return null + } + + return generatedPureCxxSourceDirectory + .get() + .file("${dep.libraryName}/jni/CMakeLists.txt") + .asFile + .absolutePath } internal fun generateCppFileContent( diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt index dccc13a7a9dd..36034f081069 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt @@ -13,6 +13,7 @@ import com.facebook.react.model.ModelAutolinkingDependenciesPlatformAndroidJson import com.facebook.react.model.ModelAutolinkingDependenciesPlatformJson import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask.Companion.sanitizeCmakeListsPath import com.facebook.react.tests.createTestTask +import java.io.File import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test @@ -41,7 +42,7 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) - assertThat(task.generatedPureCxxCmakeListsPaths.get()).isEmpty() + assertThat(task.generatedPureCxxSourceDirectory.isPresent).isFalse() } @Test @@ -181,11 +182,14 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { @Test fun generateCmakeFileContent_withGeneratedPureCxxPath_usesItWhenCmakeListsPathIsMissing() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + val generatedCmakeListsPath = + File(generatedPureCxxSourceDirectory, "aPackage/jni/CMakeLists.txt").absolutePath + val generatedNativeFolderPath = sanitizeCmakeListsPath(generatedCmakeListsPath) + val task = createTestTask { - it.generatedPureCxxCmakeListsPaths.set( - mapOf("aPackage" to "./generated/pureCxx/aPackage/jni/CMakeLists.txt") - ) + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) } val output = @@ -197,6 +201,7 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { packageInstance = "new APackage()", buildTypes = emptyList(), libraryName = "aPackage", + isPureCxxDependency = true, ) ) ) @@ -204,11 +209,11 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { assertThat(output) .contains( """ - if(EXISTS "./generated/pureCxx/aPackage/jni/") - add_subdirectory("./generated/pureCxx/aPackage/jni/" aPackage_autolinked_build) + if(EXISTS "$generatedNativeFolderPath") + add_subdirectory("$generatedNativeFolderPath" aPackage_autolinked_build) list(APPEND AUTOLINKED_LIBRARIES react_codegen_aPackage) else() - message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: ./generated/pureCxx/aPackage/jni/") + message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: $generatedNativeFolderPath") endif() """ .trimIndent() @@ -217,11 +222,11 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { @Test fun generateCmakeFileContent_withGeneratedPureCxxPath_preservesCmakeListsPath() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + val task = createTestTask { - it.generatedPureCxxCmakeListsPaths.set( - mapOf("aPackage" to "./generated/pureCxx/aPackage/jni/CMakeLists.txt") - ) + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) } val output = @@ -234,6 +239,7 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { buildTypes = emptyList(), libraryName = "aPackage", cmakeListsPath = "./a/directory/CMakeLists.txt", + isPureCxxDependency = true, ) ) ) @@ -250,7 +256,54 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { """ .trimIndent() ) - assertThat(output).doesNotContain("./generated/pureCxx/aPackage/jni/") + assertThat(output).doesNotContain(generatedPureCxxSourceDirectory.absolutePath) + } + + @Test + fun generateCmakeFileContent_withGeneratedPureCxxPath_ignoresNonPureCxxDependency() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + + val task = + createTestTask { + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) + } + + val output = + task.generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = false, + ) + ) + ) + + assertThat(output).doesNotContain("aPackage_autolinked_build") + assertThat(output).doesNotContain(generatedPureCxxSourceDirectory.absolutePath) + } + + @Test + fun generateCmakeFileContent_withPureCxxDependencyAndMissingGeneratedDirectory_skipsIt() { + val output = + createTestTask() + .generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = true, + ) + ) + ) + + assertThat(output).doesNotContain("aPackage_autolinked_build") } @Test From 0cfd8d965b0e7e923cbed16b83c5640ec7f5e562 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Fri, 22 May 2026 20:15:17 +0200 Subject: [PATCH 3/4] Fix lazy package.json resolution for app codegen --- .../kotlin/com/facebook/react/ReactPlugin.kt | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 5fa68c617a85..9e77a4888447 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -175,37 +175,43 @@ class ReactPlugin : Plugin { localExtension.jsRootDir.convention(localExtension.root) } - // We're reading the package.json at configuration time to properly feed - // the `jsRootDir` @Input property of the schema task & the onlyIf for both codegen tasks. - val packageJson = findPackageJsonFile(project, rootExtension.root) - val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } - - val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir - val includesGeneratedCode = parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false - val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) - val shouldRunCodegen = { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode } - // We create the tasks to produce schema from JS files and generate artifacts from schema. val generateCodegenArtifactsTask = registerCodegenTasks( project = project, rootExtension = rootExtension, generatedSrcDir = generatedSrcDir, - packageJsonFile = packageJson, + packageJsonFile = { findPackageJsonFile(project, rootExtension.root) }, schemaTaskName = "generateCodegenSchemaFromJavaScript", artifactsTaskName = "generateCodegenArtifactsFromSchema", - configureJsRoot = { task -> - if (jsSrcsDirInPackageJson != null) { + configureJsRoot = { task, packageJson -> + // We're reading the package.json at configuration time to properly feed + // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the + // parsePackageJson should be invoked inside this lambda. + val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } + val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir + + if (packageJson != null && jsSrcsDirInPackageJson != null) { task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson)) } else { task.jsRootDir.set(localExtension.jsRootDir) } }, - configureCodegenArtifacts = { task -> + configureCodegenArtifacts = { task, _ -> task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName) task.libraryName.set(localExtension.libraryName) }, - onlyIf = shouldRunCodegen, + onlyIf = { packageJson -> + // Please note that appNeedsCodegen is triggering a read of the package.json at + // configuration time as we need to feed the onlyIf condition of this task. + // Therefore, the appNeedsCodegen needs to be invoked inside this lambda. + val needsCodegenFromPackageJson = + project.needsCodegenFromPackageJson(rootExtension.root) + val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } + val includesGeneratedCode = + parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false + (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode + }, ) // We update the android configuration to include the generated sources. @@ -233,12 +239,12 @@ class ReactPlugin : Plugin { project: Project, rootExtension: PrivateReactExtension, generatedSrcDir: Provider, - packageJsonFile: File?, + packageJsonFile: () -> File?, schemaTaskName: String, artifactsTaskName: String, - configureJsRoot: (GenerateCodegenSchemaTask) -> Unit, - configureCodegenArtifacts: (GenerateCodegenArtifactsTask) -> Unit, - onlyIf: () -> Boolean = { true }, + configureJsRoot: (GenerateCodegenSchemaTask, File?) -> Unit, + configureCodegenArtifacts: (GenerateCodegenArtifactsTask, File?) -> Unit, + onlyIf: (File?) -> Boolean = { true }, ): TaskProvider { // We create the task to produce schema from JS files. val generateCodegenSchemaTask = @@ -246,12 +252,14 @@ class ReactPlugin : Plugin { schemaTaskName, GenerateCodegenSchemaTask::class.java, ) { task -> + val packageJson = packageJsonFile() + task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) task.codegenDir.set(rootExtension.codegenDir) task.generatedSrcDir.set(generatedSrcDir) task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) - configureJsRoot(task) + configureJsRoot(task, packageJson) task.jsInputFiles.set( project.fileTree(task.jsRootDir) { tree -> @@ -267,7 +275,8 @@ class ReactPlugin : Plugin { tree.exclude("**/build/**/*") } ) - task.onlyIf { onlyIf() } + val shouldRunTask = onlyIf(packageJson) + task.onlyIf { shouldRunTask } } // We create the task to generate Java code from schema. @@ -275,19 +284,22 @@ class ReactPlugin : Plugin { artifactsTaskName, GenerateCodegenArtifactsTask::class.java, ) { task -> + val packageJson = packageJsonFile() + task.dependsOn(generateCodegenSchemaTask) task.reactNativeDir.set(rootExtension.reactNativeDir) task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) task.generatedSrcDir.set(generatedSrcDir) - task.packageJsonFile.set(packageJsonFile) + task.packageJsonFile.set(packageJson) task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) - configureCodegenArtifacts(task) + configureCodegenArtifacts(task, packageJson) // The caller decides whether codegen should run. For app/library projects this depends on // package.json and includesGeneratedCode. Pure C++ dependencies are filtered before task // registration, so their generated tasks can always run. - task.onlyIf { onlyIf() } + val shouldRunTask = onlyIf(packageJson) + task.onlyIf { shouldRunTask } } } @@ -403,17 +415,17 @@ class ReactPlugin : Plugin { project = project, rootExtension = rootExtension, generatedSrcDir = generatedSrcDir, - packageJsonFile = packageJson, + packageJsonFile = { packageJson }, schemaTaskName = "generate${taskNameSuffix}CodegenSchemaFromJavaScript", artifactsTaskName = "generate${taskNameSuffix}CodegenArtifactsFromSchema", - configureJsRoot = { task -> + configureJsRoot = { task, _ -> if (jsSrcsDir != null) { task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDir)) } else { task.jsRootDir.set(dependencyRoot) } }, - configureCodegenArtifacts = { task -> + configureCodegenArtifacts = { task, _ -> val codegenJavaPackageName = parsedPackageJson?.codegenConfig?.android?.javaPackageName if (codegenJavaPackageName != null) { task.codegenJavaPackageName.set(codegenJavaPackageName) From bacd360cbe764285308ee94c65323a7de7fe6812 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Fri, 22 May 2026 20:18:38 +0200 Subject: [PATCH 4/4] Update comments --- .../kotlin/com/facebook/react/ReactPlugin.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 9e77a4888447..6ac71449e475 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -202,9 +202,10 @@ class ReactPlugin : Plugin { task.libraryName.set(localExtension.libraryName) }, onlyIf = { packageJson -> - // Please note that appNeedsCodegen is triggering a read of the package.json at - // configuration time as we need to feed the onlyIf condition of this task. - // Therefore, the appNeedsCodegen needs to be invoked inside this lambda. + // Please note that needsCodegenFromPackageJson is triggering a read of the + // package.json at configuration time as we need to feed the onlyIf condition of this + // task. Therefore, needsCodegenFromPackageJson needs to be invoked inside this + // lambda. val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } @@ -215,7 +216,7 @@ class ReactPlugin : Plugin { ) // We update the android configuration to include the generated sources. - // This equivalent to this DSL: + // This is equivalent to this DSL: // // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } } if (isLibrary) { @@ -270,7 +271,7 @@ class ReactPlugin : Plugin { tree.exclude("node_modules/**/*") tree.exclude("**/*.d.ts") - // We want to exclude the build directory, to don't pick them up for execution + // We want to exclude the build directory, to avoid picking them up for execution // avoidance. tree.exclude("**/build/**/*") } @@ -331,9 +332,7 @@ class ReactPlugin : Plugin { pureCxxDependencies, ) - // 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. + // We add a task called generateReactNativeEntryPoint to generate the React Native entry point. val generatePackageListTask = project.tasks.register( "generateAutolinkingPackageList", @@ -374,9 +373,8 @@ class ReactPlugin : Plugin { .named("preBuild", Task::class.java) .dependsOn(generateAutolinkingNewArchitectureFilesTask) - // We let generateAutolinkingPackageList and generateEntryPoint depend on the preBuild task so - // it's executed before - // everything else. + // We make preBuild depend on generateAutolinkingPackageList and generateEntryPoint so they run + // before everything else. project.tasks .named("preBuild", Task::class.java) .dependsOn(generatePackageListTask, generateEntryPointTask)