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 c5ceebcdff5765..22628fa94e893e 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.android.build.gradle.internal.tasks.factory.dependsOn import com.facebook.react.tasks.BuildCodegenCLITask import com.facebook.react.tasks.GenerateCodegenArtifactsTask import com.facebook.react.tasks.GenerateCodegenSchemaTask +import com.facebook.react.utils.JsonUtils import java.io.File import kotlin.system.exitProcess import org.gradle.api.Plugin @@ -67,10 +68,10 @@ class ReactPlugin : Plugin { /** * A plugin to enable react-native-codegen in Gradle environment. See the Gradle API docs for more - * information: https://docs.gradle.org/6.5.1/javadoc/org/gradle/api/Project.html + * information: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html */ private fun applyCodegenPlugin(project: Project, extension: ReactExtension) { - // 1. Set up build dir. + // First, we set up the output dir for the codegen. val generatedSrcDir = File(project.buildDir, "generated/source/codegen") val buildCodegenTask = @@ -80,18 +81,33 @@ class ReactPlugin : Plugin { it.bashWindowsHome.set(bashWindowsHome) } - // 2. Task: produce schema from JS files. + // We create the task to produce schema from JS files. val generateCodegenSchemaTask = project.tasks.register( - "generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { + "generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { it -> it.dependsOn(buildCodegenTask) - it.jsRootDir.set(extension.jsRootDir) it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) it.codegenDir.set(extension.codegenDir) it.generatedSrcDir.set(generatedSrcDir) + + // We're reading the package.json at configuration time to properly feed + // the `jsRootDir` @Input property of this task. Therefore, the + // parsePackageJson should be invoked here. + val parsedPackageJson = + extension.root.file("package.json").orNull?.asFile?.let { + JsonUtils.fromCodegenJson(it) + } + + val parsedJsRootDir = + parsedPackageJson?.codegenConfig?.jsSrcsDir?.let { relativePath -> + extension.root.dir(relativePath) + } + ?: extension.jsRootDir + + it.jsRootDir.set(parsedJsRootDir) } - // 3. Task: generate Java code from schema. + // We create the task to generate Java code from schema. val generateCodegenArtifactsTask = project.tasks.register( "generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) { @@ -100,12 +116,13 @@ class ReactPlugin : Plugin { it.deprecatedReactRoot.set(extension.reactRoot) it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) it.codegenDir.set(extension.codegenDir) + it.generatedSrcDir.set(generatedSrcDir) + it.packageJsonFile.set(extension.root.file("package.json")) it.codegenJavaPackageName.set(extension.codegenJavaPackageName) it.libraryName.set(extension.libraryName) - it.generatedSrcDir.set(generatedSrcDir) } - // 4. Add dependencies & generated sources to the project. + // We add dependencies & generated sources to the project. // Note: This last step needs to happen after the project has been evaluated. project.afterEvaluate { diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelCodegenConfig.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelCodegenConfig.kt new file mode 100644 index 00000000000000..26d7f08d95d3c7 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelCodegenConfig.kt @@ -0,0 +1,15 @@ +/* + * 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 ModelCodegenConfig( + val name: String?, + val type: String?, + val jsSrcsDir: String?, + val android: ModelCodegenConfigAndroid? +) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelCodegenConfigAndroid.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelCodegenConfigAndroid.kt new file mode 100644 index 00000000000000..7619098a8dbc74 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelCodegenConfigAndroid.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 ModelCodegenConfigAndroid(val javaPackageName: String?) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelPackageJson.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelPackageJson.kt new file mode 100644 index 00000000000000..96e2bd22d704fb --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/model/ModelPackageJson.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 ModelPackageJson(val codegenConfig: ModelCodegenConfig?) 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 40470979469ef6..f00bfad9143f46 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 @@ -7,14 +7,20 @@ package com.facebook.react.tasks +import com.facebook.react.utils.JsonUtils import com.facebook.react.utils.windowsAwareCommandLine import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory abstract class GenerateCodegenArtifactsTask : Exec() { @@ -24,6 +30,8 @@ abstract class GenerateCodegenArtifactsTask : Exec() { @get:Internal abstract val generatedSrcDir: DirectoryProperty + @get:InputFile abstract val packageJsonFile: RegularFileProperty + @get:Input abstract val nodeExecutableAndArgs: ListProperty @get:Input abstract val codegenJavaPackageName: Property @@ -46,7 +54,9 @@ abstract class GenerateCodegenArtifactsTask : Exec() { override fun exec() { checkForDeprecatedProperty() - setupCommandLine() + + val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters() + setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName) super.exec() } @@ -74,7 +84,20 @@ abstract class GenerateCodegenArtifactsTask : Exec() { } } - internal fun setupCommandLine() { + internal fun resolveTaskParameters(): Pair { + val parsedPackageJson = + if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) { + JsonUtils.fromCodegenJson(packageJsonFile.get().asFile) + } else { + null + } + val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get() + val resolvedCodegenJavaPackageName = + parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get() + return resolvedLibraryName to resolvedCodegenJavaPackageName + } + + internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) { commandLine( windowsAwareCommandLine( *nodeExecutableAndArgs.get().toTypedArray(), @@ -86,8 +109,8 @@ abstract class GenerateCodegenArtifactsTask : Exec() { "--outputDir", generatedSrcDir.get().asFile.absolutePath, "--libraryName", - libraryName.get(), + libraryName, "--javaPackageName", - codegenJavaPackageName.get())) + codegenJavaPackageName)) } } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt index 7d372f07904dec..e0c44599205255 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt @@ -29,7 +29,12 @@ abstract class GenerateCodegenSchemaTask : Exec() { @get:Input abstract val nodeExecutableAndArgs: ListProperty - @get:InputFiles val jsInputFiles = project.fileTree(jsRootDir) { it.include("**/*.js") } + @get:InputFiles + val jsInputFiles = + project.fileTree(jsRootDir) { + it.include("**/*.js") + it.exclude("**/generated/source/codegen/**/*") + } @get:OutputFile val generatedSchemaFile: Provider = generatedSrcDir.file("schema.json") 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 new file mode 100644 index 00000000000000..4fa27510f484e1 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt @@ -0,0 +1,21 @@ +/* + * 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.model.ModelPackageJson +import com.google.gson.Gson +import java.io.File + +object JsonUtils { + private val gsonConverter = Gson() + + fun fromCodegenJson(input: File): ModelPackageJson? = + input.bufferedReader().use { + runCatching { gsonConverter.fromJson(it, ModelPackageJson::class.java) }.getOrNull() + } +} 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 44e02fec6a9df1..a559b036422cdb 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 @@ -58,11 +58,14 @@ class GenerateCodegenArtifactsTaskTest { @Test fun generateCodegenSchema_simpleProperties_areInsideInput() { + val packageJsonFile = tempFolder.newFile("package.json") + val task = createTestTask { it.nodeExecutableAndArgs.set(listOf("npm", "help")) it.codegenJavaPackageName.set("com.example.test") it.libraryName.set("example-test") + it.packageJsonFile.set(packageJsonFile) } assertEquals(listOf("npm", "help"), task.nodeExecutableAndArgs.get()) @@ -75,7 +78,7 @@ class GenerateCodegenArtifactsTaskTest { @Test @WithOs(OS.UNIX) - fun setupCommandLine_withoutJavaGenerator_willSetupCorrectly() { + fun setupCommandLine_willSetupCorrectly() { val reactNativeDir = tempFolder.newFolder("node_modules/react-native/") val codegenDir = tempFolder.newFolder("codegen") val outputDir = tempFolder.newFolder("output") @@ -86,11 +89,9 @@ class GenerateCodegenArtifactsTaskTest { it.codegenDir.set(codegenDir) it.generatedSrcDir.set(outputDir) it.nodeExecutableAndArgs.set(listOf("--verbose")) - it.codegenJavaPackageName.set("com.example.test") - it.libraryName.set("example-test") } - task.setupCommandLine() + task.setupCommandLine("example-test", "com.example.test") assertEquals( listOf( @@ -109,4 +110,79 @@ class GenerateCodegenArtifactsTaskTest { ), task.commandLine.toMutableList()) } + + @Test + fun resolveTaskParameters_withConfigInPackageJson_usesIt() { + val packageJsonFile = + tempFolder.newFile("package.json").apply { + // language=JSON + writeText( + """ + { + "name": "@a/libray", + "codegenConfig": { + "name": "an-awesome-library", + "android": { + "javaPackageName": "com.awesome.package" + } + } + } + """.trimIndent()) + } + + val task = + createTestTask { + it.packageJsonFile.set(packageJsonFile) + it.codegenJavaPackageName.set("com.example.ignored") + it.libraryName.set("a-library-name-that-is-ignored") + } + + val (libraryName, javaPackageName) = task.resolveTaskParameters() + + assertEquals("an-awesome-library", libraryName) + assertEquals("com.awesome.package", javaPackageName) + } + + @Test + fun resolveTaskParameters_withConfigMissingInPackageJson_usesGradleOne() { + val packageJsonFile = + tempFolder.newFile("package.json").apply { + // language=JSON + writeText( + """ + { + "name": "@a/libray", + "codegenConfig": { + } + } + """.trimIndent()) + } + + val task = + createTestTask { + it.packageJsonFile.set(packageJsonFile) + it.codegenJavaPackageName.set("com.example.test") + it.libraryName.set("a-library-name-from-gradle") + } + + val (libraryName, javaPackageName) = task.resolveTaskParameters() + + assertEquals("a-library-name-from-gradle", libraryName) + assertEquals("com.example.test", javaPackageName) + } + + @Test + fun resolveTaskParameters_withMissingPackageJson_usesGradleOne() { + val task = + createTestTask { + it.packageJsonFile.set(File(tempFolder.root, "package.json")) + it.codegenJavaPackageName.set("com.example.test") + it.libraryName.set("a-library-name-from-gradle") + } + + val (libraryName, javaPackageName) = task.resolveTaskParameters() + + assertEquals("a-library-name-from-gradle", libraryName) + assertEquals("com.example.test", javaPackageName) + } } 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 new file mode 100644 index 00000000000000..d52bd985ac03dd --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt @@ -0,0 +1,92 @@ +/* + * 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 org.intellij.lang.annotations.Language +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class JsonUtilsTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun withInvalidJson_returnsNull() { + val invalidJson = createJsonFile("""¯\_(ツ)_/¯""") + + assertNull(JsonUtils.fromCodegenJson(invalidJson)) + } + + @Test + fun withEmptyJson_returnsEmptyObject() { + val invalidJson = createJsonFile("""{}""") + + val parsed = JsonUtils.fromCodegenJson(invalidJson) + + assertNotNull(parsed) + assertNull(parsed?.codegenConfig) + } + + @Test + fun withOldJsonConfig_returnsAnEmptyLibrary() { + val oldJsonConfig = + createJsonFile( + """ + { + "name": "yet another npm package", + "codegenConfig": { + "libraries": [ + { + "name": "an awesome library", + "jsSrcsDir": "../js/", + "android": {} + } + ] + } + } + """.trimIndent()) + + val parsed = JsonUtils.fromCodegenJson(oldJsonConfig)!! + + assertNull(parsed.codegenConfig?.name) + assertNull(parsed.codegenConfig?.jsSrcsDir) + assertNull(parsed.codegenConfig?.android) + } + + @Test + fun withValidJson_parsesCorrectly() { + val validJson = + createJsonFile( + """ + { + "name": "yet another npm package", + "codegenConfig": { + "name": "an awesome library", + "jsSrcsDir": "../js/", + "android": { + "javaPackageName": "com.awesome.library" + }, + "ios": { + "other ios only keys": "which are ignored during parsing" + } + } + } + """.trimIndent()) + + val parsed = JsonUtils.fromCodegenJson(validJson)!! + + assertEquals("an awesome library", parsed.codegenConfig!!.name) + assertEquals("../js/", parsed.codegenConfig!!.jsSrcsDir) + assertEquals("com.awesome.library", parsed.codegenConfig!!.android!!.javaPackageName) + } + + private fun createJsonFile(@Language("JSON") input: String) = + tempFolder.newFile().apply { writeText(input) } +} diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index bd10c322f3956f..06c914e763f8c5 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -80,17 +80,15 @@ react { cliPath = "../../../../cli.js" bundleAssetName = "RNTesterApp.android.bundle" entryFile = file("../../js/RNTesterApp.android.js") - root = rootDir + root = file("../../") inputExcludes = ["android/**", "./**", ".gradle/**"] composeSourceMapsPath = "$rootDir/scripts/compose-source-maps.js" hermesCommand = "$rootDir/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" enableHermesForVariant { def v -> v.name.contains("hermes") } // Codegen Configs - jsRootDir = file("$rootDir/packages/rn-tester") reactNativeDir = rootDir - libraryName = "rntester" - useJavaGenerator = System.getenv("USE_CODEGEN_JAVAPOET")?.toBoolean() ?: false + codegenDir = new File(rootDir, "node_modules/react-native-codegen") } project.ext.react = [ diff --git a/packages/rn-tester/android/app/src/main/jni/Android.mk b/packages/rn-tester/android/app/src/main/jni/Android.mk index a912fec85e1585..ec6991cfb589cd 100644 --- a/packages/rn-tester/android/app/src/main/jni/Android.mk +++ b/packages/rn-tester/android/app/src/main/jni/Android.mk @@ -15,7 +15,7 @@ LOCAL_PATH := $(THIS_DIR) include $(CLEAR_VARS) LOCAL_MODULE := rntester_appmodules -# Note: We are linking against react_codegen_rntester hence no need to built the react-native-codegen output. +# Note: We are linking against react_codegen_AppSpecs hence no need to built the react-native-codegen output. LOCAL_C_INCLUDES := $(LOCAL_PATH) $(GENERATED_SRC_DIR)/codegen/jni LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) LOCAL_SRC_FILES := $(subst $(LOCAL_PATH)/,,$(LOCAL_SRC_FILES)) @@ -26,7 +26,7 @@ LOCAL_SHARED_LIBRARIES := \ libfolly_runtime \ libglog \ libreact_codegen_rncore \ - libreact_codegen_rntester \ + libreact_codegen_AppSpecs \ libreact_debug \ libreact_nativemodule_core \ libreact_render_componentregistry \ diff --git a/packages/rn-tester/android/app/src/main/jni/RNTesterAppModuleProvider.cpp b/packages/rn-tester/android/app/src/main/jni/RNTesterAppModuleProvider.cpp index 76104b7455ccd9..5b55ce4a9de9bd 100644 --- a/packages/rn-tester/android/app/src/main/jni/RNTesterAppModuleProvider.cpp +++ b/packages/rn-tester/android/app/src/main/jni/RNTesterAppModuleProvider.cpp @@ -7,9 +7,9 @@ #include "RNTesterAppModuleProvider.h" +#include #include #include -#include namespace facebook { namespace react { @@ -17,7 +17,7 @@ namespace react { std::shared_ptr RNTesterAppModuleProvider( const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { - auto module = rntester_ModuleProvider(moduleName, params); + auto module = AppSpecs_ModuleProvider(moduleName, params); if (module != nullptr) { return module; } diff --git a/packages/rn-tester/android/app/src/main/jni/RNTesterComponentsRegistry.cpp b/packages/rn-tester/android/app/src/main/jni/RNTesterComponentsRegistry.cpp index 398bd4d2528037..ad8370b19e2311 100644 --- a/packages/rn-tester/android/app/src/main/jni/RNTesterComponentsRegistry.cpp +++ b/packages/rn-tester/android/app/src/main/jni/RNTesterComponentsRegistry.cpp @@ -10,8 +10,8 @@ #include #include #include +#include #include -#include namespace facebook { namespace react { diff --git a/packages/rn-tester/package.json b/packages/rn-tester/package.json index 4a34e465562613..6fbbfdb387c1d5 100644 --- a/packages/rn-tester/package.json +++ b/packages/rn-tester/package.json @@ -33,6 +33,9 @@ "codegenConfig": { "name": "AppSpecs", "type": "all", - "jsSrcsDir": "." + "jsSrcsDir": ".", + "android": { + "javaPackageName": "com.facebook.fbreact.specs" + } } }