Skip to content

Commit 8672d36

Browse files
cortinicokosmydel
authored andcommitted
RNGP - Move generateAutolinkingPackageList to core autolinking
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
1 parent 7ae32cb commit 8672d36

File tree

5 files changed

+525
-10
lines changed

5 files changed

+525
-10
lines changed

packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn
1212
import com.facebook.react.internal.PrivateReactExtension
1313
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
1414
import com.facebook.react.tasks.GenerateCodegenSchemaTask
15+
import com.facebook.react.tasks.GeneratePackageListTask
1516
import com.facebook.react.tasks.RunAutolinkingConfigTask
1617
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
1718
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
@@ -219,13 +220,41 @@ class ReactPlugin : Plugin<Project> {
219220
) {
220221
val generatedAutolinkingDir: Provider<Directory> =
221222
project.layout.buildDirectory.dir("generated/autolinking")
223+
val generatedAutolinkingJavaDir: Provider<Directory> =
224+
project.layout.buildDirectory.dir("generated/autolinking/src/main/java")
222225
val configOutputFile = generatedAutolinkingDir.get().file("config-output.json")
223226

224-
project.tasks.register("runAutolinkingConfig", RunAutolinkingConfigTask::class.java) { task ->
225-
task.autolinkConfigCommand.set(extension.autolinkConfigCommand)
226-
task.autolinkConfigFile.set(extension.autolinkConfigFile)
227-
task.autolinkOutputFile.set(configOutputFile)
228-
task.autolinkLockFiles.set(extension.autolinkLockFiles)
227+
val runAutolinkingConfigTask =
228+
project.tasks.register("runAutolinkingConfig", RunAutolinkingConfigTask::class.java) { task
229+
->
230+
task.autolinkConfigCommand.set(extension.autolinkConfigCommand)
231+
task.autolinkConfigFile.set(extension.autolinkConfigFile)
232+
task.autolinkOutputFile.set(configOutputFile)
233+
task.autolinkLockFiles.set(extension.autolinkLockFiles)
234+
}
235+
236+
// We add a task called generateAutolinkingPackageList to do not clash with the existing task
237+
// called generatePackageList. This can to be renamed once we unlink the rn <-> cli
238+
// dependency.
239+
val generatePackageListTask =
240+
project.tasks.register(
241+
"generateAutolinkingPackageList", GeneratePackageListTask::class.java) { task ->
242+
task.dependsOn(runAutolinkingConfigTask)
243+
task.autolinkInputFile.set(configOutputFile)
244+
task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
245+
}
246+
247+
// We let generateAutolinkingPackageList depend on the preBuild task so it's executed before
248+
// everything else.
249+
project.tasks.named("preBuild", Task::class.java).dependsOn(generatePackageListTask)
250+
251+
// We tell Android Gradle Plugin that inside /build/generated/autolinking/src/main/java there
252+
// are sources to be compiled as well.
253+
project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
254+
onVariants(selector().all()) { variant ->
255+
variant.sources.java?.addStaticSourceDirectory(
256+
generatedAutolinkingJavaDir.get().asFile.absolutePath)
257+
}
229258
}
230259
}
231260
}

packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ data class ModelAutolinkingDependenciesPlatformAndroidJson(
1515
val libraryName: String,
1616
val componentDescriptors: List<String>,
1717
val cmakeListsPath: String,
18-
val cxxModuleCMakeListsModuleName: String?,
19-
val cxxModuleCMakeListsPath: String?,
20-
val cxxModuleHeaderName: String?,
21-
val dependencyConfiguration: String?
18+
val cxxModuleCMakeListsModuleName: String? = null,
19+
val cxxModuleCMakeListsPath: String? = null,
20+
val cxxModuleHeaderName: String? = null,
21+
val dependencyConfiguration: String? = null
2222
)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.tasks
9+
10+
import com.facebook.react.model.ModelAutolinkingDependenciesJson
11+
import com.facebook.react.utils.JsonUtils
12+
import java.io.File
13+
import org.gradle.api.DefaultTask
14+
import org.gradle.api.file.DirectoryProperty
15+
import org.gradle.api.file.RegularFileProperty
16+
import org.gradle.api.tasks.InputFile
17+
import org.gradle.api.tasks.OutputDirectory
18+
import org.gradle.api.tasks.TaskAction
19+
20+
abstract class GeneratePackageListTask : DefaultTask() {
21+
22+
init {
23+
group = "react"
24+
}
25+
26+
@get:InputFile abstract val autolinkInputFile: RegularFileProperty
27+
28+
@get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty
29+
30+
@TaskAction
31+
fun taskAction() {
32+
val model = JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile)
33+
34+
val packageName =
35+
model?.project?.android?.packageName
36+
?: error(
37+
"RNGP - Autolinking: Could not find project.android.packageName in react-native config output! Could not autolink packages without this field.")
38+
val packages = model.dependencies?.values ?: emptyList()
39+
40+
val packageImports = composePackageImports(packageName, packages)
41+
val packageClassInstance = composePackageInstance(packageName, packages)
42+
val generatedFileContents = composeFileContent(packageImports, packageClassInstance)
43+
44+
val outputDir = generatedOutputDirectory.get().asFile
45+
outputDir.mkdirs()
46+
File(outputDir, GENERATED_FILENAME).apply {
47+
parentFile.mkdirs()
48+
writeText(generatedFileContents)
49+
}
50+
}
51+
52+
internal fun composePackageImports(
53+
packageName: String,
54+
packages: Collection<ModelAutolinkingDependenciesJson>
55+
) =
56+
packages.joinToString("\n") { entry ->
57+
val packageImportPath =
58+
requireNotNull(entry.platforms?.android?.packageImportPath) {
59+
"RNGP - Autolinking: Missing `packageImportPath` in `config` for dependency ${entry.name}. This is required to generate the autolinking package list."
60+
}
61+
"// ${entry.name}\n${interpolateDynamicValues(packageImportPath, packageName)}"
62+
}
63+
64+
internal fun composePackageInstance(
65+
packageName: String,
66+
packages: Collection<ModelAutolinkingDependenciesJson>
67+
) =
68+
if (packages.isEmpty()) {
69+
""
70+
} else {
71+
",\n " +
72+
packages.joinToString(",\n ") { entry ->
73+
val packageInstance =
74+
requireNotNull(entry.platforms?.android?.packageInstance) {
75+
"RNGP - Autolinking: Missing `packageInstance` in `config` for dependency ${entry.name}. This is required to generate the autolinking package list."
76+
}
77+
interpolateDynamicValues(packageInstance, packageName)
78+
}
79+
}
80+
81+
internal fun composeFileContent(packageImports: String, packageClassInstance: String): String =
82+
generatedFileContentsTemplate
83+
.replace("{{ packageImports }}", packageImports)
84+
.replace("{{ packageClassInstances }}", packageClassInstance)
85+
86+
companion object {
87+
const val GENERATED_FILENAME = "com/facebook/react/PackageList2.java"
88+
89+
/**
90+
* Before adding the package replacement mechanism, BuildConfig and R classes were imported
91+
* automatically into the scope of the file. We want to replace all non-FQDN references to those
92+
* classes with the package name of the MainApplication.
93+
*
94+
* We want to match "R" or "BuildConfig":
95+
* - new Package(R.string…),
96+
* - Module.configure(BuildConfig);
97+
* ^ hence including (BuildConfig|R)
98+
* but we don't want to match "R":
99+
* - new Package(getResources…),
100+
* - new PackageR…,
101+
* - new Royal…,
102+
* ^ hence excluding \w before and after matches
103+
* and "BuildConfig" that has FQDN reference:
104+
* - Module.configure(com.acme.BuildConfig);
105+
* ^ hence excluding . before the match.
106+
*/
107+
internal fun interpolateDynamicValues(input: String, packageName: String): String =
108+
input.replace(Regex("([^.\\w])(BuildConfig|R)(\\W)")) { match ->
109+
val (prefix, className, suffix) = match.destructured
110+
"${prefix}${packageName}.${className}${suffix}"
111+
}
112+
113+
// language=java
114+
val generatedFileContentsTemplate =
115+
"""
116+
package com.facebook.react;
117+
118+
import android.app.Application;
119+
import android.content.Context;
120+
import android.content.res.Resources;
121+
122+
import com.facebook.react.ReactPackage;
123+
import com.facebook.react.shell.MainPackageConfig;
124+
import com.facebook.react.shell.MainReactPackage;
125+
import java.util.Arrays;
126+
import java.util.ArrayList;
127+
128+
{{ packageImports }}
129+
130+
public class PackageList2 {
131+
private Application application;
132+
private ReactNativeHost reactNativeHost;
133+
private MainPackageConfig mConfig;
134+
135+
public PackageList(ReactNativeHost reactNativeHost) {
136+
this(reactNativeHost, null);
137+
}
138+
139+
public PackageList(Application application) {
140+
this(application, null);
141+
}
142+
143+
public PackageList(ReactNativeHost reactNativeHost, MainPackageConfig config) {
144+
this.reactNativeHost = reactNativeHost;
145+
mConfig = config;
146+
}
147+
148+
public PackageList(Application application, MainPackageConfig config) {
149+
this.reactNativeHost = null;
150+
this.application = application;
151+
mConfig = config;
152+
}
153+
154+
private ReactNativeHost getReactNativeHost() {
155+
return this.reactNativeHost;
156+
}
157+
158+
private Resources getResources() {
159+
return this.getApplication().getResources();
160+
}
161+
162+
private Application getApplication() {
163+
if (this.reactNativeHost == null) return this.application;
164+
return this.reactNativeHost.getApplication();
165+
}
166+
167+
private Context getApplicationContext() {
168+
return this.getApplication().getApplicationContext();
169+
}
170+
171+
public ArrayList<ReactPackage> getPackages() {
172+
return new ArrayList<>(Arrays.<ReactPackage>asList(
173+
new MainReactPackage(mConfig){{ packageClassInstances }}
174+
));
175+
}
176+
}
177+
"""
178+
.trimIndent()
179+
}
180+
}

0 commit comments

Comments
 (0)