diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt index 6c09759a0819..4e7573876540 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt @@ -7,15 +7,12 @@ package com.facebook.react -import com.android.build.gradle.api.BaseVariant import com.facebook.react.utils.projectPathToLibraryName -import java.io.File import javax.inject.Inject import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property abstract class ReactExtension @Inject constructor(project: Project) { @@ -54,10 +51,10 @@ abstract class ReactExtension @Inject constructor(project: Project) { val bundleCommand: Property = objects.property(String::class.java).convention("bundle") /** - * Custom configuration for the [bundleCommand]. If provided it will be passed over with a + * Custom configuration file for the [bundleCommand]. If provided, it will be passed over with a * `--config` flag to the bundle command. */ - val bundleConfig: Property = objects.property(String::class.java) + val bundleConfig: RegularFileProperty = objects.fileProperty() /** * The Bundle Asset name. This name will be used also for deriving other bundle outputs such as @@ -69,69 +66,24 @@ abstract class ReactExtension @Inject constructor(project: Project) { objects.property(String::class.java).convention("index.android.bundle") /** - * Variant Name to File destination map that allows to specify where is the resource dir for a - * specific variant. If a value is supplied, the plugin will copy the bundled resource for that - * variant from `generated/res/react/` into the custom specified location. Default: {} + * Toggles the .so Cleanup step. If enabled, we will clean up all the unnecessary files before the + * bundle task. If disabled, the developers will have to manually cleanup the files. Default: true */ - val resourcesDir: MapProperty = - objects.mapProperty(String::class.java, File::class.java).convention(emptyMap()) - - /** - * Variant Name to File destination map that allows to specify where is the asset dir for a - * specific variant. If a value is supplied, the plugin will copy the bundled JS for that variant - * from `generated/assets/react/` into the custom specified location. Default: {} - */ - val jsBundleDir: MapProperty = - objects.mapProperty(String::class.java, File::class.java).convention(emptyMap()) - - /** ANT-style excludes for the bundle command. Default: ["android / **", "ios / **"] */ - val inputExcludes: ListProperty = - objects.listProperty(String::class.java).convention(listOf("android/**", "ios/**")) - - /** - * Toggles the VM Cleanup step. If enabled, before the bundle task we will clean up all the - * unnecessary files. If disabled, the developers will have to manually cleanup the files. - * Default: true - */ - val enableVmCleanup: Property = objects.property(Boolean::class.java).convention(true) + val enableSoCleanup: Property = objects.property(Boolean::class.java).convention(true) /** Extra args that will be passed to the [bundleCommand] Default: [] */ val extraPackagerArgs: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) /** - * Allows to disable dev mode for certain variants. That's useful if you have a production variant - * (say `canary`) where you don't want dev mode to be enabled. Default: [] + * Allows to specify the debuggable variants (by default just 'debug'). Variants in this list + * will: + * - Not be bundled (the bundle file will not be created and won't be copied over). + * - Have the Hermes Debug flags set. That's useful if you have another variant (say `canary`) + * where you want dev mode to be enabled. Default: ['debug'] */ - val devDisabledInVariants: ListProperty = - objects.listProperty(String::class.java).convention(emptyList()) - - /** - * Functional interface to disable dev mode only on specific [BaseVariant] Default: will check - * [devDisabledInVariants] or return True for Release variants and False for Debug variants. - */ - var disableDevForVariant: (BaseVariant) -> Boolean = { variant -> - variant.name in devDisabledInVariants.get() || variant.isRelease - } - - /** - * Variant Name to Boolean map that allows to toggle the bundle command for a specific variant. - * Default: {} - */ - // todo maybe lambda as for hermes? - val bundleIn: MapProperty = - objects.mapProperty(String::class.java, Boolean::class.java).convention(emptyMap()) - - /** - * Functional interface to toggle the bundle command only on specific [BaseVariant] Default: will - * check [bundleIn] or return True for Release variants and False for Debug variants. - */ - var bundleForVariant: (BaseVariant) -> Boolean = { variant -> - if (bundleIn.getting(variant.name).isPresent) bundleIn.getting(variant.name).get() - else if (bundleIn.getting(variant.buildType.name).isPresent) - bundleIn.getting(variant.buildType.name).get() - else variant.isRelease - } + val debuggableVariants: ListProperty = + objects.listProperty(String::class.java).convention(listOf("debug")) /** Hermes Config */ @@ -141,35 +93,18 @@ abstract class ReactExtension @Inject constructor(project: Project) { */ val hermesCommand: Property = objects.property(String::class.java).convention("") - /** Toggle Hermes for the whole build. Default: false */ - val enableHermes: Property = objects.property(Boolean::class.java).convention(false) - - /** - * Functional interface to selectively enabled Hermes only on specific [BaseVariant] Default: will - * return [enableHermes] for all the variants. - */ - var enableHermesForVariant: (BaseVariant) -> Boolean = { enableHermes.get() } - - /** - * Functional interface specify flags for Hermes on specific [BaseVariant] Default: will return - * [hermesFlagsRelease] for Release variants and [hermesFlagsDebug] for Debug variants. - */ - var hermesFlagsForVariant: (BaseVariant) -> List = { variant -> - if (variant.isRelease) hermesFlagsRelease.get() else hermesFlagsDebug.get() - } - /** - * Functional interface to delete debug files only on specific [BaseVariant] Default: will return - * True for Release variants and False for Debug variants. + * Whether to enable Hermes only on certain variants. If specified as a non-empty list, hermesc + * and the .so cleanup for Hermes will be executed only for variants in this list. An empty list + * assumes you're either using Hermes for all variants or not (see [enableHermes]). + * + * Default: [] */ - var deleteDebugFilesForVariant: (BaseVariant) -> Boolean = { variant -> variant.isRelease } - - /** Flags to pass to Hermes for Debug variants. Default: [] */ - val hermesFlagsDebug: ListProperty = + val enableHermesOnlyInVariants: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) - /** Flags to pass to Hermes for Release variants. Default: ["-O", "-output-source-map"] */ - val hermesFlagsRelease: ListProperty = + /** Flags to pass to Hermesc. Default: ["-O", "-output-source-map"] */ + val hermesFlags: ListProperty = objects.listProperty(String::class.java).convention(listOf("-O", "-output-source-map")) /** @@ -221,4 +156,11 @@ abstract class ReactExtension @Inject constructor(project: Project) { */ val codegenJavaPackageName: Property = objects.property(String::class.java).convention("com.facebook.fbreact.specs") + + /** + * Toggles the Codegen for App modules. By default we enable the codegen only for library modules. + * If you want to enable codegen in your app, you will have to set this to true. Default: false + */ + val enableCodegenInApps: Property = + objects.property(Boolean::class.java).convention(false) } 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 034a6be090f8..24790daa83c5 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 @@ -8,20 +8,20 @@ package com.facebook.react import com.android.build.api.variant.AndroidComponentsExtension -import com.android.build.gradle.AppExtension -import com.android.build.gradle.LibraryExtension 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.AgpConfiguratorUtils.configureBuildConfigFields import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts +import com.facebook.react.utils.DependencyUtils.configureDependencies +import com.facebook.react.utils.DependencyUtils.configureRepositories +import com.facebook.react.utils.DependencyUtils.readVersionString import com.facebook.react.utils.JsonUtils import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk import com.facebook.react.utils.findPackageJsonFile import java.io.File import kotlin.system.exitProcess -import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -31,8 +31,31 @@ class ReactPlugin : Plugin { override fun apply(project: Project) { checkJvmVersion(project) val extension = project.extensions.create("react", ReactExtension::class.java, project) - applyAppPlugin(project, extension) - applyCodegenPlugin(project, extension) + + // App Only Configuration + project.pluginManager.withPlugin("com.android.application") { + project.afterEvaluate { + val reactNativeDir = extension.reactNativeDir.get().asFile + val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties") + val versionString = readVersionString(propertiesFile) + configureDependencies(project, versionString) + configureRepositories(project, reactNativeDir) + } + + configureReactNativeNdk(project, extension) + configureBuildConfigFields(project) + configureDevPorts(project) + + project.extensions.getByType(AndroidComponentsExtension::class.java).onVariants { variant -> + project.configureReactTasks(variant = variant, config = extension) + } + configureCodegen(project, extension, isLibrary = false) + } + + // Library Only Configuration + project.pluginManager.withPlugin("com.android.library") { + configureCodegen(project, extension, isLibrary = true) + } } private fun checkJvmVersion(project: Project) { @@ -54,30 +77,12 @@ class ReactPlugin : Plugin { } } - private fun applyAppPlugin(project: Project, config: ReactExtension) { - project.pluginManager.withPlugin("com.android.application") { - configureReactNativeNdk(project, config) - configureBuildConfigFields(project) - configureDevPorts(project) - project.afterEvaluate { - val isAndroidLibrary = project.plugins.hasPlugin("com.android.library") - val variants = - if (isAndroidLibrary) { - project.extensions.getByType(LibraryExtension::class.java).libraryVariants - } else { - project.extensions.getByType(AppExtension::class.java).applicationVariants - } - variants.all { project.configureReactTasks(variant = it, config = config) } - } - } - } - /** * A plugin to enable react-native-codegen in Gradle environment. See the Gradle API docs for more * information: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html */ @Suppress("UnstableApiUsage") - private fun applyCodegenPlugin(project: Project, extension: ReactExtension) { + private fun configureCodegen(project: Project, extension: ReactExtension, isLibrary: Boolean) { // First, we set up the output dir for the codegen. val generatedSrcDir = File(project.buildDir, "generated/source/codegen") @@ -86,6 +91,7 @@ class ReactPlugin : Plugin { it.codegenDir.set(extension.codegenDir) val bashWindowsHome = project.findProperty("REACT_WINDOWS_BASH") as String? it.bashWindowsHome.set(bashWindowsHome) + it.onlyIf { isLibrary || extension.enableCodegenInApps.get() } } // We create the task to produce schema from JS files. @@ -96,6 +102,7 @@ class ReactPlugin : Plugin { it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) it.codegenDir.set(extension.codegenDir) it.generatedSrcDir.set(generatedSrcDir) + it.onlyIf { isLibrary || extension.enableCodegenInApps.get() } // We're reading the package.json at configuration time to properly feed // the `jsRootDir` @Input property of this task. Therefore, the @@ -123,6 +130,7 @@ class ReactPlugin : Plugin { it.packageJsonFile.set(findPackageJsonFile(project, extension)) it.codegenJavaPackageName.set(extension.codegenJavaPackageName) it.libraryName.set(extension.libraryName) + it.onlyIf { isLibrary || extension.enableCodegenInApps.get() } } // We update the android configuration to include the generated sources. @@ -133,12 +141,8 @@ class ReactPlugin : Plugin { ext.sourceSets.getByName("main").java.srcDir(File(generatedSrcDir, "java")) } - // `preBuild` is one of the base tasks automatically registered by Gradle. + // `preBuild` is one of the base tasks automatically registered by AGP. // This will invoke the codegen before compiling the entire project. - val androidPluginHandler = Action { _: Plugin<*> -> - project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) - } - project.plugins.withId("com.android.application", androidPluginHandler) - project.plugins.withId("com.android.library", androidPluginHandler) + project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) } } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt index bba9e91f959c..4ec27e9f559d 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -7,246 +7,95 @@ package com.facebook.react -import com.android.build.gradle.api.ApplicationVariant -import com.android.build.gradle.api.BaseVariant -import com.android.build.gradle.api.LibraryVariant -import com.android.build.gradle.internal.tasks.factory.dependsOn -import com.facebook.react.tasks.BundleJsAndAssetsTask -import com.facebook.react.tasks.HermesBinaryTask +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.Variant +import com.facebook.react.tasks.BundleHermesCTask +import com.facebook.react.tasks.NativeLibraryAabCleanupTask +import com.facebook.react.tasks.NativeLibraryApkCleanupTask +import com.facebook.react.utils.ProjectUtils.isHermesEnabled import com.facebook.react.utils.detectedCliPath import com.facebook.react.utils.detectedEntryFile import java.io.File import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.tasks.Copy -import org.gradle.api.tasks.TaskProvider -private const val REACT_GROUP = "react" - -@Suppress("SpreadOperator") -internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactExtension) { +@Suppress("SpreadOperator", "UnstableApiUsage") +internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) { val targetName = variant.name.replaceFirstChar { it.uppercase() } - val targetPath = variant.dirName + val targetPath = variant.name // React js bundle directories - val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath") val resourcesDir = File(buildDir, "generated/res/react/$targetPath") - - val bundleAssetName = config.bundleAssetName.get() - val jsBundleFile = File(jsBundleDir, bundleAssetName) + // Bundle: generated/assets/react/path/index.android.bundle + val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath") + // Sourcemap: generated/sourcemaps/react/path/index.android.bundle.map val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath") + // Intermediate packager: intermediates/sourcemaps/react/path/index.android.bundle.packager.map + // Intermediate compiler: intermediates/sourcemaps/react/path/index.android.bundle.compiler.map val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath") - val jsPackagerSourceMapFile = File(jsIntermediateSourceMapsDir, "${bundleAssetName}.packager.map") - val jsCompilerSourceMapFile = File(jsIntermediateSourceMapsDir, "${bundleAssetName}.compiler.map") - val jsOutputSourceMapFile = File(jsSourceMapsDir, "${bundleAssetName}.map") // Additional node and packager commandline arguments - val nodeExecutableAndArgs = config.nodeExecutableAndArgs.get() val cliPath = detectedCliPath(project.projectDir, config) - val execCommand = nodeExecutableAndArgs + cliPath - val enableHermes = config.enableHermesForVariant(variant) - val cleanup = config.deleteDebugFilesForVariant(variant) - val bundleEnabled = config.bundleForVariant(variant) - - val bundleTask = - tasks.register("createBundle${targetName}JsAndAssets", BundleJsAndAssetsTask::class.java) { - it.group = REACT_GROUP - it.description = "create JS bundle and assets for $targetName." - - it.reactRoot = config.root.get().asFile - it.sources = - fileTree(config.root) { fileTree -> fileTree.setExcludes(config.inputExcludes.get()) } - it.execCommand = execCommand - it.bundleCommand = config.bundleCommand.get() - it.devEnabled = !config.disableDevForVariant(variant) - it.entryFile = detectedEntryFile(config) - - val extraArgs = mutableListOf() - - if (config.bundleConfig.isPresent) { - extraArgs.add("--config") - extraArgs.add(config.bundleConfig.get()) - } - - // Hermes doesn't require JS minification. - if (enableHermes && !it.devEnabled) { - extraArgs.add("--minify") - extraArgs.add("false") - } - - extraArgs.addAll(config.extraPackagerArgs.get()) - - it.extraArgs = extraArgs - - it.jsBundleDir = jsBundleDir - it.jsBundleFile = jsBundleFile - it.resourcesDir = resourcesDir - it.jsIntermediateSourceMapsDir = jsIntermediateSourceMapsDir - it.jsSourceMapsDir = jsSourceMapsDir - it.jsSourceMapsFile = if (enableHermes) jsPackagerSourceMapFile else jsOutputSourceMapFile - - it.enabled = bundleEnabled - } - - val hermesTask = - tasks.register("emit${targetName}HermesResources", HermesBinaryTask::class.java) { - it.group = REACT_GROUP - it.description = "bundle hermes resources for $targetName" - - it.root = config.root.get().asFile - it.hermesCommand = config.hermesCommand.get() - it.hermesFlags = config.hermesFlagsForVariant(variant) - it.jsBundleFile = jsBundleFile - it.composeSourceMapsCommand = nodeExecutableAndArgs + config.composeSourceMapsPath.get() - it.jsPackagerSourceMapFile = jsPackagerSourceMapFile - it.jsCompilerSourceMapFile = jsCompilerSourceMapFile - it.jsOutputSourceMapFile = jsOutputSourceMapFile - - it.dependsOn(bundleTask) - - it.enabled = bundleEnabled && enableHermes - } - - val aggregatedBundleTask = - tasks.register("bundle${targetName}JsAndAssets") { - it.group = REACT_GROUP - it.description = "bundle JS and resources for $targetName" - - it.dependsOn(bundleTask, hermesTask) - - // this was exposed before, do we still need it? - it.extensions.extraProperties["generatedResFolders"] = files(resourcesDir).builtBy(it) - it.extensions.extraProperties["generatedAssetsFolders"] = files(jsBundleDir).builtBy(it) - } - - val generatedResFolders = files(resourcesDir).builtBy(aggregatedBundleTask) - - // Android configuration - variant.registerGeneratedResFolders(generatedResFolders) - - val packageTask: TaskProvider? = - when (variant) { - is ApplicationVariant -> variant.packageApplicationProvider - is LibraryVariant -> variant.packageLibraryProvider - else -> tasks.named("package$targetName") + val enableHermesInProject = project.isHermesEnabled + val enableHermesInThisVariant = + if (config.enableHermesOnlyInVariants.get().isNotEmpty()) { + config.enableHermesOnlyInVariants.get().contains(variant.name) && enableHermesInProject + } else { + enableHermesInProject } - val stripDebugSymbolsTask: TaskProvider? = tasks.named("strip${targetName}DebugSymbols") - val mergeNativeLibsTask: TaskProvider? = tasks.named("merge${targetName}NativeLibs") - - val mergeResourcesTask = variant.mergeResourcesProvider - val mergeAssetsTask = variant.mergeAssetsProvider - val preBundleTask = tasks.named("build${targetName}PreBundle") - - val resourcesDirConfigValue = config.resourcesDir.getting(variant.name) - if (resourcesDirConfigValue.isPresent) { - val currentCopyResTask = - tasks.register("copy${targetName}BundledResources", Copy::class.java) { - it.group = "react" - it.description = "copy bundled resources into custom location for $targetName." - - it.from(resourcesDir) - it.into(file(resourcesDirConfigValue.get())) - - it.dependsOn(bundleTask) - - it.enabled = bundleEnabled + val isDebuggableVariant = + config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) } + + if (!isDebuggableVariant) { + val bundleTask = + tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) { + it.root.set(config.root) + it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs) + it.cliPath.set(cliPath) + it.bundleCommand.set(config.bundleCommand) + it.entryFile.set(detectedEntryFile(config)) + it.extraPackagerArgs.set(config.extraPackagerArgs) + it.bundleConfig.set(config.bundleConfig) + it.bundleAssetName.set(config.bundleAssetName) + it.jsBundleDir.set(jsBundleDir) + it.resourcesDir.set(resourcesDir) + it.hermesEnabled.set(enableHermesInThisVariant) + it.minifyEnabled.set(!enableHermesInThisVariant) + it.devEnabled.set(false) + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + it.jsSourceMapsDir.set(jsSourceMapsDir) + it.hermesCommand.set(config.hermesCommand) + it.hermesFlags.set(config.hermesFlags) + it.composeSourceMapsPath.set(config.composeSourceMapsPath) } - - packageTask?.dependsOn(currentCopyResTask) - preBundleTask.dependsOn(currentCopyResTask) + variant.sources.res?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::resourcesDir) + variant.sources.assets?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::jsBundleDir) } - packageTask?.configure { - if (config.enableVmCleanup.get()) { - val libDir = "$buildDir/intermediates/transforms/" - val targetVariant = ".*/transforms/[^/]*/${variant.name}/.*".toRegex() - it.doFirst { cleanupVMFiles(libDir, targetVariant, enableHermes, cleanup) } - } + if (config.enableSoCleanup.get()) { + val nativeLibraryApkCleanupTask = + project.tasks.register( + "nativeLibrary${targetName}ApkCleanup", NativeLibraryApkCleanupTask::class.java) { + it.debuggableVariant.set(isDebuggableVariant) + it.enableHermes.set(enableHermesInThisVariant) + } + val nativeLibraryBundleCleanupTask = + project.tasks.register( + "nativeLibrary${targetName}BundleCleanup", NativeLibraryAabCleanupTask::class.java) { + it.debuggableVariant.set(isDebuggableVariant) + it.enableHermes.set(enableHermesInThisVariant) + } + + variant.artifacts + .use(nativeLibraryApkCleanupTask) + .wiredWithDirectories( + NativeLibraryApkCleanupTask::inputApkDirectory, + NativeLibraryApkCleanupTask::outputApkDirectory) + .toTransform(SingleArtifact.APK) + variant.artifacts + .use(nativeLibraryBundleCleanupTask) + .wiredWithFiles( + NativeLibraryAabCleanupTask::inputBundle, NativeLibraryAabCleanupTask::outputBundle) + .toTransform(SingleArtifact.BUNDLE) } - - stripDebugSymbolsTask?.configure { - if (config.enableVmCleanup.get()) { - val libDir = "$buildDir/intermediates/stripped_native_libs/${variant.name}/out/lib/" - val targetVariant = ".*/stripped_native_libs/${variant.name}/out/lib/.*".toRegex() - it.doLast { cleanupVMFiles(libDir, targetVariant, enableHermes, cleanup) } - } - } - - mergeNativeLibsTask?.configure { - if (config.enableVmCleanup.get()) { - val libDir = "$buildDir/intermediates/merged_native_libs/${variant.name}/out/lib/" - val targetVariant = ".*/merged_native_libs/${variant.name}/out/lib/.*".toRegex() - it.doLast { cleanupVMFiles(libDir, targetVariant, enableHermes, cleanup) } - } - } - - val currentAssetsCopyTask = - tasks.register("copy${targetName}BundledJs", Copy::class.java) { - it.group = "react" - it.description = "copy bundled JS into $targetName." - - it.from(jsBundleDir) - - val jsBundleDirConfigValue = config.jsBundleDir.getting(targetName) - if (jsBundleDirConfigValue.isPresent) { - it.into(jsBundleDirConfigValue.get()) - } else { - it.into(mergeAssetsTask.map { mergeFoldersTask -> mergeFoldersTask.outputDir.get() }) - // Workaround for Android Gradle Plugin 7.3 asset directory - it.into("$buildDir/intermediates/assets/${variant.name}") - } - - it.dependsOn(mergeAssetsTask) - - it.enabled = bundleEnabled - } - - // mergeResources task runs before the bundle file is copied to the intermediate asset directory - // from Android plugin 4.1+. - // This ensures to copy the bundle file before mergeResources task starts - mergeResourcesTask.dependsOn(currentAssetsCopyTask) - packageTask?.dependsOn(currentAssetsCopyTask) - preBundleTask.dependsOn(currentAssetsCopyTask) } - -private fun Project.cleanupVMFiles( - libDir: String, - targetVariant: Regex, - enableHermes: Boolean, - cleanup: Boolean -) { - // Delete the VM related libraries that this build doesn't need. - // The application can manage this manually by setting 'enableVmCleanup: false' - // - // This should really be done by packaging all Hermes related libs into - // two separate HermesDebug and HermesRelease AARs, but until then we'll - // kludge it by deleting the .so files out of the /transforms/ directory. - fileTree(libDir) { - if (enableHermes) { - // For Hermes, delete all the libjsc* files - it.include("**/libjsc*.so") - - if (cleanup) { - // Reduce size by deleting the debugger/inspector - it.include("**/libhermes-executor-debug.so") - } else { - // Release libs take precedence and must be removed - // to allow debugging - it.include("**/libhermes-executor-release.so") - } - } else { - // For JSC, delete all the libhermes* files - it.include("**/libhermes*.so") - } - } - .visit { visit -> - val path = visit.file.absolutePath.replace(File.separatorChar, '/') - if (path.matches(targetVariant) && visit.file.isFile) { - visit.file.delete() - } - } -} - -internal val BaseVariant.isRelease: Boolean - get() = name.lowercase().contains("release") diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt new file mode 100644 index 000000000000..c1dfb58fd136 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt @@ -0,0 +1,185 @@ +/* + * 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.tasks + +import com.facebook.react.utils.detectOSAwareHermesCommand +import com.facebook.react.utils.moveTo +import com.facebook.react.utils.windowsAwareCommandLine +import java.io.File +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* + +abstract class BundleHermesCTask : DefaultTask() { + + init { + group = "react" + } + + @get:Internal abstract val root: DirectoryProperty + + @get:InputFiles + val sources: ConfigurableFileTree = + project.fileTree(root) { + it.include("**/*.js") + it.include("**/*.jsx") + it.include("**/*.ts") + it.include("**/*.tsx") + it.exclude("**/android/**/*") + it.exclude("**/ios/**/*") + it.exclude("**/build/**/*") + it.exclude("**/node_modules/**/*") + } + + @get:Input abstract val nodeExecutableAndArgs: ListProperty + + @get:Input abstract val cliPath: Property + + @get:Input abstract val composeSourceMapsPath: Property + + @get:Input abstract val bundleCommand: Property + + @get:InputFile abstract val entryFile: RegularFileProperty + + @get:InputFile @get:Optional abstract val bundleConfig: RegularFileProperty + + @get:Input abstract val bundleAssetName: Property + + @get:Input abstract val minifyEnabled: Property + + @get:Input abstract val hermesEnabled: Property + + @get:Input abstract val devEnabled: Property + + @get:Input abstract val extraPackagerArgs: ListProperty + + @get:Input abstract val hermesCommand: Property + + @get:Input abstract val hermesFlags: ListProperty + + @get:OutputDirectory abstract val jsBundleDir: DirectoryProperty + + @get:OutputDirectory abstract val resourcesDir: DirectoryProperty + + @get:OutputDirectory abstract val jsIntermediateSourceMapsDir: RegularFileProperty + + @get:OutputDirectory abstract val jsSourceMapsDir: DirectoryProperty + + @TaskAction + fun run() { + jsBundleDir.get().asFile.mkdirs() + resourcesDir.get().asFile.mkdirs() + jsIntermediateSourceMapsDir.get().asFile.mkdirs() + jsSourceMapsDir.get().asFile.mkdirs() + val bundleAssetFilename = bundleAssetName.get() + + val bundleFile = File(jsBundleDir.get().asFile, bundleAssetFilename) + val packagerSourceMap = resolvePackagerSourceMapFile(bundleAssetFilename) + + val bundleCommand = getBundleCommand(bundleFile, packagerSourceMap) + runCommand(bundleCommand) + + if (hermesEnabled.get()) { + val detectedHermesCommand = detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get()) + val bytecodeTempFile = File("${bundleFile}.hbc") + val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename) + val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename) + + val hermesCommand = getHermescCommand(detectedHermesCommand, bundleFile, packagerSourceMap) + runCommand(hermesCommand) + bytecodeTempFile.moveTo(bundleFile) + + if (hermesFlags.get().contains("-output-source-map")) { + val hermesTempSourceMapFile = File("$bytecodeTempFile.map") + hermesTempSourceMapFile.moveTo(compilerSourceMap) + val composeSourceMapsCommand = + getComposeSourceMapsCommand(packagerSourceMap, compilerSourceMap, outputSourceMap) + runCommand(composeSourceMapsCommand) + } + } + } + + internal fun resolvePackagerSourceMapFile(bundleAssetName: String) = + if (hermesEnabled.get()) { + File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.packager.map") + } else { + resolveOutputSourceMap(bundleAssetName) + } + + internal fun resolveOutputSourceMap(bundleAssetName: String) = + File(jsSourceMapsDir.get().asFile, "$bundleAssetName.map") + + internal fun resolveCompilerSourceMap(bundleAssetName: String) = + File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map") + + private fun runCommand(command: List) { + project.exec { + it.workingDir(root.get().asFile) + it.commandLine(command) + } + } + + internal fun getBundleCommand(bundleFile: File, sourceMapFile: File): List = + windowsAwareCommandLine( + buildList { + addAll(nodeExecutableAndArgs.get()) + add(cliPath.get()) + add(bundleCommand.get()) + add("--platform") + add("android") + add("--dev") + add(devEnabled.get().toString()) + add("--reset-cache") + add("--entry-file") + add(entryFile.get().asFile.toString()) + add("--bundle-output") + add(bundleFile.toString()) + add("--assets-dest") + add(resourcesDir.get().asFile.toString()) + add("--sourcemap-output") + add(sourceMapFile.toString()) + if (bundleConfig.isPresent) { + add("--config") + add(bundleConfig.get().asFile.absolutePath) + } + add("--minify") + add(minifyEnabled.get().toString()) + addAll(extraPackagerArgs.get()) + add("--verbose") + }) + + internal fun getHermescCommand( + hermesCommand: String, + bundleFile: File, + outputFile: File + ): List = + windowsAwareCommandLine( + hermesCommand, + "-emit-binary", + "-out", + outputFile.absolutePath, + bundleFile.absolutePath, + *hermesFlags.get().toTypedArray()) + + internal fun getComposeSourceMapsCommand( + packagerSourceMap: File, + compilerSourceMap: File, + outputSourceMap: File + ): List = + windowsAwareCommandLine( + *nodeExecutableAndArgs.get().toTypedArray(), + composeSourceMapsPath.get(), + packagerSourceMap.toString(), + compilerSourceMap.toString(), + "-o", + outputSourceMap.toString()) +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt deleted file mode 100644 index 2e1a0b8be819..000000000000 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.tasks - -import com.facebook.react.utils.recreateDir -import com.facebook.react.utils.windowsAwareCommandLine -import java.io.File -import org.gradle.api.file.FileTree -import org.gradle.api.tasks.* - -abstract class BundleJsAndAssetsTask : Exec() { - - @get:Internal lateinit var reactRoot: File - - @get:InputFiles - @Suppress("UNUSED") // used to invalidate caches - lateinit var sources: FileTree - - @get:Input lateinit var execCommand: List - @get:Input lateinit var bundleCommand: String - @get:Input var devEnabled: Boolean = true - @get:InputFile lateinit var entryFile: File - @get:Input var extraArgs: List = emptyList() - - @get:OutputDirectory lateinit var jsBundleDir: File - @get:OutputFile lateinit var jsBundleFile: File - @get:OutputDirectory lateinit var resourcesDir: File - @get:OutputDirectory lateinit var jsIntermediateSourceMapsDir: File - @get:OutputDirectory lateinit var jsSourceMapsDir: File - @get:OutputFile lateinit var jsSourceMapsFile: File - - override fun exec() { - cleanOutputDirectories() - configureBundleCommand() - super.exec() - } - - private fun cleanOutputDirectories() { - jsBundleDir.recreateDir() - resourcesDir.recreateDir() - jsIntermediateSourceMapsDir.recreateDir() - jsSourceMapsDir.recreateDir() - } - - private fun configureBundleCommand() { - workingDir(reactRoot) - - @Suppress("SpreadOperator") - commandLine( - windowsAwareCommandLine( - *execCommand.toTypedArray(), - bundleCommand, - "--platform", - "android", - "--dev", - devEnabled, - "--reset-cache", - "--entry-file", - entryFile, - "--bundle-output", - jsBundleFile, - "--assets-dest", - resourcesDir, - "--sourcemap-output", - jsSourceMapsFile, - *extraArgs.toTypedArray())) - } -} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt deleted file mode 100644 index 3f9e63597384..000000000000 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.tasks - -import com.facebook.react.utils.detectOSAwareHermesCommand -import com.facebook.react.utils.moveTo -import com.facebook.react.utils.windowsAwareCommandLine -import java.io.File -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -open class HermesBinaryTask : DefaultTask() { - - @get:Internal internal lateinit var root: File - - @get:Input internal lateinit var hermesCommand: String - @get:Input internal var hermesFlags: List = emptyList() - @get:InputFile internal lateinit var jsBundleFile: File - - @get:Input internal lateinit var composeSourceMapsCommand: List - @get:InputFile internal lateinit var jsPackagerSourceMapFile: File - - @get:OutputFile internal lateinit var jsCompilerSourceMapFile: File - @get:OutputFile internal lateinit var jsOutputSourceMapFile: File - - @TaskAction - fun run() { - val detectedHermesCommand = detectOSAwareHermesCommand(root, hermesCommand) - val bytecodeTempFile = File("$jsBundleFile.hbc") - emitHermesBinary(hermesCommand = detectedHermesCommand, outputFile = bytecodeTempFile) - bytecodeTempFile.moveTo(jsBundleFile) - - if (hermesFlags.contains("-output-source-map")) { - val hermesTempSourceMapFile = File("$bytecodeTempFile.map") - hermesTempSourceMapFile.moveTo(jsCompilerSourceMapFile) - composeSourceMaps() - } - } - - private fun emitHermesBinary(hermesCommand: String, outputFile: File) { - project.exec { - @Suppress("SpreadOperator") - it.commandLine( - windowsAwareCommandLine( - hermesCommand, - "-emit-binary", - "-out", - outputFile, - jsBundleFile, - *hermesFlags.toTypedArray())) - } - } - - private fun composeSourceMaps() { - project.exec { - it.workingDir(root) - - @Suppress("SpreadOperator") - it.commandLine( - windowsAwareCommandLine( - *composeSourceMapsCommand.toTypedArray(), - jsPackagerSourceMapFile, - jsCompilerSourceMapFile, - "-o", - jsOutputSourceMapFile)) - } - } -} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt index 17a0982ad83b..f70e14cbccab 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt @@ -19,8 +19,8 @@ internal object ProjectUtils { internal val Project.isHermesEnabled: Boolean get() = - if (project.hasProperty("enableHermes")) { - project.property("enableHermes").toString().lowercase().toBooleanStrictOrNull() ?: true + if (project.hasProperty("hermesEnabled")) { + project.property("hermesEnabled").toString().lowercase().toBooleanStrictOrNull() ?: true } else if (project.extensions.extraProperties.has("react")) { @Suppress("UNCHECKED_CAST") val reactMap = project.extensions.extraProperties.get("react") as? Map diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt index fd557079f59d..df99d06e47bc 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt @@ -8,10 +8,13 @@ package com.facebook.react.utils internal fun windowsAwareCommandLine(vararg args: Any): List = + windowsAwareCommandLine(args.toList()) + +internal fun windowsAwareCommandLine(args: List): List = if (Os.isWindows()) { listOf("cmd", "/c") + args } else { - args.toList() + args } internal fun windowsAwareBashCommandLine( diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt new file mode 100644 index 000000000000..8a180e1080d1 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt @@ -0,0 +1,315 @@ +/* + * 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.tasks + +import com.facebook.react.tests.OsRule +import com.facebook.react.tests.createTestTask +import java.io.File +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class BundleHermesCTaskTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @get:Rule val osRule = OsRule() + + @Test + fun bundleTask_groupIsSetCorrectly() { + val task = createTestTask {} + assertEquals("react", task.group) + } + + @Test + fun bundleTask_inputFiles_areSetCorrectly() { + val rootDir = + tempFolder.newFolder("js").apply { + File(this, "file.js").createNewFile() + File(this, "file.jsx").createNewFile() + File(this, "file.ts").createNewFile() + File(this, "file.tsx").createNewFile() + } + + val task = createTestTask { it.root.set(rootDir) } + + assertEquals(4, task.sources.files.size) + assertEquals( + setOf( + File(rootDir, "file.js"), + File(rootDir, "file.jsx"), + File(rootDir, "file.ts"), + File(rootDir, "file.tsx")), + task.sources.files) + } + + @Test + fun bundleTask_inputFilesInExcludedPath_areExcluded() { + fun File.createFileAndPath() { + parentFile.mkdirs() + createNewFile() + } + + val rootDir = + tempFolder.newFolder("js").apply { + File(this, "afolder/includedfile.js").createFileAndPath() + // Those files should be excluded due to their filepath + File(this, "android/excludedfile.js").createFileAndPath() + File(this, "ios/excludedfile.js").createFileAndPath() + File(this, "build/excludedfile.js").createFileAndPath() + File(this, "node_modules/react-native/excludedfile.js").createFileAndPath() + } + + val task = createTestTask { it.root.set(rootDir) } + + assertEquals( + setOf( + "**/android/**/*", + "**/ios/**/*", + "**/build/**/*", + "**/node_modules/**/*", + ), + task.sources.excludes) + assertEquals(1, task.sources.files.size) + assertEquals(setOf(File(rootDir, "afolder/includedfile.js")), task.sources.files) + } + + @Test + fun bundleTask_staticInputs_areSetCorrectly() { + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.cliPath.set("../node_modules/react-native/cli.js") + it.composeSourceMapsPath.set( + "../node_modules/react-native/scripts/compose-source-maps.js") + it.bundleCommand.set("bundle") + it.bundleAssetName.set("myassetname") + it.minifyEnabled.set(true) + it.hermesEnabled.set(true) + it.devEnabled.set(true) + it.extraPackagerArgs.set(listOf("extra", "arg")) + it.hermesCommand.set("./my-hermesc") + it.hermesFlags.set(listOf("flag1", "flag2")) + } + + assertEquals(listOf("node", "arg1", "arg2"), task.nodeExecutableAndArgs.get()) + assertEquals("../node_modules/react-native/cli.js", task.cliPath.get()) + assertEquals( + "../node_modules/react-native/scripts/compose-source-maps.js", + task.composeSourceMapsPath.get()) + assertEquals("bundle", task.bundleCommand.get()) + assertEquals("myassetname", task.bundleAssetName.get()) + assertTrue(task.minifyEnabled.get()) + assertTrue(task.hermesEnabled.get()) + assertTrue(task.devEnabled.get()) + assertEquals(listOf("extra", "arg"), task.extraPackagerArgs.get()) + assertEquals("./my-hermesc", task.hermesCommand.get()) + assertEquals(listOf("flag1", "flag2"), task.hermesFlags.get()) + } + + @Test + fun bundleTask_filesInput_areSetCorrectly() { + val entryFile = tempFolder.newFile("entry.js") + val jsBundleDir = tempFolder.newFolder("jsbundle") + val resourcesDir = tempFolder.newFolder("resources") + val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps") + val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps") + val bundleConfig = tempFolder.newFile("bundle.config") + + val task = + createTestTask { + it.entryFile.set(entryFile) + it.jsBundleDir.set(jsBundleDir) + it.resourcesDir.set(resourcesDir) + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + it.jsSourceMapsDir.set(jsSourceMapsDir) + it.bundleConfig.set(bundleConfig) + } + + assertEquals(entryFile, task.entryFile.get().asFile) + assertEquals(jsBundleDir, task.jsBundleDir.get().asFile) + assertEquals(resourcesDir, task.resourcesDir.get().asFile) + assertEquals(jsIntermediateSourceMapsDir, task.jsIntermediateSourceMapsDir.get().asFile) + assertEquals(jsSourceMapsDir, task.jsSourceMapsDir.get().asFile) + assertEquals(bundleConfig, task.bundleConfig.get().asFile) + } + + @Test + fun resolvePackagerSourceMapFile_withHermesEnabled_returnsCorrectFile() { + val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps") + val bundleAssetName = "myassetname" + val task = + createTestTask { + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + it.hermesEnabled.set(true) + it.bundleAssetName.set(bundleAssetName) + } + + assertEquals( + File(jsIntermediateSourceMapsDir, "myassetname.packager.map"), + task.resolvePackagerSourceMapFile(bundleAssetName)) + } + + @Test + fun resolvePackagerSourceMapFile_withHermesDisabled_returnsCorrectFile() { + val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps") + val bundleAssetName = "myassetname" + val task = + createTestTask { + it.jsSourceMapsDir.set(jsSourceMapsDir) + it.hermesEnabled.set(false) + } + + assertEquals( + File(jsSourceMapsDir, "myassetname.map"), + task.resolvePackagerSourceMapFile(bundleAssetName)) + } + + @Test + fun resolveOutputSourceMap_returnsCorrectFile() { + val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps") + val bundleAssetName = "myassetname" + val task = createTestTask { it.jsSourceMapsDir.set(jsSourceMapsDir) } + + assertEquals( + File(jsSourceMapsDir, "myassetname.map"), task.resolveOutputSourceMap(bundleAssetName)) + } + + @Test + fun resolveCompilerSourceMap_returnsCorrectFile() { + val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps") + val bundleAssetName = "myassetname" + val task = + createTestTask { + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + } + + assertEquals( + File(jsIntermediateSourceMapsDir, "myassetname.compiler.map"), + task.resolveCompilerSourceMap(bundleAssetName)) + } + + @Test + fun getBundleCommand_returnsCorrectCommand() { + val entryFile = tempFolder.newFile("index.js") + val bundleFile = tempFolder.newFile("bundle.js") + val sourceMapFile = tempFolder.newFile("bundle.js.map") + val resourcesDir = tempFolder.newFolder("res") + val bundleConfig = tempFolder.newFile("bundle.config") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.cliPath.set("../node_modules/react-native/cli.js") + it.bundleCommand.set("bundle") + it.devEnabled.set(true) + it.entryFile.set(entryFile) + it.resourcesDir.set(resourcesDir) + it.bundleConfig.set(bundleConfig) + it.minifyEnabled.set(true) + it.extraPackagerArgs.set(listOf("--read-global-cache")) + } + + val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile) + + assertEquals("node", bundleCommand[0]) + assertEquals("arg1", bundleCommand[1]) + assertEquals("arg2", bundleCommand[2]) + assertEquals("../node_modules/react-native/cli.js", bundleCommand[3]) + assertEquals("bundle", bundleCommand[4]) + assertEquals("--platform", bundleCommand[5]) + assertEquals("android", bundleCommand[6]) + assertEquals("--dev", bundleCommand[7]) + assertEquals("true", bundleCommand[8]) + assertEquals("--reset-cache", bundleCommand[9]) + assertEquals("--entry-file", bundleCommand[10]) + assertEquals(entryFile.absolutePath, bundleCommand[11]) + assertEquals("--bundle-output", bundleCommand[12]) + assertEquals(bundleFile.absolutePath, bundleCommand[13]) + assertEquals("--assets-dest", bundleCommand[14]) + assertEquals(resourcesDir.absolutePath, bundleCommand[15]) + assertEquals("--sourcemap-output", bundleCommand[16]) + assertEquals(sourceMapFile.absolutePath, bundleCommand[17]) + assertEquals("--config", bundleCommand[18]) + assertEquals(bundleConfig.absolutePath, bundleCommand[19]) + assertEquals("--minify", bundleCommand[20]) + assertEquals("true", bundleCommand[21]) + assertEquals("--read-global-cache", bundleCommand[22]) + assertEquals("--verbose", bundleCommand[23]) + assertEquals(24, bundleCommand.size) + } + + @Test + fun getBundleCommand_withoutConfig_returnsCommandWithoutConfig() { + val entryFile = tempFolder.newFile("index.js") + val bundleFile = tempFolder.newFile("bundle.js") + val sourceMapFile = tempFolder.newFile("bundle.js.map") + val resourcesDir = tempFolder.newFolder("res") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.cliPath.set("../node_modules/react-native/cli.js") + it.bundleCommand.set("bundle") + it.devEnabled.set(true) + it.entryFile.set(entryFile) + it.resourcesDir.set(resourcesDir) + it.minifyEnabled.set(true) + it.extraPackagerArgs.set(listOf("--read-global-cache")) + } + + val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile) + + assertTrue("--config" !in bundleCommand) + } + + @Test + fun getHermescCommand_returnsCorrectCommand() { + val customHermesc = "hermesc" + val bundleFile = tempFolder.newFile("bundle.js") + val outputFile = tempFolder.newFile("bundle.js.packager.map") + val task = + createTestTask { it.hermesFlags.set(listOf("my-custom-hermes-flag")) } + + val hermesCommand = task.getHermescCommand(customHermesc, bundleFile, outputFile) + + assertEquals(customHermesc, hermesCommand[0]) + assertEquals("-emit-binary", hermesCommand[1]) + assertEquals("-out", hermesCommand[2]) + assertEquals(outputFile.absolutePath, hermesCommand[3]) + assertEquals(bundleFile.absolutePath, hermesCommand[4]) + assertEquals("my-custom-hermes-flag", hermesCommand[5]) + assertEquals(6, hermesCommand.size) + } + + @Test + fun getComposeSourceMapsCommand_returnsCorrectCommand() { + val packagerMap = tempFolder.newFile("bundle.js.packager.map") + val compilerMap = tempFolder.newFile("bundle.js.compiler.map") + val outputMap = tempFolder.newFile("bundle.js.map") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.composeSourceMapsPath.set( + "../node_modules/react-native/scripts/compose-source-maps.js") + } + + val composeSourcemapCommand = + task.getComposeSourceMapsCommand(packagerMap, compilerMap, outputMap) + + assertEquals("node", composeSourcemapCommand[0]) + assertEquals("arg1", composeSourcemapCommand[1]) + assertEquals("arg2", composeSourcemapCommand[2]) + assertEquals( + "../node_modules/react-native/scripts/compose-source-maps.js", composeSourcemapCommand[3]) + assertEquals(packagerMap.absolutePath, composeSourcemapCommand[4]) + assertEquals(compilerMap.absolutePath, composeSourcemapCommand[5]) + assertEquals("-o", composeSourcemapCommand[6]) + assertEquals(outputMap.absolutePath, composeSourcemapCommand[7]) + assertEquals(8, composeSourcemapCommand.size) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt index 495715416f76..c41cc65a27cc 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt @@ -50,21 +50,21 @@ class ProjectUtilsTest { @Test fun isNewArchEnabled_withDisabledViaProperty_returnsFalse() { val project = createProject() - project.extensions.extraProperties.set("enableHermes", "false") + project.extensions.extraProperties.set("hermesEnabled", "false") assertFalse(project.isHermesEnabled) } @Test fun isHermesEnabled_withEnabledViaProperty_returnsTrue() { val project = createProject() - project.extensions.extraProperties.set("enableHermes", "true") + project.extensions.extraProperties.set("hermesEnabled", "true") assertTrue(project.isHermesEnabled) } @Test fun isHermesEnabled_withInvalidViaProperty_returnsTrue() { val project = createProject() - project.extensions.extraProperties.set("enableHermes", "¯\\_(ツ)_/¯") + project.extensions.extraProperties.set("hermesEnabled", "¯\\_(ツ)_/¯") assertTrue(project.isHermesEnabled) } diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt index a1534d365998..cafbba6f7afd 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt @@ -24,6 +24,12 @@ class TaskUtilsTest { assertTrue(windowsAwareCommandLine().isEmpty()) } + @Test + fun windowsAwareCommandLine_withList_isEqualAsVararg() { + assertEquals( + windowsAwareCommandLine(listOf("a", "b", "c")), windowsAwareCommandLine("a", "b", "c")) + } + @Test @WithOs(OS.MAC) fun windowsAwareCommandLine_onMac_returnsTheList() { diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index e80589e004d3..e0fc0dcf655e 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -80,14 +80,15 @@ react { bundleAssetName = "RNTesterApp.android.bundle" entryFile = file("../../js/RNTesterApp.android.js") 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") } + debuggableVariants = ["hermesDebug", "jscDebug"] + enableHermesOnlyInVariants = ["hermesDebug", "hermesRelease"] // Codegen Configs reactNativeDir = rootDir codegenDir = new File(rootDir, "node_modules/react-native-codegen") + enableCodegenInApps = true } /** @@ -227,6 +228,5 @@ android { afterEvaluate { // As we're consuming Hermes from source, we want to make sure // `hermesc` is built before we actually invoke the `emit*HermesResource` task - emitHermesDebugHermesResources.dependsOn(":ReactAndroid:hermes-engine:buildHermes") - emitHermesReleaseHermesResources.dependsOn(":ReactAndroid:hermes-engine:buildHermes") + createBundleHermesReleaseJsAndAssets.dependsOn(":ReactAndroid:hermes-engine:buildHermes") } diff --git a/packages/rn-tester/android/app/gradle.properties b/packages/rn-tester/android/app/gradle.properties index 159049803a66..18b9871789b1 100644 --- a/packages/rn-tester/android/app/gradle.properties +++ b/packages/rn-tester/android/app/gradle.properties @@ -14,3 +14,5 @@ FLIPPER_VERSION=0.125.0 # RN-Tester is building with NewArch always enabled newArchEnabled=true +# RN-Tester is running with Hermes enabled and filtering variants with enableHermesOnlyInVariants +hermesEnabled=true