Skip to content

Commit 5f8dd41

Browse files
committed
Add packageImportPaths array field to autolinking config
Instead of regex-parsing Java import statements from the packageImportPath string, introduce a new packageImportPaths field that accepts a structured array of FQCNs directly from the CLI config. RNGP prefers packageImportPaths when present and falls back to the legacy extractFqcnFromImport path for backward compatibility with older CLI versions that don't yet emit the new field. Made-with: Cursor
1 parent 13c67a7 commit 5f8dd41

4 files changed

Lines changed: 141 additions & 8 deletions

File tree

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,28 @@ abstract class GeneratePackageListTask : DefaultTask() {
8383
requireNotNull(dep.packageInstance) {
8484
"RNGP - Autolinking: Missing `packageInstance` in `config` for dependency $name. This is required to generate the autolinking package list."
8585
}
86-
val packageImportPath = dep.packageImportPath
8786
val interpolated = interpolateDynamicValues(packageInstance, packageName)
8887

89-
// Use FQCN to avoid class name collisions between different packages
90-
val fqcn = extractFqcnFromImport(interpolateDynamicValues(packageImportPath, packageName))
88+
// Use FQCNs to avoid class name collisions between different packages.
89+
val packageImportPaths = dep.packageImportPaths
9190
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 }
91+
if (packageImportPaths != null) {
92+
packageImportPaths.fold(interpolated) { acc, fqcn ->
93+
val className = fqcn.substringAfterLast('.')
94+
// Negative lookbehind ensures we only replace bare class names,
95+
// not ones already part of a fully qualified name.
96+
acc.replace(Regex("(?<!\\.)\\b${Regex.escape(className)}\\b")) { fqcn }
97+
}
9698
} else {
97-
interpolated
99+
val fqcn =
100+
extractFqcnFromImport(
101+
interpolateDynamicValues(dep.packageImportPath, packageName))
102+
if (fqcn != null) {
103+
val className = fqcn.substringAfterLast('.')
104+
interpolated.replace(Regex("\\b${Regex.escape(className)}\\b")) { fqcn }
105+
} else {
106+
interpolated
107+
}
98108
}
99109

100110
// Add comment with package name before each instance

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,83 @@ class GeneratePackageListTaskTest {
8383
)
8484
}
8585

86+
@Test
87+
fun composePackageInstance_withPackageImportPaths_usesArrayDirectly() {
88+
val task = createTestTask<GeneratePackageListTask>()
89+
val packageName = "com.example.app"
90+
91+
val deps =
92+
mapOf(
93+
"react-native-appsflyer" to
94+
ModelAutolinkingDependenciesPlatformAndroidJson(
95+
sourceDir = "./node_modules/react-native-appsflyer/android",
96+
packageImportPath = "",
97+
packageImportPaths =
98+
listOf(
99+
"com.appsflyer.reactnative.RNAppsFlyerPackage",
100+
"com.appsflyer.reactnative.PCAppsFlyerPackage",
101+
),
102+
packageInstance = "new RNAppsFlyerPackage(),\nnew PCAppsFlyerPackage()",
103+
buildTypes = emptyList(),
104+
),
105+
)
106+
107+
val result = task.composePackageInstance(packageName, deps)
108+
assertThat(result)
109+
.isEqualTo(
110+
"""
111+
,
112+
// react-native-appsflyer
113+
new com.appsflyer.reactnative.RNAppsFlyerPackage(),
114+
new com.appsflyer.reactnative.PCAppsFlyerPackage()
115+
"""
116+
.trimIndent()
117+
)
118+
}
119+
120+
@Test
121+
fun composePackageInstance_withBothFields_prefersPackageImportPaths() {
122+
val task = createTestTask<GeneratePackageListTask>()
123+
val packageName = "com.example.app"
124+
125+
val deps =
126+
mapOf(
127+
"my-library" to
128+
ModelAutolinkingDependenciesPlatformAndroidJson(
129+
sourceDir = "./node_modules/my-library/android",
130+
packageImportPath = "import com.wrong.WrongPackage;",
131+
packageImportPaths = listOf("com.correct.CorrectPackage"),
132+
packageInstance = "new CorrectPackage()",
133+
buildTypes = emptyList(),
134+
),
135+
)
136+
137+
val result = task.composePackageInstance(packageName, deps)
138+
assertThat(result).contains("com.correct.CorrectPackage")
139+
assertThat(result).doesNotContain("com.wrong.WrongPackage")
140+
}
141+
142+
@Test
143+
fun composePackageInstance_withNullPackageImportPaths_fallsBackToSingleFqcn() {
144+
val task = createTestTask<GeneratePackageListTask>()
145+
val packageName = "com.example.app"
146+
147+
val deps =
148+
mapOf(
149+
"my-library" to
150+
ModelAutolinkingDependenciesPlatformAndroidJson(
151+
sourceDir = "./node_modules/my-library/android",
152+
packageImportPath = "import com.legacy.LegacyPackage;",
153+
packageImportPaths = null,
154+
packageInstance = "new LegacyPackage()",
155+
buildTypes = emptyList(),
156+
),
157+
)
158+
159+
val result = task.composePackageInstance(packageName, deps)
160+
assertThat(result).contains("com.legacy.LegacyPackage")
161+
}
162+
86163
@Test
87164
fun interpolateDynamicValues_withNoBuildConfigOrROccurrencies_doesNothing() {
88165
val packageName = "com.facebook.react"

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package com.facebook.react.model
1010
data class ModelAutolinkingDependenciesPlatformAndroidJson(
1111
val sourceDir: String,
1212
val packageImportPath: String,
13+
val packageImportPaths: List<String>? = null,
1314
val packageInstance: String,
1415
val buildTypes: List<String>,
1516
val libraryName: String? = null,

packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,51 @@ class JsonUtilsTest {
375375
.isPureCxxDependency!!
376376
)
377377
.isFalse()
378+
assertThat(
379+
parsed.dependencies!!["@react-native/oss-library-example"]!!
380+
.platforms!!
381+
.android!!
382+
.packageImportPaths
383+
)
384+
.isNull()
385+
}
386+
387+
@Test
388+
fun fromAutolinkingConfigJson_withPackageImportPaths_canParseIt() {
389+
val validJson =
390+
createJsonFile(
391+
"""
392+
{
393+
"reactNativeVersion": "1000.0.0",
394+
"dependencies": {
395+
"@react-native/oss-library-example": {
396+
"root": "./node_modules/@react-native/oss-library-example",
397+
"name": "@react-native/oss-library-example",
398+
"platforms": {
399+
"android": {
400+
"sourceDir": "./node_modules/@react-native/oss-library-example/android",
401+
"packageImportPath": "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;",
402+
"packageImportPaths": [
403+
"com.facebook.react.osslibraryexample.OSSLibraryExamplePackage"
404+
],
405+
"packageInstance": "new OSSLibraryExamplePackage()",
406+
"buildTypes": ["debug", "release"]
407+
}
408+
}
409+
}
410+
}
411+
}
412+
"""
413+
.trimIndent()
414+
)
415+
val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!!
416+
417+
val android =
418+
parsed.dependencies!!["@react-native/oss-library-example"]!!.platforms!!.android!!
419+
assertThat(android.packageImportPaths)
420+
.containsExactly("com.facebook.react.osslibraryexample.OSSLibraryExamplePackage")
421+
assertThat(android.packageImportPath)
422+
.isEqualTo("import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;")
378423
}
379424

380425
private fun createJsonFile(@Language("JSON") input: String) =

0 commit comments

Comments
 (0)