diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt index 1156bc280694..cf5f28bfd9a2 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt @@ -83,18 +83,28 @@ abstract class GeneratePackageListTask : DefaultTask() { requireNotNull(dep.packageInstance) { "RNGP - Autolinking: Missing `packageInstance` in `config` for dependency $name. This is required to generate the autolinking package list." } - val packageImportPath = dep.packageImportPath val interpolated = interpolateDynamicValues(packageInstance, packageName) - // Use FQCN to avoid class name collisions between different packages - val fqcn = extractFqcnFromImport(interpolateDynamicValues(packageImportPath, packageName)) + // Use FQCNs to avoid class name collisions between different libraries. + val packageImportPaths = dep.packageImportPaths val fqcnInstance = - if (fqcn != null) { - val className = fqcn.substringAfterLast('.') - // Replace the short class name with FQCN in the instance - interpolated.replace(Regex("\\b${Regex.escape(className)}\\b")) { fqcn } + if (packageImportPaths != null) { + packageImportPaths.fold(interpolated) { acc, fqcn -> + val className = fqcn.substringAfterLast('.') + // Negative lookbehind ensures we only replace bare class names, + // not ones already part of a fully qualified name. + acc.replace(Regex("(?() + val packageName = "com.example.app" + + val deps = + mapOf( + "react-native-appsflyer" to + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./node_modules/react-native-appsflyer/android", + packageImportPath = "", + packageImportPaths = + listOf( + "com.appsflyer.reactnative.RNAppsFlyerPackage", + "com.appsflyer.reactnative.PCAppsFlyerPackage", + ), + packageInstance = "new RNAppsFlyerPackage(),\nnew PCAppsFlyerPackage()", + buildTypes = emptyList(), + ), + ) + + val result = task.composePackageInstance(packageName, deps) + assertThat(result) + .isEqualTo( + """ + , + // react-native-appsflyer + new com.appsflyer.reactnative.RNAppsFlyerPackage(), + new com.appsflyer.reactnative.PCAppsFlyerPackage() + """ + .trimIndent() + ) + } + + @Test + fun composePackageInstance_withBothFields_prefersPackageImportPaths() { + val task = createTestTask() + val packageName = "com.example.app" + + val deps = + mapOf( + "my-library" to + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./node_modules/my-library/android", + packageImportPath = "import com.wrong.WrongPackage;", + packageImportPaths = listOf("com.correct.CorrectPackage"), + packageInstance = "new CorrectPackage()", + buildTypes = emptyList(), + ), + ) + + val result = task.composePackageInstance(packageName, deps) + assertThat(result).contains("com.correct.CorrectPackage") + assertThat(result).doesNotContain("com.wrong.WrongPackage") + } + + @Test + fun composePackageInstance_withNullPackageImportPaths_fallsBackToSingleFqcn() { + val task = createTestTask() + val packageName = "com.example.app" + + val deps = + mapOf( + "my-library" to + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./node_modules/my-library/android", + packageImportPath = "import com.legacy.LegacyPackage;", + packageImportPaths = null, + packageInstance = "new LegacyPackage()", + buildTypes = emptyList(), + ), + ) + + val result = task.composePackageInstance(packageName, deps) + assertThat(result).contains("com.legacy.LegacyPackage") + } + @Test fun interpolateDynamicValues_withNoBuildConfigOrROccurrencies_doesNothing() { val packageName = "com.facebook.react" diff --git a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt index 947f0f3f2293..ae6fdfc90836 100644 --- a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt +++ b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt @@ -10,6 +10,7 @@ package com.facebook.react.model data class ModelAutolinkingDependenciesPlatformAndroidJson( val sourceDir: String, val packageImportPath: String, + val packageImportPaths: List? = null, val packageInstance: String, val buildTypes: List, val libraryName: String? = null, diff --git a/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt b/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt index 0544a8775470..2c7f11d906de 100644 --- a/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt +++ b/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt @@ -375,6 +375,51 @@ class JsonUtilsTest { .isPureCxxDependency!! ) .isFalse() + assertThat( + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .packageImportPaths + ) + .isNull() + } + + @Test + fun fromAutolinkingConfigJson_withPackageImportPaths_canParseIt() { + val validJson = + createJsonFile( + """ + { + "reactNativeVersion": "1000.0.0", + "dependencies": { + "@react-native/oss-library-example": { + "root": "./node_modules/@react-native/oss-library-example", + "name": "@react-native/oss-library-example", + "platforms": { + "android": { + "sourceDir": "./node_modules/@react-native/oss-library-example/android", + "packageImportPath": "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;", + "packageImportPaths": [ + "com.facebook.react.osslibraryexample.OSSLibraryExamplePackage" + ], + "packageInstance": "new OSSLibraryExamplePackage()", + "buildTypes": ["debug", "release"] + } + } + } + } + } + """ + .trimIndent() + ) + val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! + + val android = + parsed.dependencies!!["@react-native/oss-library-example"]!!.platforms!!.android!! + assertThat(android.packageImportPaths) + .containsExactly("com.facebook.react.osslibraryexample.OSSLibraryExamplePackage") + assertThat(android.packageImportPath) + .isEqualTo("import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;") } private fun createJsonFile(@Language("JSON") input: String) =