Skip to content

Commit

Permalink
RNGP - Move generateAutolinkingPackageList to core autolinking
Browse files Browse the repository at this point in the history
Summary:
This diff is part of RFC0759
react-native-community/discussions-and-proposals#759

Here I'm moving over the task to generate the Package List for Autolinking inside RNGP.
The logic is the same as this one:
https://github.com/react-native-community/cli/blob/73f880c3d87cdde81204364289f2f488a473c52b/packages/cli-platform-android/native_modules.gradle#L217

The class is generated as PackageList2 to avoid a duplicate class build failure with the current Autolinking from CLI.

Changelog:
[Internal] [Changed] - RNGP - Move generateAutolinkingPackageList to core autolinking

Reviewed By: cipolleschi

Differential Revision: D56637394

fbshipit-source-id: 929b42af3a0e1951cb7a0f4ace47bbbb84000780
  • Loading branch information
cortinico authored and kosmydel committed Jun 11, 2024
1 parent 7ae32cb commit 8672d36
Show file tree
Hide file tree
Showing 5 changed files with 525 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.tasks.RunAutolinkingConfigTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
Expand Down Expand Up @@ -219,13 +220,41 @@ class ReactPlugin : Plugin<Project> {
) {
val generatedAutolinkingDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/autolinking")
val generatedAutolinkingJavaDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/autolinking/src/main/java")
val configOutputFile = generatedAutolinkingDir.get().file("config-output.json")

project.tasks.register("runAutolinkingConfig", RunAutolinkingConfigTask::class.java) { task ->
task.autolinkConfigCommand.set(extension.autolinkConfigCommand)
task.autolinkConfigFile.set(extension.autolinkConfigFile)
task.autolinkOutputFile.set(configOutputFile)
task.autolinkLockFiles.set(extension.autolinkLockFiles)
val runAutolinkingConfigTask =
project.tasks.register("runAutolinkingConfig", RunAutolinkingConfigTask::class.java) { task
->
task.autolinkConfigCommand.set(extension.autolinkConfigCommand)
task.autolinkConfigFile.set(extension.autolinkConfigFile)
task.autolinkOutputFile.set(configOutputFile)
task.autolinkLockFiles.set(extension.autolinkLockFiles)
}

// 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.
val generatePackageListTask =
project.tasks.register(
"generateAutolinkingPackageList", GeneratePackageListTask::class.java) { task ->
task.dependsOn(runAutolinkingConfigTask)
task.autolinkInputFile.set(configOutputFile)
task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
}

// We let generateAutolinkingPackageList depend on the preBuild task so it's executed before
// everything else.
project.tasks.named("preBuild", Task::class.java).dependsOn(generatePackageListTask)

// We tell Android Gradle Plugin that inside /build/generated/autolinking/src/main/java there
// are sources to be compiled as well.
project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
onVariants(selector().all()) { variant ->
variant.sources.java?.addStaticSourceDirectory(
generatedAutolinkingJavaDir.get().asFile.absolutePath)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ data class ModelAutolinkingDependenciesPlatformAndroidJson(
val libraryName: String,
val componentDescriptors: List<String>,
val cmakeListsPath: String,
val cxxModuleCMakeListsModuleName: String?,
val cxxModuleCMakeListsPath: String?,
val cxxModuleHeaderName: String?,
val dependencyConfiguration: String?
val cxxModuleCMakeListsModuleName: String? = null,
val cxxModuleCMakeListsPath: String? = null,
val cxxModuleHeaderName: String? = null,
val dependencyConfiguration: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* 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.tasks

import com.facebook.react.model.ModelAutolinkingDependenciesJson
import com.facebook.react.utils.JsonUtils
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction

abstract class GeneratePackageListTask : DefaultTask() {

init {
group = "react"
}

@get:InputFile abstract val autolinkInputFile: RegularFileProperty

@get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty

@TaskAction
fun taskAction() {
val model = JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile)

val packageName =
model?.project?.android?.packageName
?: error(
"RNGP - Autolinking: Could not find project.android.packageName in react-native config output! Could not autolink packages without this field.")
val packages = model.dependencies?.values ?: emptyList()

val packageImports = composePackageImports(packageName, packages)
val packageClassInstance = composePackageInstance(packageName, packages)
val generatedFileContents = composeFileContent(packageImports, packageClassInstance)

val outputDir = generatedOutputDirectory.get().asFile
outputDir.mkdirs()
File(outputDir, GENERATED_FILENAME).apply {
parentFile.mkdirs()
writeText(generatedFileContents)
}
}

internal fun composePackageImports(
packageName: String,
packages: Collection<ModelAutolinkingDependenciesJson>
) =
packages.joinToString("\n") { entry ->
val packageImportPath =
requireNotNull(entry.platforms?.android?.packageImportPath) {
"RNGP - Autolinking: Missing `packageImportPath` in `config` for dependency ${entry.name}. This is required to generate the autolinking package list."
}
"// ${entry.name}\n${interpolateDynamicValues(packageImportPath, packageName)}"
}

internal fun composePackageInstance(
packageName: String,
packages: Collection<ModelAutolinkingDependenciesJson>
) =
if (packages.isEmpty()) {
""
} else {
",\n " +
packages.joinToString(",\n ") { entry ->
val packageInstance =
requireNotNull(entry.platforms?.android?.packageInstance) {
"RNGP - Autolinking: Missing `packageInstance` in `config` for dependency ${entry.name}. This is required to generate the autolinking package list."
}
interpolateDynamicValues(packageInstance, packageName)
}
}

internal fun composeFileContent(packageImports: String, packageClassInstance: String): String =
generatedFileContentsTemplate
.replace("{{ packageImports }}", packageImports)
.replace("{{ packageClassInstances }}", packageClassInstance)

companion object {
const val GENERATED_FILENAME = "com/facebook/react/PackageList2.java"

/**
* Before adding the package replacement mechanism, BuildConfig and R classes were imported
* automatically into the scope of the file. We want to replace all non-FQDN references to those
* classes with the package name of the MainApplication.
*
* We want to match "R" or "BuildConfig":
* - new Package(R.string…),
* - Module.configure(BuildConfig);
* ^ hence including (BuildConfig|R)
* but we don't want to match "R":
* - new Package(getResources…),
* - new PackageR…,
* - new Royal…,
* ^ hence excluding \w before and after matches
* and "BuildConfig" that has FQDN reference:
* - Module.configure(com.acme.BuildConfig);
* ^ hence excluding . before the match.
*/
internal fun interpolateDynamicValues(input: String, packageName: String): String =
input.replace(Regex("([^.\\w])(BuildConfig|R)(\\W)")) { match ->
val (prefix, className, suffix) = match.destructured
"${prefix}${packageName}.${className}${suffix}"
}

// language=java
val generatedFileContentsTemplate =
"""
package com.facebook.react;
import android.app.Application;
import android.content.Context;
import android.content.res.Resources;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainPackageConfig;
import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays;
import java.util.ArrayList;
{{ packageImports }}
public class PackageList2 {
private Application application;
private ReactNativeHost reactNativeHost;
private MainPackageConfig mConfig;
public PackageList(ReactNativeHost reactNativeHost) {
this(reactNativeHost, null);
}
public PackageList(Application application) {
this(application, null);
}
public PackageList(ReactNativeHost reactNativeHost, MainPackageConfig config) {
this.reactNativeHost = reactNativeHost;
mConfig = config;
}
public PackageList(Application application, MainPackageConfig config) {
this.reactNativeHost = null;
this.application = application;
mConfig = config;
}
private ReactNativeHost getReactNativeHost() {
return this.reactNativeHost;
}
private Resources getResources() {
return this.getApplication().getResources();
}
private Application getApplication() {
if (this.reactNativeHost == null) return this.application;
return this.reactNativeHost.getApplication();
}
private Context getApplicationContext() {
return this.getApplication().getApplicationContext();
}
public ArrayList<ReactPackage> getPackages() {
return new ArrayList<>(Arrays.<ReactPackage>asList(
new MainReactPackage(mConfig){{ packageClassInstances }}
));
}
}
"""
.trimIndent()
}
}
Loading

0 comments on commit 8672d36

Please sign in to comment.