Skip to content

Commit 5bb3a6d

Browse files
riteshshukla04meta-codesync[bot]
authored andcommitted
fix: Use FQCN to avoid collisions in PackageList.java (#54736)
Summary: When two different React Native libraries export a package class with the same name but in different namespaces, the generated PackageList.java causes a compilation error due to ambiguous class references. The current autolinking generates: ``` kotlin import com.pikachu.NativeStorage; import com.snowfox.NativeStorage; // ❌ Compile error: NativeStorage is already defined public ArrayList<ReactPackage> getPackages() { return new ArrayList<>(Arrays.<ReactPackage>asList( new MainReactPackage(mConfig), new NativeStorage(), // ❌ Ambiguous reference new NativeStorage() // ❌ Ambiguous reference )); } ``` Solution:- Use fully qualified class names (FQCN) instead of imports: ``` kotlin // No imports needed public ArrayList<ReactPackage> getPackages() { return new ArrayList<>(Arrays.<ReactPackage>asList( new MainReactPackage(mConfig), // pikachu-storage new com.pikachu.NativeStorage(), // snowfox-storage new com.snowfox.NativeStorage() )); } ``` ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [ANDROID][FIXED]- Use FQCN to avoid collisions Pull Request resolved: #54736 Test Plan: Updated test. Also tested on my app and RN tester <img width="687" height="223" alt="image" src="https://github.com/user-attachments/assets/fe54d0c3-4ab4-4d74-a98f-97243851e8c4" /> CI will tell if build fails Reviewed By: mdvacca Differential Revision: D88157961 Pulled By: cortinico fbshipit-source-id: 5fe072dcaed177af1036ca88d55870081a3f4205
1 parent 41eace0 commit 5bb3a6d

2 files changed

Lines changed: 67 additions & 75 deletions

File tree

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

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ abstract class GeneratePackageListTask : DefaultTask() {
3434
JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile)
3535
?: error(
3636
"""
37-
RNGP - Autolinking: Could not parse autolinking config file:
38-
${autolinkInputFile.get().asFile.absolutePath}
39-
40-
The file is either missing or not containing valid JSON so the build won't succeed.
41-
"""
37+
RNGP - Autolinking: Could not parse autolinking config file:
38+
${autolinkInputFile.get().asFile.absolutePath}
39+
40+
The file is either missing or not containing valid JSON so the build won't succeed.
41+
"""
4242
.trimIndent()
4343
)
4444

@@ -49,9 +49,8 @@ abstract class GeneratePackageListTask : DefaultTask() {
4949
)
5050

5151
val androidPackages = filterAndroidPackages(model)
52-
val packageImports = composePackageImports(packageName, androidPackages)
5352
val packageClassInstance = composePackageInstance(packageName, androidPackages)
54-
val generatedFileContents = composeFileContent(packageImports, packageClassInstance)
53+
val generatedFileContents = composeFileContent(packageClassInstance)
5554

5655
val outputDir = generatedOutputDirectory.get().asFile
5756
outputDir.mkdirs()
@@ -61,34 +60,49 @@ abstract class GeneratePackageListTask : DefaultTask() {
6160
}
6261
}
6362

64-
internal fun composePackageImports(
65-
packageName: String,
66-
packages: Map<String, ModelAutolinkingDependenciesPlatformAndroidJson>,
67-
) =
68-
packages.entries.joinToString("\n") { (name, dep) ->
69-
val packageImportPath =
70-
requireNotNull(dep.packageImportPath) {
71-
"RNGP - Autolinking: Missing `packageImportPath` in `config` for dependency $name. This is required to generate the autolinking package list."
72-
}
73-
"// $name\n${interpolateDynamicValues(packageImportPath, packageName)}"
74-
}
63+
/**
64+
* Extracts the fully qualified class name from an import statement. E.g., "import
65+
* com.foo.bar.MyClass;" -> "com.foo.bar.MyClass"
66+
*/
67+
internal fun extractFqcnFromImport(importStatement: String): String? {
68+
val match = Regex("import\\s+([\\w.]+)\\s*;").find(importStatement)
69+
return match?.groupValues?.get(1)
70+
}
7571

7672
internal fun composePackageInstance(
7773
packageName: String,
7874
packages: Map<String, ModelAutolinkingDependenciesPlatformAndroidJson>,
79-
) =
80-
if (packages.isEmpty()) {
81-
""
82-
} else {
83-
",\n " +
84-
packages.entries.joinToString(",\n ") { (name, dep) ->
85-
val packageInstance =
86-
requireNotNull(dep.packageInstance) {
87-
"RNGP - Autolinking: Missing `packageInstance` in `config` for dependency $name. This is required to generate the autolinking package list."
88-
}
89-
interpolateDynamicValues(packageInstance, packageName)
90-
}
91-
}
75+
): String {
76+
if (packages.isEmpty()) {
77+
return ""
78+
}
79+
80+
val instances =
81+
packages.entries.map { (name, dep) ->
82+
val packageInstance =
83+
requireNotNull(dep.packageInstance) {
84+
"RNGP - Autolinking: Missing `packageInstance` in `config` for dependency $name. This is required to generate the autolinking package list."
85+
}
86+
val packageImportPath = dep.packageImportPath
87+
val interpolated = interpolateDynamicValues(packageInstance, packageName)
88+
89+
// Use FQCN to avoid class name collisions between different packages
90+
val fqcn = extractFqcnFromImport(interpolateDynamicValues(packageImportPath, packageName))
91+
val fqcnInstance =
92+
if (fqcn != null) {
93+
val className = fqcn.substringAfterLast('.')
94+
// Replace the short class name with FQCN in the instance
95+
interpolated.replace(Regex("\\b${Regex.escape(className)}\\b")) { fqcn }
96+
} else {
97+
interpolated
98+
}
99+
100+
// Add comment with package name before each instance
101+
"// $name\n $fqcnInstance"
102+
}
103+
104+
return ",\n " + instances.joinToString(",\n ")
105+
}
92106

93107
internal fun filterAndroidPackages(
94108
model: ModelAutolinkingConfigJson?
@@ -101,10 +115,8 @@ abstract class GeneratePackageListTask : DefaultTask() {
101115
.associate { it.name to checkNotNull(it.platforms?.android) }
102116
}
103117

104-
internal fun composeFileContent(packageImports: String, packageClassInstance: String): String =
105-
generatedFileContentsTemplate
106-
.replace("{{ packageImports }}", packageImports)
107-
.replace("{{ packageClassInstances }}", packageClassInstance)
118+
internal fun composeFileContent(packageClassInstance: String): String =
119+
generatedFileContentsTemplate.replace("{{ packageClassInstances }}", packageClassInstance)
108120

109121
companion object {
110122
const val GENERATED_FILENAME = "com/facebook/react/PackageList.java"
@@ -148,8 +160,6 @@ abstract class GeneratePackageListTask : DefaultTask() {
148160
import java.util.Arrays;
149161
import java.util.ArrayList;
150162
151-
{{ packageImports }}
152-
153163
@SuppressWarnings("deprecation")
154164
public class PackageList {
155165
private Application application;

packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GeneratePackageListTaskTest.kt

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,17 @@ class GeneratePackageListTaskTest {
4343
}
4444

4545
@Test
46-
fun composePackageImports_withNoPackages_returnsEmpty() {
46+
fun extractFqcnFromImport_withValidImport_returnsClassName() {
4747
val task = createTestTask<GeneratePackageListTask>()
48-
val packageName = "com.facebook.react"
49-
val result = task.composePackageImports(packageName, emptyMap())
50-
assertThat(result).isEqualTo("")
48+
val result = task.extractFqcnFromImport("import com.facebook.react.APackage;")
49+
assertThat(result).isEqualTo("com.facebook.react.APackage")
5150
}
5251

5352
@Test
54-
fun composePackageImports_withPackages_returnsImportCorrectly() {
53+
fun extractFqcnFromImport_withInvalidImport_returnsNull() {
5554
val task = createTestTask<GeneratePackageListTask>()
56-
val packageName = "com.facebook.react"
57-
58-
val result = task.composePackageImports(packageName, testDependencies)
59-
assertThat(result)
60-
.isEqualTo(
61-
"""
62-
// @react-native/a-package
63-
import com.facebook.react.aPackage;
64-
// @react-native/another-package
65-
import com.facebook.react.anotherPackage;
66-
"""
67-
.trimIndent()
68-
)
55+
val result = task.extractFqcnFromImport("invalid import statement")
56+
assertThat(result).isNull()
6957
}
7058

7159
@Test
@@ -77,7 +65,7 @@ class GeneratePackageListTaskTest {
7765
}
7866

7967
@Test
80-
fun composePackageInstance_withPackages_returnsImportCorrectly() {
68+
fun composePackageInstance_withPackages_returnsFqcnCorrectly() {
8169
val task = createTestTask<GeneratePackageListTask>()
8270
val packageName = "com.facebook.react"
8371

@@ -86,8 +74,10 @@ class GeneratePackageListTaskTest {
8674
.isEqualTo(
8775
"""
8876
,
89-
new APackage(),
90-
new AnotherPackage()
77+
// @react-native/a-package
78+
new com.facebook.react.APackage(),
79+
// @react-native/another-package
80+
new com.facebook.react.AnotherPackage()
9181
"""
9282
.trimIndent()
9383
)
@@ -226,10 +216,8 @@ class GeneratePackageListTaskTest {
226216
@Test
227217
fun composeFileContent_withNoPackages_returnsValidFile() {
228218
val task = createTestTask<GeneratePackageListTask>()
229-
val packageName = "com.facebook.react"
230-
val imports = task.composePackageImports(packageName, emptyMap())
231-
val instance = task.composePackageInstance(packageName, emptyMap())
232-
val result = task.composeFileContent(imports, instance)
219+
val instance = task.composePackageInstance("com.facebook.react", emptyMap())
220+
val result = task.composeFileContent(instance)
233221
// language=java
234222
assertThat(result)
235223
.isEqualTo(
@@ -246,8 +234,6 @@ class GeneratePackageListTaskTest {
246234
import java.util.Arrays;
247235
import java.util.ArrayList;
248236
249-
250-
251237
@SuppressWarnings("deprecation")
252238
public class PackageList {
253239
private Application application;
@@ -305,9 +291,8 @@ class GeneratePackageListTaskTest {
305291
fun composeFileContent_withPackages_returnsValidFile() {
306292
val task = createTestTask<GeneratePackageListTask>()
307293
val packageName = "com.facebook.react"
308-
val imports = task.composePackageImports(packageName, testDependencies)
309294
val instance = task.composePackageInstance(packageName, testDependencies)
310-
val result = task.composeFileContent(imports, instance)
295+
val result = task.composeFileContent(instance)
311296
// language=java
312297
assertThat(result)
313298
.isEqualTo(
@@ -324,11 +309,6 @@ class GeneratePackageListTaskTest {
324309
import java.util.Arrays;
325310
import java.util.ArrayList;
326311
327-
// @react-native/a-package
328-
import com.facebook.react.aPackage;
329-
// @react-native/another-package
330-
import com.facebook.react.anotherPackage;
331-
332312
@SuppressWarnings("deprecation")
333313
public class PackageList {
334314
private Application application;
@@ -374,8 +354,10 @@ class GeneratePackageListTaskTest {
374354
public ArrayList<ReactPackage> getPackages() {
375355
return new ArrayList<>(Arrays.<ReactPackage>asList(
376356
new MainReactPackage(mConfig),
377-
new APackage(),
378-
new AnotherPackage()
357+
// @react-native/a-package
358+
new com.facebook.react.APackage(),
359+
// @react-native/another-package
360+
new com.facebook.react.AnotherPackage()
379361
));
380362
}
381363
}
@@ -389,7 +371,7 @@ class GeneratePackageListTaskTest {
389371
"@react-native/a-package" to
390372
ModelAutolinkingDependenciesPlatformAndroidJson(
391373
sourceDir = "./a/directory",
392-
packageImportPath = "import com.facebook.react.aPackage;",
374+
packageImportPath = "import com.facebook.react.APackage;",
393375
packageInstance = "new APackage()",
394376
buildTypes = emptyList(),
395377
libraryName = "aPackage",
@@ -399,7 +381,7 @@ class GeneratePackageListTaskTest {
399381
"@react-native/another-package" to
400382
ModelAutolinkingDependenciesPlatformAndroidJson(
401383
sourceDir = "./another/directory",
402-
packageImportPath = "import com.facebook.react.anotherPackage;",
384+
packageImportPath = "import com.facebook.react.AnotherPackage;",
403385
packageInstance = "new AnotherPackage()",
404386
buildTypes = emptyList(),
405387
libraryName = "anotherPackage",

0 commit comments

Comments
 (0)