From 718952c8d38bcc4fbcfe3e59052f2ffeff176e2e Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 28 Nov 2022 05:14:16 -0800 Subject: [PATCH] RNGP - Various improvements needed for 3rd party libs Summary: This commit includes a series of fixes needed for better integration with libraries for 0.71: - I've added an `android/README.md` file as some libraries were failing the build if the folder was missing - RNGP now applies dep substitution on app and all the libraries project - RNGP now adds repositories on app and all the libraries project - I've removed the maven local repo to the `/android` folder as now is empty - I've fixed the path for the JSC repo for Windows users - I've added a bit of backward compat by re-adding an empty `project.react.ext` block that libraries might read from. - I've removed `codegenDir` from the `GenerateCodegenArtifactsTask` which was unused. Changelog: [Internal] [Changed] - RNGP - Various improvements needed for 3rd party libs Differential Revision: D41549489 fbshipit-source-id: 177f58675c281f349ba9437beadf7bbcab9e5a96 --- .gitignore | 3 +- android/README.md | 21 ++++++ .../kotlin/com/facebook/react/ReactPlugin.kt | 3 +- .../tasks/GenerateCodegenArtifactsTask.kt | 6 -- .../react/utils/BackwardCompatUtils.kt | 49 ++++++++++++ .../facebook/react/utils/DependencyUtils.kt | 73 ++++++++++-------- .../tasks/GenerateCodegenArtifactsTaskTest.kt | 18 +---- .../react/utils/BackwardCompatUtilsTest.kt | 45 +++++++++++ .../react/utils/DependencyUtilsTest.kt | 74 +++++++++++++++---- 9 files changed, 223 insertions(+), 69 deletions(-) create mode 100644 android/README.md create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/BackwardCompatUtils.kt create mode 100644 packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/BackwardCompatUtilsTest.kt diff --git a/.gitignore b/.gitignore index 7611c0691d574e..2102fc76bb1250 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,8 @@ buck-out .gradle local.properties *.iml -/android/ +/android/* +!/android/README.md # Node node_modules diff --git a/android/README.md b/android/README.md new file mode 100644 index 00000000000000..6d9b25c2e9646e --- /dev/null +++ b/android/README.md @@ -0,0 +1,21 @@ +# The `/android` folder inside `react-native` + +Starting from React Native 0.71, we're not shipping the `/android` folder inside the React Native NPM package +anymore due to sizing constraints on NPM. The Android artifacts are distributed via Maven Central. +You can read more about it in this RFC: +https://github.com/react-native-community/discussions-and-proposals/pull/508 + +If you're a library author and you're manipulating the React Native .aar files, to extract headers, +extract `.so` files or do anything with it, you're probably doing something wrong. React Native +0.71 ships with all the necessary logic to let you consume it transparently by just using: + +``` +implementation("com.facebook.react:react-android") +// or to keep backward compatibility with older versions of React Native: +implementation("com.facebook.react:react-native:+") +``` + +You should consider refactoring your library code to don't unzip/manipulate the React Native .aar files. + +This README.md file is kept in this folder as some libraries are checking the existence of the `/android` folder +and failing user builds if the folder is missing. diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 152a7667e4ca03..af47458d37f50a 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -14,6 +14,7 @@ import com.facebook.react.tasks.GenerateCodegenArtifactsTask import com.facebook.react.tasks.GenerateCodegenSchemaTask import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFields import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts +import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap import com.facebook.react.utils.DependencyUtils.configureDependencies import com.facebook.react.utils.DependencyUtils.configureRepositories import com.facebook.react.utils.DependencyUtils.readVersionString @@ -46,6 +47,7 @@ class ReactPlugin : Plugin { configureReactNativeNdk(project, extension) configureBuildConfigFields(project) configureDevPorts(project) + configureBackwardCompatibilityReactMap(project) project.extensions.getByType(AndroidComponentsExtension::class.java).apply { onVariants(selector().all()) { variant -> @@ -134,7 +136,6 @@ class ReactPlugin : Plugin { it.dependsOn(generateCodegenSchemaTask) it.reactNativeDir.set(extension.reactNativeDir) it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) - it.codegenDir.set(extension.codegenDir) it.generatedSrcDir.set(generatedSrcDir) it.packageJsonFile.set(findPackageJsonFile(project, extension)) it.codegenJavaPackageName.set(extension.codegenJavaPackageName) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt index 12830982d0cabe..b6003eeb5e14e6 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt @@ -26,8 +26,6 @@ abstract class GenerateCodegenArtifactsTask : Exec() { @get:Internal abstract val reactNativeDir: DirectoryProperty - @get:Internal abstract val codegenDir: DirectoryProperty - @get:Internal abstract val generatedSrcDir: DirectoryProperty @get:InputFile abstract val packageJsonFile: RegularFileProperty @@ -38,10 +36,6 @@ abstract class GenerateCodegenArtifactsTask : Exec() { @get:Input abstract val libraryName: Property - @get:InputFile - val combineJsToSchemaCli: Provider = - codegenDir.file("lib/cli/combine/combine-js-to-schema-cli.js") - @get:InputFile val generatedSchemaFile: Provider = generatedSrcDir.file("schema.json") diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/BackwardCompatUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/BackwardCompatUtils.kt new file mode 100644 index 00000000000000..9d6a015fdd2d51 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/BackwardCompatUtils.kt @@ -0,0 +1,49 @@ +/* + * 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.utils + +import java.io.File +import java.net.URI +import java.util.* +import org.gradle.api.Project +import org.gradle.api.artifacts.repositories.MavenArtifactRepository + +internal object BackwardCompatUtils { + + fun configureBackwardCompatibilityReactMap(project: Project) { + if (project.extensions.extraProperties.has("react")) { + @Suppress("UNCHECKED_CAST") + val reactMap = project.extensions.extraProperties.get("react") as? Map ?: mapOf() + if (reactMap.isNotEmpty()) { + project.logger.error( + """ + ******************************************************************************** + + ERROR: Using old project.ext.react configuration. + We identified that your project is using a old configuration block as: + + project.ext.react = [ + // ... + ] + + You should migrate to the new configuration: + + react { + // ... + } + You can find documentation inside `android/app/build.gradle` on how to use it. + + ******************************************************************************** + """.trimIndent()) + } + } + + // We set an empty react[] map so if a library is reading it, they will find empty values. + project.extensions.extraProperties.set("react", mapOf()) + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt index d11199ed2cdcf4..68480b6016bc7e 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt @@ -15,44 +15,57 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository internal object DependencyUtils { + /** + * This method takes care of configuring the repositories{} block for both the app and all the 3rd + * party libraries which are auto-linked. + */ fun configureRepositories(project: Project, reactNativeDir: File) { - with(project) { - if (hasProperty("REACT_NATIVE_MAVEN_LOCAL_REPO")) { - mavenRepoFromUrl("file://${property("REACT_NATIVE_MAVEN_LOCAL_REPO")}") + project.rootProject.allprojects { eachProject -> + with(eachProject) { + if (hasProperty("REACT_NATIVE_MAVEN_LOCAL_REPO")) { + mavenRepoFromUrl("file://${property("REACT_NATIVE_MAVEN_LOCAL_REPO")}") + } + // We add the snapshot for users on nightlies. + mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/") + repositories.mavenCentral() + // Android JSC is installed from npm + mavenRepoFromUrl( + "file://${reactNativeDir}${File.separator}..${File.separator}jsc-android${File.separator}dist") + repositories.google() + mavenRepoFromUrl("https://www.jitpack.io") } - // We add the snapshot for users on nightlies. - mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/") - repositories.mavenCentral() - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - mavenRepoFromUrl("file://${reactNativeDir}/android") - // Android JSC is installed from npm - mavenRepoFromUrl("file://${reactNativeDir}/../jsc-android/dist") - repositories.google() - mavenRepoFromUrl("https://www.jitpack.io") } } + /** + * This method takes care of configuring the resolution strategy for both the app and all the 3rd + * party libraries which are auto-linked. Specifically it takes care of: + * - Forcing the react-android/hermes-android version to the one specified in the package.json + * - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`. + */ fun configureDependencies(project: Project, versionString: String) { if (versionString.isBlank()) return - project.configurations.all { configuration -> - // Here we set a dependencySubstitution for both react-native and hermes-engine as those - // coordinates are voided due to https://github.com/facebook/react-native/issues/35210 - // This allows users to import libraries that are still using - // implementation("com.facebook.react:react-native:+") and resolve the right dependency. - configuration.resolutionStrategy.dependencySubstitution { - it.substitute(it.module("com.facebook.react:react-native")) - .using(it.module("com.facebook.react:react-android:${versionString}")) - .because( - "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210.") - it.substitute(it.module("com.facebook.react:hermes-engine")) - .using(it.module("com.facebook.react:hermes-android:${versionString}")) - .because( - "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210.") + project.rootProject.allprojects { eachProject -> + eachProject.configurations.all { configuration -> + // Here we set a dependencySubstitution for both react-native and hermes-engine as those + // coordinates are voided due to https://github.com/facebook/react-native/issues/35210 + // This allows users to import libraries that are still using + // implementation("com.facebook.react:react-native:+") and resolve the right dependency. + configuration.resolutionStrategy.dependencySubstitution { + it.substitute(it.module("com.facebook.react:react-native")) + .using(it.module("com.facebook.react:react-android:${versionString}")) + .because( + "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210.") + it.substitute(it.module("com.facebook.react:hermes-engine")) + .using(it.module("com.facebook.react:hermes-android:${versionString}")) + .because( + "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210.") + } + configuration.resolutionStrategy.force( + "com.facebook.react:react-android:${versionString}", + "com.facebook.react:hermes-android:${versionString}", + ) } - configuration.resolutionStrategy.force( - "com.facebook.react:react-android:${versionString}", - "com.facebook.react:hermes-android:${versionString}", - ) } } diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt index 86f45fec3207d8..e188e47c849525 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt @@ -29,28 +29,16 @@ class GenerateCodegenArtifactsTaskTest { val codegenDir = tempFolder.newFolder("codegen") val outputDir = tempFolder.newFolder("output") - val task = - createTestTask { - it.codegenDir.set(codegenDir) - it.generatedSrcDir.set(outputDir) - } + val task = createTestTask { it.generatedSrcDir.set(outputDir) } - assertEquals( - File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js"), - task.combineJsToSchemaCli.get().asFile) assertEquals(File(outputDir, "schema.json"), task.generatedSchemaFile.get().asFile) } @Test fun generateCodegenSchema_outputFile_isSetCorrectly() { - val codegenDir = tempFolder.newFolder("codegen") val outputDir = tempFolder.newFolder("output") - val task = - createTestTask { - it.codegenDir.set(codegenDir) - it.generatedSrcDir.set(outputDir) - } + val task = createTestTask { it.generatedSrcDir.set(outputDir) } assertEquals(File(outputDir, "java"), task.generatedJavaFiles.get().asFile) assertEquals(File(outputDir, "jni"), task.generatedJniFiles.get().asFile) @@ -80,13 +68,11 @@ class GenerateCodegenArtifactsTaskTest { @WithOs(OS.LINUX) fun setupCommandLine_willSetupCorrectly() { val reactNativeDir = tempFolder.newFolder("node_modules/react-native/") - val codegenDir = tempFolder.newFolder("codegen") val outputDir = tempFolder.newFolder("output") val task = createTestTask { it.reactNativeDir.set(reactNativeDir) - it.codegenDir.set(codegenDir) it.generatedSrcDir.set(outputDir) it.nodeExecutableAndArgs.set(listOf("--verbose")) } diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/BackwardCompatUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/BackwardCompatUtilsTest.kt new file mode 100644 index 00000000000000..e07630a5daa7e7 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/BackwardCompatUtilsTest.kt @@ -0,0 +1,45 @@ +/* + * 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.utils + +import com.facebook.react.tests.createProject +import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap +import org.gradle.api.logging.Logger +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class BackwardCompatUtilsTest { + + @get:Rule + val tempFolder = TemporaryFolder() + + @Test + fun configureBackwardCompatibilityReactMap_addsEmptyReactMap() { + val project = createProject() + + configureBackwardCompatibilityReactMap(project) + + assertTrue(project.extensions.extraProperties.has("react")) + @Suppress("UNCHECKED_CAST") + assertTrue((project.extensions.extraProperties.get("react") as Map).isEmpty()) + } + + @Test + fun configureBackwardCompatibilityReactMap_withExistingMapSetByUser_wipesTheMap() { + val project = createProject() + project.extensions.extraProperties.set("react", mapOf("enableHermes" to true)) + + configureBackwardCompatibilityReactMap(project) + + assertTrue(project.extensions.extraProperties.has("react")) + @Suppress("UNCHECKED_CAST") + assertTrue((project.extensions.extraProperties.get("react") as Map).isEmpty()) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt index 4386e33233d36e..70fba6e60972a9 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt @@ -14,6 +14,7 @@ import com.facebook.react.utils.DependencyUtils.mavenRepoFromUrl import com.facebook.react.utils.DependencyUtils.readVersionString import java.net.URI import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.testfixtures.ProjectBuilder import org.junit.Assert.* import org.junit.Rule import org.junit.Test @@ -51,21 +52,6 @@ class DependencyUtilsTest { }) } - @Test - fun configureRepositories_containsReactNativeNpmLocalMavenRepo() { - val projectFolder = tempFolder.newFolder() - val reactNativeDir = tempFolder.newFolder("react-native") - val repositoryURI = URI.create("file://${reactNativeDir}/android") - val project = createProject(projectFolder) - - configureRepositories(project, reactNativeDir) - - assertNotNull( - project.repositories.firstOrNull { - it is MavenArtifactRepository && it.url == repositoryURI - }) - } - @Test fun configureRepositories_containsJscLocalMavenRepo() { val projectFolder = tempFolder.newFolder() @@ -161,6 +147,46 @@ class DependencyUtilsTest { assertTrue(indexOfSnapshotRepo < indexOfMavenCentral) } + @Test + fun configureRepositories_appliesToAllProjects() { + val repositoryURI = URI.create("https://repo.maven.apache.org/maven2/") + val rootProject = ProjectBuilder.builder().build() + val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() + val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() + + configureRepositories(appProject, tempFolder.root) + + assertNotNull( + appProject.repositories.firstOrNull { + it is MavenArtifactRepository && it.url == repositoryURI + }) + assertNotNull( + libProject.repositories.firstOrNull { + it is MavenArtifactRepository && it.url == repositoryURI + }) + } + + @Test + fun configureRepositories_withPreviousExclusionRulesOnMavenCentral_appliesCorrectly() { + val repositoryURI = URI.create("https://repo.maven.apache.org/maven2/") + val rootProject = ProjectBuilder.builder().build() + val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() + val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() + + // Let's emulate a library which set an `excludeGroup` on `com.facebook.react` for Central. + libProject.repositories.mavenCentral { repo -> + repo.content { content -> content.excludeGroup("com.facebook.react") } + } + + configureRepositories(appProject, tempFolder.root) + + // We need to make sure we have Maven Central defined twice, one by the library, + // and another is the override by RNGP. + assertEquals( + 2, + libProject.repositories.count { it is MavenArtifactRepository && it.url == repositoryURI }) + } + @Test fun configureDependencies_withEmptyVersion_doesNothing() { val project = createProject() @@ -181,6 +207,24 @@ class DependencyUtilsTest { assertTrue(forcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" }) } + @Test + fun configureDependencies_withVersionString_appliesOnAllProjects() { + val rootProject = ProjectBuilder.builder().build() + val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() + val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() + appProject.plugins.apply("com.android.application") + libProject.plugins.apply("com.android.library") + + configureDependencies(appProject, "1.2.3") + + val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules + val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules + assertTrue(appForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) + assertTrue(appForcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" }) + assertTrue(libForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) + assertTrue(libForcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" }) + } + @Test fun readVersionString_withCorrectVersionString_returnsIt() { val propertiesFile =