-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Handle autolinking for pure C++ turbo modules without includesGeneratedCode
#56938
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
satya164
wants to merge
4
commits into
facebook:main
Choose a base branch
from
satya164:@satya164/fix-cpp-autolinking
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3dc7df3
Handle autolinking for pure C++ turbo modules without `includesGenera…
satya164 1899f34
Extract task registration and other simplification
satya164 0cfd8d9
Fix lazy package.json resolution for app codegen
satya164 bacd360
Update comments
satya164 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<Project> { | ||
|
|
@@ -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) | ||
|
|
@@ -173,78 +175,48 @@ class ReactPlugin : Plugin<Project> { | |
| 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") | ||
|
|
||
| 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 } | ||
| } | ||
|
|
||
| // 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 = { findPackageJsonFile(project, rootExtension.root) }, | ||
| schemaTaskName = "generateCodegenSchemaFromJavaScript", | ||
| artifactsTaskName = "generateCodegenArtifactsFromSchema", | ||
| 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, _ -> | ||
| task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName) | ||
| task.libraryName.set(localExtension.libraryName) | ||
| }, | ||
| onlyIf = { packageJson -> | ||
| // 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) } | ||
| val includesGeneratedCode = | ||
| parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false | ||
| (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode | ||
| }, | ||
| ) | ||
|
|
||
| // 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) { | ||
|
|
@@ -264,24 +236,103 @@ class ReactPlugin : Plugin<Project> { | |
| project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) | ||
| } | ||
|
|
||
| private fun registerCodegenTasks( | ||
| project: Project, | ||
| rootExtension: PrivateReactExtension, | ||
| generatedSrcDir: Provider<Directory>, | ||
| packageJsonFile: () -> File?, | ||
| schemaTaskName: String, | ||
| artifactsTaskName: String, | ||
| configureJsRoot: (GenerateCodegenSchemaTask, File?) -> Unit, | ||
| configureCodegenArtifacts: (GenerateCodegenArtifactsTask, File?) -> Unit, | ||
| onlyIf: (File?) -> Boolean = { true }, | ||
| ): TaskProvider<GenerateCodegenArtifactsTask> { | ||
| // We create the task to produce schema from JS files. | ||
| val generateCodegenSchemaTask = | ||
| project.tasks.register( | ||
| 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, packageJson) | ||
|
|
||
| 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 avoid picking them up for execution | ||
| // avoidance. | ||
| tree.exclude("**/build/**/*") | ||
| } | ||
| ) | ||
| val shouldRunTask = onlyIf(packageJson) | ||
| task.onlyIf { shouldRunTask } | ||
| } | ||
|
|
||
| // We create the task to generate Java code from schema. | ||
| return project.tasks.register( | ||
| 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(packageJson) | ||
| task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) | ||
|
|
||
| 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. | ||
| val shouldRunTask = onlyIf(packageJson) | ||
| task.onlyIf { shouldRunTask } | ||
| } | ||
| } | ||
|
|
||
| /** This function sets up Autolinking for App users */ | ||
| 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, | ||
| ) | ||
|
|
||
| // 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", | ||
|
|
@@ -311,14 +362,19 @@ class ReactPlugin : Plugin<Project> { | |
| ) { task -> | ||
| task.autolinkInputFile.set(rootGeneratedAutolinkingFile) | ||
| task.generatedOutputDirectory.set(generatedAutolinkingJniDir) | ||
|
|
||
| if (pureCxxDependencies.isNotEmpty()) { | ||
| task.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDir) | ||
| } | ||
|
|
||
| task.dependsOn(pureCxxCodegenTasks) | ||
| } | ||
| project.tasks | ||
| .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) | ||
|
|
@@ -333,4 +389,76 @@ class ReactPlugin : Plugin<Project> { | |
| } | ||
| } | ||
| } | ||
|
|
||
| private fun configurePureCxxDependenciesCodegen( | ||
| project: Project, | ||
| extension: ReactExtension, | ||
| rootExtension: PrivateReactExtension, | ||
| generatedPureCxxSourceDir: Provider<Directory>, | ||
| dependencies: List<ModelAutolinkingDependenciesJson>, | ||
| ): List<TaskProvider<GenerateCodegenArtifactsTask>> { | ||
| // 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) | ||
|
|
||
| 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) | ||
| } | ||
| }, | ||
| 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) | ||
| }, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| 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 = | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also write unit tests for this function please? |
||
| dependency.name | ||
| .map { char -> if (char.isLetterOrDigit()) char.toString() else "_${char.code}_" } | ||
| .joinToString("") | ||
| .replaceFirstChar { char -> char.titlecase() } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
configureCodegeninto a reusable helper, then call it here for pure C++ deps instead?There was a problem hiding this comment.
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