@@ -11,6 +11,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
1111import com.android.build.api.variant.LibraryAndroidComponentsExtension
1212import com.android.build.gradle.internal.tasks.factory.dependsOn
1313import com.facebook.react.internal.PrivateReactExtension
14+ import com.facebook.react.model.ModelAutolinkingDependenciesJson
1415import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask
1516import com.facebook.react.tasks.GenerateCodegenArtifactsTask
1617import com.facebook.react.tasks.GenerateCodegenSchemaTask
@@ -38,6 +39,7 @@ import org.gradle.api.Project
3839import org.gradle.api.Task
3940import org.gradle.api.file.Directory
4041import org.gradle.api.provider.Provider
42+ import org.gradle.api.tasks.TaskProvider
4143import org.gradle.internal.jvm.Jvm
4244
4345class ReactPlugin : Plugin <Project > {
@@ -107,7 +109,7 @@ class ReactPlugin : Plugin<Project> {
107109 project.configureReactTasks(variant = variant, config = extension)
108110 }
109111 }
110- configureAutolinking(project, extension)
112+ configureAutolinking(project, extension, rootExtension )
111113 configureCodegen(project, extension, rootExtension, isLibrary = false )
112114 configureResources(project, extension)
113115 configureBuildTypesForApp(project)
@@ -173,78 +175,48 @@ class ReactPlugin : Plugin<Project> {
173175 localExtension.jsRootDir.convention(localExtension.root)
174176 }
175177
176- // We create the task to produce schema from JS files.
177- val generateCodegenSchemaTask =
178- project.tasks.register(
179- " generateCodegenSchemaFromJavaScript" ,
180- GenerateCodegenSchemaTask ::class .java,
181- ) { it ->
182- it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
183- it.codegenDir.set(rootExtension.codegenDir)
184- it.generatedSrcDir.set(generatedSrcDir)
185- it.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
186-
187- // We're reading the package.json at configuration time to properly feed
188- // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the
189- // parsePackageJson should be invoked inside this lambda.
190- val packageJson = findPackageJsonFile(project, rootExtension.root)
191- val parsedPackageJson = packageJson?.let { JsonUtils .fromPackageJson(it) }
192-
193- val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
194- val includesGeneratedCode =
195- parsedPackageJson?.codegenConfig?.includesGeneratedCode ? : false
196- if (jsSrcsDirInPackageJson != null ) {
197- it.jsRootDir.set(File (packageJson.parentFile, jsSrcsDirInPackageJson))
198- } else {
199- it.jsRootDir.set(localExtension.jsRootDir)
200- }
201- it.jsInputFiles.set(
202- project.fileTree(it.jsRootDir) { tree ->
203- tree.include(" **/*.js" )
204- tree.include(" **/*.jsx" )
205- tree.include(" **/*.ts" )
206- tree.include(" **/*.tsx" )
207-
208- tree.exclude(" node_modules/**/*" )
209- tree.exclude(" **/*.d.ts" )
210- // We want to exclude the build directory, to don't pick them up for execution
211- // avoidance.
212- tree.exclude(" **/build/**/*" )
213- }
214- )
215-
216- val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
217- it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && ! includesGeneratedCode }
218- }
219-
220- // We create the task to generate Java code from schema.
178+ // We create the tasks to produce schema from JS files and generate artifacts from schema.
221179 val generateCodegenArtifactsTask =
222- project.tasks.register(
223- " generateCodegenArtifactsFromSchema" ,
224- GenerateCodegenArtifactsTask ::class .java,
225- ) { task ->
226- task.dependsOn(generateCodegenSchemaTask)
227- task.reactNativeDir.set(rootExtension.reactNativeDir)
228- task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
229- task.generatedSrcDir.set(generatedSrcDir)
230- task.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
231- task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
232- task.libraryName.set(localExtension.libraryName)
233- task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
234-
235- // Please note that appNeedsCodegen is triggering a read of the package.json at
236- // configuration time as we need to feed the onlyIf condition of this task.
237- // Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
238- val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
239- val packageJson = findPackageJsonFile(project, rootExtension.root)
240- val parsedPackageJson = packageJson?.let { JsonUtils .fromPackageJson(it) }
241- val includesGeneratedCode =
242- parsedPackageJson?.codegenConfig?.includesGeneratedCode ? : false
243- task.onlyIf { (isLibrary || needsCodegenFromPackageJson) && ! includesGeneratedCode }
244- }
180+ registerCodegenTasks(
181+ project = project,
182+ rootExtension = rootExtension,
183+ generatedSrcDir = generatedSrcDir,
184+ packageJsonFile = { findPackageJsonFile(project, rootExtension.root) },
185+ schemaTaskName = " generateCodegenSchemaFromJavaScript" ,
186+ artifactsTaskName = " generateCodegenArtifactsFromSchema" ,
187+ configureJsRoot = { task, packageJson ->
188+ // We're reading the package.json at configuration time to properly feed
189+ // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the
190+ // parsePackageJson should be invoked inside this lambda.
191+ val parsedPackageJson = packageJson?.let { JsonUtils .fromPackageJson(it) }
192+ val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
193+
194+ if (packageJson != null && jsSrcsDirInPackageJson != null ) {
195+ task.jsRootDir.set(File (packageJson.parentFile, jsSrcsDirInPackageJson))
196+ } else {
197+ task.jsRootDir.set(localExtension.jsRootDir)
198+ }
199+ },
200+ configureCodegenArtifacts = { task, _ ->
201+ task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
202+ task.libraryName.set(localExtension.libraryName)
203+ },
204+ onlyIf = { packageJson ->
205+ // Please note that needsCodegenFromPackageJson is triggering a read of the
206+ // package.json at configuration time as we need to feed the onlyIf condition of this
207+ // task. Therefore, needsCodegenFromPackageJson needs to be invoked inside this
208+ // lambda.
209+ val needsCodegenFromPackageJson =
210+ project.needsCodegenFromPackageJson(rootExtension.root)
211+ val parsedPackageJson = packageJson?.let { JsonUtils .fromPackageJson(it) }
212+ val includesGeneratedCode =
213+ parsedPackageJson?.codegenConfig?.includesGeneratedCode ? : false
214+ (isLibrary || needsCodegenFromPackageJson) && ! includesGeneratedCode
215+ },
216+ )
245217
246218 // We update the android configuration to include the generated sources.
247- // This equivalent to this DSL:
219+ // This is equivalent to this DSL:
248220 //
249221 // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
250222 if (isLibrary) {
@@ -264,20 +236,101 @@ class ReactPlugin : Plugin<Project> {
264236 project.tasks.named(" preBuild" , Task ::class .java).dependsOn(generateCodegenArtifactsTask)
265237 }
266238
239+ private fun registerCodegenTasks (
240+ project : Project ,
241+ rootExtension : PrivateReactExtension ,
242+ generatedSrcDir : Provider <Directory >,
243+ packageJsonFile : () -> File ? ,
244+ schemaTaskName : String ,
245+ artifactsTaskName : String ,
246+ configureJsRoot : (GenerateCodegenSchemaTask , File ? ) -> Unit ,
247+ configureCodegenArtifacts : (GenerateCodegenArtifactsTask , File ? ) -> Unit ,
248+ onlyIf : (File ? ) -> Boolean = { true },
249+ ): TaskProvider <GenerateCodegenArtifactsTask > {
250+ // We create the task to produce schema from JS files.
251+ val generateCodegenSchemaTask =
252+ project.tasks.register(
253+ schemaTaskName,
254+ GenerateCodegenSchemaTask ::class .java,
255+ ) { task ->
256+ val packageJson = packageJsonFile()
257+
258+ task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
259+ task.codegenDir.set(rootExtension.codegenDir)
260+ task.generatedSrcDir.set(generatedSrcDir)
261+ task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
262+
263+ configureJsRoot(task, packageJson)
264+
265+ task.jsInputFiles.set(
266+ project.fileTree(task.jsRootDir) { tree ->
267+ tree.include(" **/*.js" )
268+ tree.include(" **/*.jsx" )
269+ tree.include(" **/*.ts" )
270+ tree.include(" **/*.tsx" )
271+
272+ tree.exclude(" node_modules/**/*" )
273+ tree.exclude(" **/*.d.ts" )
274+ // We want to exclude the build directory, to avoid picking them up for execution
275+ // avoidance.
276+ tree.exclude(" **/build/**/*" )
277+ }
278+ )
279+ val shouldRunTask = onlyIf(packageJson)
280+ task.onlyIf { shouldRunTask }
281+ }
282+
283+ // We create the task to generate Java code from schema.
284+ return project.tasks.register(
285+ artifactsTaskName,
286+ GenerateCodegenArtifactsTask ::class .java,
287+ ) { task ->
288+ val packageJson = packageJsonFile()
289+
290+ task.dependsOn(generateCodegenSchemaTask)
291+ task.reactNativeDir.set(rootExtension.reactNativeDir)
292+ task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
293+ task.generatedSrcDir.set(generatedSrcDir)
294+ task.packageJsonFile.set(packageJson)
295+ task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)
296+
297+ configureCodegenArtifacts(task, packageJson)
298+
299+ // The caller decides whether codegen should run. For app/library projects this depends on
300+ // package.json and includesGeneratedCode. Pure C++ dependencies are filtered before task
301+ // registration, so their generated tasks can always run.
302+ val shouldRunTask = onlyIf(packageJson)
303+ task.onlyIf { shouldRunTask }
304+ }
305+ }
306+
267307 /* * This function sets up Autolinking for App users */
268308 private fun configureAutolinking (
269309 project : Project ,
270310 extension : ReactExtension ,
311+ rootExtension : PrivateReactExtension ,
271312 ) {
272313 val generatedAutolinkingJavaDir: Provider <Directory > =
273314 project.layout.buildDirectory.dir(" generated/autolinking/src/main/java" )
274315 val generatedAutolinkingJniDir: Provider <Directory > =
275316 project.layout.buildDirectory.dir(" generated/autolinking/src/main/jni" )
317+ val generatedPureCxxSourceDir: Provider <Directory > =
318+ project.layout.buildDirectory.dir(" generated/source/codegen/pureCxx" )
276319
277320 // The autolinking.json file is available in the root build folder as it's generated
278321 // by ReactSettingsPlugin.kt
279322 val rootGeneratedAutolinkingFile =
280323 project.rootProject.layout.buildDirectory.file(" generated/autolinking/autolinking.json" )
324+ val pureCxxDependencies =
325+ getPureCxxCodegenDependencies(rootGeneratedAutolinkingFile.get().asFile)
326+ val pureCxxCodegenTasks =
327+ configurePureCxxDependenciesCodegen(
328+ project,
329+ extension,
330+ rootExtension,
331+ generatedPureCxxSourceDir,
332+ pureCxxDependencies,
333+ )
281334
282335 // We add a task called generateAutolinkingPackageList to do not clash with the existing task
283336 // called generatePackageList. This can to be renamed once we unlink the rn <-> cli
@@ -291,9 +344,7 @@ class ReactPlugin : Plugin<Project> {
291344 task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
292345 }
293346
294- // We add a task called generateAutolinkingPackageList to do not clash with the existing task
295- // called generatePackageList. This can to be renamed once we unlink the rn <-> cli
296- // dependency.
347+ // We add a task called generateReactNativeEntryPoint to generate the React Native entry point.
297348 val generateEntryPointTask =
298349 project.tasks.register(
299350 " generateReactNativeEntryPoint" ,
@@ -311,14 +362,19 @@ class ReactPlugin : Plugin<Project> {
311362 ) { task ->
312363 task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
313364 task.generatedOutputDirectory.set(generatedAutolinkingJniDir)
365+
366+ if (pureCxxDependencies.isNotEmpty()) {
367+ task.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDir)
368+ }
369+
370+ task.dependsOn(pureCxxCodegenTasks)
314371 }
315372 project.tasks
316373 .named(" preBuild" , Task ::class .java)
317374 .dependsOn(generateAutolinkingNewArchitectureFilesTask)
318375
319- // We let generateAutolinkingPackageList and generateEntryPoint depend on the preBuild task so
320- // it's executed before
321- // everything else.
376+ // We make preBuild depend on generateAutolinkingPackageList and generateEntryPoint so they run
377+ // before everything else.
322378 project.tasks
323379 .named(" preBuild" , Task ::class .java)
324380 .dependsOn(generatePackageListTask, generateEntryPointTask)
@@ -333,4 +389,73 @@ class ReactPlugin : Plugin<Project> {
333389 }
334390 }
335391 }
392+
393+ private fun configurePureCxxDependenciesCodegen (
394+ project : Project ,
395+ extension : ReactExtension ,
396+ rootExtension : PrivateReactExtension ,
397+ generatedPureCxxSourceDir : Provider <Directory >,
398+ dependencies : List <ModelAutolinkingDependenciesJson >,
399+ ): List <TaskProvider <GenerateCodegenArtifactsTask >> {
400+ // Pure C++ dependencies are not included as Gradle subprojects, so configureCodegen won't run
401+ // for them. The app owns these generated codegen artifacts and links them from autolinking.
402+ return dependencies.mapNotNull { dependency ->
403+ val android = dependency.platforms?.android ? : return @mapNotNull null
404+ val libraryName = android.libraryName ? : return @mapNotNull null
405+ val dependencyRoot = File (dependency.root)
406+ val packageJson = File (dependencyRoot, " package.json" )
407+ val parsedPackageJson = JsonUtils .fromPackageJson(packageJson)
408+ val jsSrcsDir = parsedPackageJson?.codegenConfig?.jsSrcsDir
409+ val generatedSrcDir = generatedPureCxxSourceDir.map { it.dir(libraryName) }
410+ val taskNameSuffix = taskNameSuffixForDependency(dependency)
411+
412+ registerCodegenTasks(
413+ project = project,
414+ rootExtension = rootExtension,
415+ generatedSrcDir = generatedSrcDir,
416+ packageJsonFile = { packageJson },
417+ schemaTaskName = " generate${taskNameSuffix} CodegenSchemaFromJavaScript" ,
418+ artifactsTaskName = " generate${taskNameSuffix} CodegenArtifactsFromSchema" ,
419+ configureJsRoot = { task, _ ->
420+ if (jsSrcsDir != null ) {
421+ task.jsRootDir.set(File (packageJson.parentFile, jsSrcsDir))
422+ } else {
423+ task.jsRootDir.set(dependencyRoot)
424+ }
425+ },
426+ configureCodegenArtifacts = { task, _ ->
427+ val codegenJavaPackageName = parsedPackageJson?.codegenConfig?.android?.javaPackageName
428+ if (codegenJavaPackageName != null ) {
429+ task.codegenJavaPackageName.set(codegenJavaPackageName)
430+ } else {
431+ task.codegenJavaPackageName.set(extension.codegenJavaPackageName)
432+ }
433+ task.libraryName.set(libraryName)
434+ },
435+ )
436+ }
437+ }
438+
439+ internal fun getPureCxxCodegenDependencies (
440+ autolinkingFile : File
441+ ): List <ModelAutolinkingDependenciesJson > {
442+ val model = JsonUtils .fromAutolinkingConfigJson(autolinkingFile)
443+ return model?.dependencies?.values?.filter { dependency ->
444+ val android = dependency.platforms?.android
445+
446+ if (android?.isPureCxxDependency != true || android.libraryName == null ) {
447+ return @filter false
448+ }
449+
450+ val packageJson = File (dependency.root, " package.json" )
451+ val codegenConfig = JsonUtils .fromPackageJson(packageJson)?.codegenConfig
452+ codegenConfig != null && codegenConfig.includesGeneratedCode != true
453+ } ? : emptyList()
454+ }
455+
456+ internal fun taskNameSuffixForDependency (dependency : ModelAutolinkingDependenciesJson ): String =
457+ dependency.name
458+ .map { char -> if (char.isLetterOrDigit()) char.toString() else " _${char.code} _" }
459+ .joinToString(" " )
460+ .replaceFirstChar { char -> char.titlecase() }
336461}
0 commit comments