From 63cc8d42d7db7fe9565a7b1cedcfbec9c8104a44 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 30 Apr 2024 02:49:48 -0700 Subject: [PATCH] RNGP - Autolinking - Add model classes for parsing the `config` output Summary: This diff is part of RFC0759 https://github.com/react-native-community/discussions-and-proposals/pull/759 Here I'm creating data classes that will allow us to parse the `config` JSON output. Code is pretty straightforward and follows the structure as the `config` command output. Changelog: [Internal] [Changed] - RNGP - Autolinking - Add model classes for parsing the `config` output Reviewed By: cipolleschi, blakef Differential Revision: D55475595 fbshipit-source-id: 3457c008ff0c5bce2b47fd637c7b10a5e7427c01 --- .../ModelAutolinkingAndroidProjectJson.kt | 18 ++ .../react/model/ModelAutolinkingConfigJson.kt | 14 ++ .../model/ModelAutolinkingDependenciesJson.kt | 18 ++ ...olinkingDependenciesPlatformAndroidJson.kt | 22 +++ ...odelAutolinkingDependenciesPlatformJson.kt | 12 ++ .../model/ModelAutolinkingProjectJson.kt | 10 + .../com/facebook/react/utils/JsonUtils.kt | 7 + .../ModelAutolinkingDependenciesJsonTest.kt | 46 +++++ .../com/facebook/react/utils/JsonUtilsTest.kt | 187 +++++++++++++++++- 9 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingAndroidProjectJson.kt create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingConfigJson.kt create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJson.kt create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformJson.kt create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingProjectJson.kt create mode 100644 packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJsonTest.kt diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingAndroidProjectJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingAndroidProjectJson.kt new file mode 100644 index 00000000000000..9a97eeab802cd1 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingAndroidProjectJson.kt @@ -0,0 +1,18 @@ +/* + * 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.model + +data class ModelAutolinkingAndroidProjectJson( + val sourceDir: String, + val appName: String, + val packageName: String, + val applicationId: String, + val mainActivity: String, + val watchModeCommandParams: List?, + val dependencyConfiguration: String? +) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingConfigJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingConfigJson.kt new file mode 100644 index 00000000000000..e2b3d3b9827554 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingConfigJson.kt @@ -0,0 +1,14 @@ +/* + * 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.model + +data class ModelAutolinkingConfigJson( + val reactNativeVersion: String, + val dependencies: Map?, + val project: ModelAutolinkingProjectJson?, +) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJson.kt new file mode 100644 index 00000000000000..2373a83d85621c --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJson.kt @@ -0,0 +1,18 @@ +/* + * 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.model + +data class ModelAutolinkingDependenciesJson( + val root: String, + val name: String, + val platforms: ModelAutolinkingDependenciesPlatformJson? +) { + + val nameCleansed: String + get() = name.replace(Regex("[~*!'()]+"), "_").replace(Regex("^@([\\w-.]+)/"), "$1_") +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt new file mode 100644 index 00000000000000..4304dcf082f100 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformAndroidJson.kt @@ -0,0 +1,22 @@ +/* + * 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.model + +data class ModelAutolinkingDependenciesPlatformAndroidJson( + val sourceDir: String, + val packageImportPath: String, + val packageInstance: String, + val buildTypes: List, + val libraryName: String, + val componentDescriptors: List, + val cmakeListsPath: String, + val cxxModuleCMakeListsModuleName: String?, + val cxxModuleCMakeListsPath: String?, + val cxxModuleHeaderName: String?, + val dependencyConfiguration: String? +) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformJson.kt new file mode 100644 index 00000000000000..c6b1e30dc91509 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesPlatformJson.kt @@ -0,0 +1,12 @@ +/* + * 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.model + +data class ModelAutolinkingDependenciesPlatformJson( + val android: ModelAutolinkingDependenciesPlatformAndroidJson? +) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingProjectJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingProjectJson.kt new file mode 100644 index 00000000000000..fdb2b620e035ad --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelAutolinkingProjectJson.kt @@ -0,0 +1,10 @@ +/* + * 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.model + +data class ModelAutolinkingProjectJson(val android: ModelAutolinkingAndroidProjectJson?) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt index 12d2cccf7139ca..12d26f04c36c65 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt @@ -7,6 +7,7 @@ package com.facebook.react.utils +import com.facebook.react.model.ModelAutolinkingConfigJson import com.facebook.react.model.ModelPackageJson import com.google.gson.Gson import java.io.File @@ -18,4 +19,10 @@ object JsonUtils { input.bufferedReader().use { runCatching { gsonConverter.fromJson(it, ModelPackageJson::class.java) }.getOrNull() } + + fun fromAutolinkingConfigJson(input: File): ModelAutolinkingConfigJson? = + input.bufferedReader().use { + runCatching { gsonConverter.fromJson(it, ModelAutolinkingConfigJson::class.java) } + .getOrNull() + } } diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJsonTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJsonTest.kt new file mode 100644 index 00000000000000..5d162c6dee3db8 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJsonTest.kt @@ -0,0 +1,46 @@ +/* + * 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.model + +import org.junit.Assert.assertEquals +import org.junit.Test + +class ModelAutolinkingDependenciesJsonTest { + + @Test + fun nameCleansed_withoutScope() { + assertEquals("name", ModelAutolinkingDependenciesJson("", "name", null).nameCleansed) + assertEquals( + "react_native", ModelAutolinkingDependenciesJson("", "react~native", null).nameCleansed) + assertEquals( + "react_native", ModelAutolinkingDependenciesJson("", "react*native", null).nameCleansed) + assertEquals( + "react_native", ModelAutolinkingDependenciesJson("", "react!native", null).nameCleansed) + assertEquals( + "react_native", ModelAutolinkingDependenciesJson("", "react'native", null).nameCleansed) + assertEquals( + "react_native", ModelAutolinkingDependenciesJson("", "react(native", null).nameCleansed) + assertEquals( + "react_native", ModelAutolinkingDependenciesJson("", "react)native", null).nameCleansed) + assertEquals( + "react_native", + ModelAutolinkingDependenciesJson("", "react~*!'()native", null).nameCleansed) + } + + @Test + fun nameCleansed_withScope() { + assertEquals( + "react-native_package", + ModelAutolinkingDependenciesJson("", "@react-native/package", null).nameCleansed) + assertEquals( + "this_is_a_more_complicated_example_of_weird_packages", + ModelAutolinkingDependenciesJson( + "", "@this*is~a(more)complicated/example!of~weird)packages", null) + .nameCleansed) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt index 0b7c2d60fb0b50..db73bbc762fd52 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt @@ -18,14 +18,14 @@ class JsonUtilsTest { @get:Rule val tempFolder = TemporaryFolder() @Test - fun withInvalidJson_returnsNull() { + fun fromPackageJson_withInvalidJson_returnsNull() { val invalidJson = createJsonFile("""¯\_(ツ)_/¯""") assertNull(JsonUtils.fromPackageJson(invalidJson)) } @Test - fun withEmptyJson_returnsEmptyObject() { + fun fromPackageJson_withEmptyJson_returnsEmptyObject() { val invalidJson = createJsonFile("""{}""") val parsed = JsonUtils.fromPackageJson(invalidJson) @@ -35,7 +35,7 @@ class JsonUtilsTest { } @Test - fun withOldJsonConfig_returnsAnEmptyLibrary() { + fun fromPackageJson_withOldJsonConfig_returnsAnEmptyLibrary() { val oldJsonConfig = createJsonFile( """ @@ -62,7 +62,7 @@ class JsonUtilsTest { } @Test - fun withValidJson_parsesCorrectly() { + fun fromPackageJson_withValidJson_parsesCorrectly() { val validJson = createJsonFile( """ @@ -121,6 +121,185 @@ class JsonUtilsTest { assertEquals("1000.0.0", parsed.version) } + @Test + fun fromAutolinkingConfigJson_withInvalidJson_returnsNull() { + val invalidJson = createJsonFile("""¯\_(ツ)_/¯""") + + assertNull(JsonUtils.fromAutolinkingConfigJson(invalidJson)) + } + + @Test + fun fromAutolinkingConfigJson_withSimpleJson_returnsIt() { + val validJson = + createJsonFile( + """ + { + "reactNativeVersion": "1000.0.0" + } + """ + .trimIndent()) + val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! + + assertEquals("1000.0.0", parsed.reactNativeVersion) + } + + @Test + fun fromAutolinkingConfigJson_withProjectSpecified_canParseIt() { + val validJson = + createJsonFile( + """ + { + "reactNativeVersion": "1000.0.0", + "project": { + "ios": { + "sourceDir": "./packages/rn-tester", + "xcodeProject": { + "name": "RNTesterPods.xcworkspace", + "isWorkspace": true + }, + "automaticPodsInstallation": false + }, + "android": { + "sourceDir": "./packages/rn-tester", + "appName": "RN-Tester", + "packageName": "com.facebook.react.uiapp", + "applicationId": "com.facebook.react.uiapp", + "mainActivity": ".RNTesterActivity", + "watchModeCommandParams": [ + "--mode HermesDebug" + ], + "dependencyConfiguration": "implementation" + } + } + } + """ + .trimIndent()) + val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! + + assertEquals("./packages/rn-tester", parsed.project!!.android!!.sourceDir) + assertEquals("RN-Tester", parsed.project!!.android!!.appName) + assertEquals("com.facebook.react.uiapp", parsed.project!!.android!!.packageName) + assertEquals("com.facebook.react.uiapp", parsed.project!!.android!!.applicationId) + assertEquals(".RNTesterActivity", parsed.project!!.android!!.mainActivity) + assertEquals("--mode HermesDebug", parsed.project!!.android!!.watchModeCommandParams!![0]) + assertEquals("implementation", parsed.project!!.android!!.dependencyConfiguration) + } + + @Test + fun fromAutolinkingConfigJson_withDependenciesSpecified_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": { + "ios": { + "podspecPath": "./node_modules/@react-native/oss-library-example/OSSLibraryExample.podspec", + "version": "0.0.1", + "configurations": [], + "scriptPhases": [] + }, + "android": { + "sourceDir": "./node_modules/@react-native/oss-library-example/android", + "packageImportPath": "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;", + "packageInstance": "new OSSLibraryExamplePackage()", + "buildTypes": ["staging", "debug", "release"], + "libraryName": "OSSLibraryExampleSpec", + "componentDescriptors": [ + "SampleNativeComponentComponentDescriptor" + ], + "cmakeListsPath": "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt", + "cxxModuleCMakeListsModuleName": null, + "cxxModuleCMakeListsPath": null, + "cxxModuleHeaderName": null, + "dependencyConfiguration": "implementation" + } + } + } + } + } + """ + .trimIndent()) + val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! + + assertEquals( + "./node_modules/@react-native/oss-library-example", + parsed.dependencies!!["@react-native/oss-library-example"]!!.root) + assertEquals( + "@react-native/oss-library-example", + parsed.dependencies!!["@react-native/oss-library-example"]!!.name) + assertEquals( + "react-native_oss-library-example", + parsed.dependencies!!["@react-native/oss-library-example"]!!.nameCleansed) + assertEquals( + "./node_modules/@react-native/oss-library-example/android", + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .sourceDir) + assertEquals( + "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;", + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .packageImportPath) + assertEquals( + "new OSSLibraryExamplePackage()", + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .packageInstance) + assertEquals( + listOf("staging", "debug", "release"), + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .buildTypes) + assertEquals( + "OSSLibraryExampleSpec", + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .libraryName) + assertEquals( + listOf("SampleNativeComponentComponentDescriptor"), + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .componentDescriptors) + assertEquals( + "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt", + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .cmakeListsPath) + assertNull( + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .cxxModuleHeaderName) + assertNull( + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .cxxModuleCMakeListsPath) + assertNull( + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .cxxModuleCMakeListsModuleName) + assertEquals( + "implementation", + parsed.dependencies!!["@react-native/oss-library-example"]!! + .platforms!! + .android!! + .dependencyConfiguration) + } + private fun createJsonFile(@Language("JSON") input: String) = tempFolder.newFile().apply { writeText(input) } }