diff --git a/.gitignore b/.gitignore index 9aea329a6c35fd..0f2d0c91d407a4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ project.xcworkspace /build/ /packages/react-native-codegen/android/build/ /packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/build +/packages/react-native-gradle-plugin/build/ /packages/rn-tester/android/app/.cxx/ /packages/rn-tester/android/app/build/ /packages/rn-tester/android/app/gradle/ diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 69c52b585b687b..056880805cec10 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -1002,6 +1002,9 @@ private void recreateReactContextInBackground( private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) { FLog.d(ReactConstants.TAG, "ReactInstanceManager.runCreateReactContextOnNewThread()"); UiThreadUtil.assertOnUiThread(); + + // Mark start of bridge loading + ReactMarker.logMarker(ReactMarkerConstants.REACT_BRIDGE_LOADING_START); synchronized (mAttachedReactRoots) { synchronized (mReactContextLock) { if (mCurrentReactContext != null) { @@ -1133,6 +1136,8 @@ public void run() { }); Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(SETUP_REACT_CONTEXT_END); + // Mark end of bridge loading + ReactMarker.logMarker(ReactMarkerConstants.REACT_BRIDGE_LOADING_END); reactContext.runOnJSQueueThread( new Runnable() { @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java index 91e13be6f040ee..a0395cb6113e66 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java @@ -115,7 +115,9 @@ public enum ReactMarkerConstants { FABRIC_BATCH_EXECUTION_END, FABRIC_UPDATE_UI_MAIN_THREAD_START, FABRIC_UPDATE_UI_MAIN_THREAD_END, - // New markers used by bridgeless RN below this line - REACT_INSTANCE_INIT_START, - REACT_INSTANCE_INIT_END + // New markers used by bridge and bridgeless loading below this line + REACT_BRIDGE_LOADING_START, + REACT_BRIDGE_LOADING_END, + REACT_BRIDGELESS_LOADING_START, + REACT_BRIDGELESS_LOADING_END } diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/build.gradle b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/build.gradle index 2eb21ffdc01217..5efb793d69f8a5 100644 --- a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/build.gradle +++ b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/build.gradle @@ -11,7 +11,7 @@ plugins { gradlePlugin { plugins { - greeting { + codegen { id = 'com.facebook.react.codegen' implementationClass = 'com.facebook.react.codegen.plugin.CodegenPlugin' } diff --git a/packages/react-native-gradle-plugin/build.gradle.kts b/packages/react-native-gradle-plugin/build.gradle.kts new file mode 100644 index 00000000000000..3c1a47a49bb682 --- /dev/null +++ b/packages/react-native-gradle-plugin/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +plugins { + `java-gradle-plugin` + `kotlin-dsl` + kotlin("jvm") version "1.4.21" +} + +repositories { + google() + jcenter() +} + +gradlePlugin { + plugins { + create("reactApp") { + id = "com.facebook.react.app" + implementationClass = "com.facebook.react.ReactAppPlugin" + } + } +} + +dependencies { + implementation("com.android.tools.build:gradle:4.1.0") +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/AndroidConfiguration.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/AndroidConfiguration.kt new file mode 100644 index 00000000000000..6ff141045e74c1 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/AndroidConfiguration.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 + +import com.android.build.gradle.BaseExtension +import org.gradle.api.Project + +fun Project.configureDevPorts(androidExt: BaseExtension) { + val devServerPort = project.properties["reactNativeDevServerPort"]?.toString() ?: "8081" + val inspectorProxyPort = project.properties["reactNativeInspectorProxyPort"]?.toString() ?: devServerPort + + androidExt.buildTypes.all { + resValue("integer", "react_native_dev_server_port", devServerPort) + resValue("integer", "react_native_inspector_proxy_port", inspectorProxyPort) + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt new file mode 100644 index 00000000000000..01343bcc126b67 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 + +import com.android.build.gradle.api.BaseVariant +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.Project +import java.io.File + +open class ReactAppExtension(private val project: Project) { + var composeSourceMapsPath: String = "node_modules/react-native/scripts/compose-source-maps.js" + var bundleAssetName: String = "index.android.bundle" + var entryFile: File? = null + var bundleCommand: String = "bundle" + var reactRoot: File = File(project.projectDir, "../../") + var inputExcludes: List = listOf("android/**", "ios/**") + var bundleConfig: String? = null + var enableVmCleanup: Boolean = true + var hermesCommand: String = "node_modules/hermes-engine/%OS-BIN%/hermesc" + var cliPath: String? = null + var nodeExecutableAndArgs: List = listOf("node") + var enableHermes: Boolean = false + var enableHermesForVariant: (BaseVariant) -> Boolean = { enableHermes } + var devDisabledInVariants: List = emptyList() + // todo maybe lambda as for hermes? + var bundleIn: Map = emptyMap() + var extraPackagerArgs: List = emptyList() + var hermesFlagsDebug: List = emptyList() + var hermesFlagsRelease: List = listOf("-O", "-output-source-map") + var resourcesDir: Map = emptyMap() + var jsBundleDir: Map = emptyMap() + + internal val detectedEntryFile: File + get() = detectEntryFile(entryFile = entryFile, reactRoot = reactRoot) + + internal val detectedCliPath: String + get() = detectCliPath( + projectDir = project.projectDir, + reactRoot = reactRoot, + preconfuredCliPath = cliPath + ) + + internal val osAwareHermesCommand: String + get() = getOSAwareHermesCommand(hermesCommand) + + private fun detectEntryFile(entryFile: File?, reactRoot: File): File = when { + System.getenv("ENTRY_FILE") != null -> File(System.getenv("ENTRY_FILE")) + entryFile != null -> entryFile + File(reactRoot, "index.android.js").exists() -> File(reactRoot, "index.android.js") + else -> File(reactRoot, "index.android.js") + } + + private fun detectCliPath(projectDir: File, reactRoot: File, preconfuredCliPath: String?): String { + // 1. preconfigured path + if (preconfuredCliPath != null) return preconfuredCliPath + + // 2. node module path + val nodeProcess = Runtime.getRuntime().exec( + arrayOf("node", "-e", "console.log(require('react-native/cli').bin);"), + emptyArray(), + projectDir + ) + + val nodeProcessOutput = nodeProcess.inputStream.use { + it.bufferedReader().readText().trim() + } + + if (nodeProcessOutput.isNotEmpty()) { + return nodeProcessOutput + } + + // 3. cli.js in the root folder + val rootCliJs = File(reactRoot, "node_modules/react-native/cli.js") + if (rootCliJs.exists()) { + return rootCliJs.absolutePath + } + + error("Couldn't determine CLI location. " + + "Please set `project.react.cliPath` to the path of the react-native cli.js") + } + + // Make sure not to inspect the Hermes config unless we need it, + // to avoid breaking any JSC-only setups. + private fun getOSAwareHermesCommand(hermesCommand: String): String { + // If the project specifies a Hermes command, don't second guess it. + if (!hermesCommand.contains("%OS-BIN%")) { + return hermesCommand + } + + // Execution on Windows fails with / as separator + return hermesCommand + .replace("%OS-BIN%", getHermesOSBin()) + .replace('/', File.separatorChar) + } + + private fun getHermesOSBin(): String { + if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin" + if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin" + if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin" + error("OS not recognized. Please set project.react.hermesCommand " + + "to the path of a working Hermes compiler.") + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt new file mode 100644 index 00000000000000..14265ca6647018 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.getByType + +class ReactAppPlugin : Plugin { + override fun apply(project: Project) { + val config = project.extensions.create("reactApp", project) + + project.afterEvaluate { + val androidConfiguration = extensions.getByType() + configureDevPorts(androidConfiguration) + + val isAndroidLibrary = plugins.hasPlugin("com.android.library") + val variants = if (isAndroidLibrary) { + extensions.getByType().libraryVariants + } else { + extensions.getByType().applicationVariants + } + variants.all { + configureReactTasks( + variant = this, + config = config + ) + } + } + } +} 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 new file mode 100644 index 00000000000000..82f8eb084a7c29 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -0,0 +1,235 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 + +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 org.gradle.api.Project +import org.gradle.api.tasks.Copy +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.register +import java.io.File + +private const val REACT_GROUP = "react" + +@Suppress("SpreadOperator") +internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppExtension) { + val targetName = variant.name.capitalize() + val isRelease = variant.isRelease + val targetPath = variant.dirName + + // React js bundle directories + val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath") + val resourcesDir = File(buildDir, "generated/res/react/$targetPath") + + val jsBundleFile = File(jsBundleDir, config.bundleAssetName) + val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath") + val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath") + val jsPackagerSourceMapFile = File(jsIntermediateSourceMapsDir, "${config.bundleAssetName}.packager.map") + val jsCompilerSourceMapFile = File(jsIntermediateSourceMapsDir, "${config.bundleAssetName}.compiler.map") + val jsOutputSourceMapFile = File(jsSourceMapsDir, "${config.bundleAssetName}.map") + + // Additional node and packager commandline arguments + val nodeExecutableAndArgs = config.nodeExecutableAndArgs + val cliPath = config.detectedCliPath + + val execCommand = nodeExecutableAndArgs + cliPath + val enableHermes = config.enableHermesForVariant(variant) + val bundleEnabled = variant.checkBundleEnabled(config) + + val bundleTask = tasks.register("createBundle${targetName}JsAndAssets") { + val task = this + task.group = REACT_GROUP + task.description = "create JS bundle and assets for $targetName." + + task.reactRoot = config.reactRoot + task.sources = fileTree(config.reactRoot) { + setExcludes(config.inputExcludes) + } + task.execCommand = execCommand + task.bundleCommand = config.bundleCommand + task.devEnabled = !(variant.name in config.devDisabledInVariants || isRelease) + task.entryFile = config.detectedEntryFile + + val extraArgs = mutableListOf() + + if (config.bundleConfig != null) { + extraArgs.add("--config") + extraArgs.add(config.bundleConfig.orEmpty()) + } + + // Hermes doesn't require JS minification. + if (enableHermes && !devEnabled) { + extraArgs.add("--minify") + extraArgs.add("false") + } + + extraArgs.addAll(config.extraPackagerArgs) + + task.extraArgs = emptyList() + + task.jsBundleDir = jsBundleDir + task.jsBundleFile = jsBundleFile + task.resourcesDir = resourcesDir + task.jsIntermediateSourceMapsDir = jsIntermediateSourceMapsDir + task.jsSourceMapsDir = jsSourceMapsDir + task.jsSourceMapsFile = if (enableHermes) jsPackagerSourceMapFile else jsOutputSourceMapFile + + task.enabled = bundleEnabled + } + + val hermesTask = tasks.register("emit${targetName}HermesResources") { + val task = this + task.group = REACT_GROUP + task.description = "bundle hermes resources for $targetName" + + task.reactRoot = config.reactRoot + task.hermesCommand = config.osAwareHermesCommand + task.hermesFlags = if (isRelease) config.hermesFlagsRelease else config.hermesFlagsDebug + task.jsBundleFile = jsBundleFile + task.composeSourceMapsCommand = nodeExecutableAndArgs + config.composeSourceMapsPath + task.jsPackagerSourceMapFile = jsPackagerSourceMapFile + task.jsCompilerSourceMapFile = jsCompilerSourceMapFile + task.jsOutputSourceMapFile = jsOutputSourceMapFile + + task.dependsOn(bundleTask) + + task.enabled = bundleEnabled && enableHermes + } + + val aggregatedBundleTask = tasks.register("bundle${targetName}JsAndAssets") { + val task = this + task.group = REACT_GROUP + task.description = "bundle JS and resources for $targetName" + + task.dependsOn(bundleTask, hermesTask) + + // this was exposed before, do we still need it? + task.extra["generatedResFolders"] = files(resourcesDir).builtBy(task) + task.extra["generatedAssetsFolders"] = files(jsBundleDir).builtBy(task) + } + + val generatedResFolders = files(resourcesDir).builtBy(aggregatedBundleTask) + + // Android configuration + variant.registerGeneratedResFolders(generatedResFolders) + + val packageTask = when (variant) { + is ApplicationVariant -> variant.packageApplicationProvider + is LibraryVariant -> variant.packageLibraryProvider + else -> tasks.named("package$targetName") + } + + val mergeResourcesTask = variant.mergeResourcesProvider + val mergeAssetsTask = variant.mergeAssetsProvider + val preBundleTask = tasks.named("build${targetName}PreBundle") + + val resourcesDirConfigValue = config.resourcesDir[variant.name] + if (resourcesDirConfigValue != null) { + val currentCopyResTask = tasks.register("copy${targetName}BundledResources") { + group = "react" + description = "copy bundled resources into custom location for $targetName." + + from(resourcesDir) + into(file(resourcesDirConfigValue)) + + dependsOn(bundleTask) + + enabled = bundleEnabled + } + + packageTask.dependsOn(currentCopyResTask) + preBundleTask.dependsOn(currentCopyResTask) + } + + packageTask.configure { + if (config.enableVmCleanup) { + doFirst { + cleanupVMFiles(enableHermes, isRelease, targetPath) + } + } + } + + val currentAssetsCopyTask = tasks.register("copy${targetName}BundledJs") { + group = "react" + description = "copy bundled JS into $targetName." + + from(jsBundleDir) + + val jsBundleDirConfigValue = config.jsBundleDir[targetName] + if (jsBundleDirConfigValue != null) { + into(jsBundleDirConfigValue) + } else { + into(mergeAssetsTask.map { it.outputDir.get() }) + } + + dependsOn(mergeAssetsTask) + + 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(enableHermes: Boolean, isRelease: Boolean, targetPath: String) { + // 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. + val libDir = "$buildDir/intermediates/transforms/" + fileTree(libDir) { + if (enableHermes) { + // For Hermes, delete all the libjsc* files + include("**/libjsc*.so") + + if (isRelease) { + // Reduce size by deleting the debugger/inspector + include("**/libhermes-inspector.so") + include("**/libhermes-executor-debug.so") + } else { + // Release libs take precedence and must be removed + // to allow debugging + include("**/libhermes-executor-release.so") + } + } else { + // For JSC, delete all the libhermes* files + include("**/libhermes*.so") + } + }.visit { + val targetVariant = ".*/transforms/[^/]*/$targetPath/.*".toRegex() + val path = file.absolutePath.replace(File.separatorChar, '/') + if (path.matches(targetVariant) && file.isFile()) { + file.delete() + } + } +} + +private fun BaseVariant.checkBundleEnabled(config: ReactAppExtension): Boolean { + if (name in config.bundleIn) { + return config.bundleIn.getValue(name) + } + + if (buildType.name in config.bundleIn) { + return config.bundleIn.getValue(buildType.name) + } + + return isRelease +} + +private val BaseVariant.isRelease: Boolean + get() = name.toLowerCase().contains("release") 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 new file mode 100644 index 00000000000000..30e8e6fb908a2a --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 org.gradle.api.DefaultTask +import org.gradle.api.file.FileTree +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import java.io.File + +open class BundleJsAndAssetsTask : DefaultTask() { + internal lateinit var reactRoot: File + + @get:InputFiles + @Suppress("UNUSED") // used to invalidate caches + internal lateinit var sources: FileTree + @get:Input + internal lateinit var execCommand: List + @get:Input + internal lateinit var bundleCommand: String + @get:Input + internal var devEnabled: Boolean = true + @get:Input + internal lateinit var entryFile: File + @get:Input + internal var extraArgs: List = emptyList() + + @get:OutputDirectory + internal lateinit var jsBundleDir: File + @get:OutputFile + internal lateinit var jsBundleFile: File + @get:OutputDirectory + internal lateinit var resourcesDir: File + @get:OutputDirectory + internal lateinit var jsIntermediateSourceMapsDir: File + @get:OutputDirectory + internal lateinit var jsSourceMapsDir: File + @get:OutputFile + internal lateinit var jsSourceMapsFile: File + + @TaskAction + fun run() { + cleanOutputDirectories() + executeBundleCommand() + } + + private fun cleanOutputDirectories() { + jsBundleDir.recreateDir() + resourcesDir.recreateDir() + jsIntermediateSourceMapsDir.recreateDir() + jsSourceMapsDir.recreateDir() + } + + private fun executeBundleCommand() { + project.exec { + workingDir(reactRoot) + + @Suppress("SpreadOperator") + windowsAwareCommandLine( + *execCommand.toTypedArray(), + bundleCommand, + "--platform", "android", + "--dev", devEnabled, + "--reset-cache", + "--entry-file", entryFile, + "--bundle-output", jsBundleFile, + "--assets-dest", resourcesDir, + "--sourcemap-output", jsSourceMapsFile, + *extraArgs.toTypedArray() + ) + } + } + + private fun File.recreateDir() { + deleteRecursively() + mkdirs() + } +} 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 new file mode 100644 index 00000000000000..8f0db469e95679 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import java.io.File + +open class HermesBinaryTask : DefaultTask() { + internal lateinit var reactRoot: 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:Input + internal lateinit var jsPackagerSourceMapFile: File + + @get:OutputFile + internal lateinit var jsCompilerSourceMapFile: File + @get:OutputFile + internal lateinit var jsOutputSourceMapFile: File + + @TaskAction + fun run() { + val bytecodeTempFile = File("$jsBundleFile.hbc") + emitHermesBinary(outputFile = bytecodeTempFile) + bytecodeTempFile.moveTo(jsBundleFile) + + if (hermesFlags.contains("-output-source-map")) { + val hermesTempSourceMapFile = File("$bytecodeTempFile.map") + hermesTempSourceMapFile.moveTo(jsCompilerSourceMapFile) + composeSourceMaps() + } + } + + private fun emitHermesBinary(outputFile: File) { + project.exec { + @Suppress("SpreadOperator") + windowsAwareCommandLine( + hermesCommand, + "-emit-binary", + "-out", outputFile, + jsBundleFile, + *hermesFlags.toTypedArray() + ) + } + } + + private fun composeSourceMaps() { + project.exec { + workingDir(reactRoot) + + @Suppress("SpreadOperator") + windowsAwareCommandLine( + *composeSourceMapsCommand.toTypedArray(), + jsPackagerSourceMapFile, + jsCompilerSourceMapFile, + "-o", jsOutputSourceMapFile + ) + } + } + + private fun File.moveTo(destination: File) { + copyTo(destination, overwrite = true) + delete() + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt new file mode 100644 index 00000000000000..84aac7af0434ed --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.process.ExecSpec + +internal fun ExecSpec.windowsAwareCommandLine(vararg args: Any) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine(listOf("cmd", "/c") + args) + } else { + commandLine(args.toList()) + } +} diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index 59a2e64275a35c..0a09e54684e75e 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -8,6 +8,7 @@ plugins { id("com.android.application") id("com.facebook.react.codegen") + id("com.facebook.react.app") } /** @@ -75,21 +76,23 @@ plugins { * ] */ +reactApp { + cliPath = "$rootDir/cli.js" + bundleAssetName = "RNTesterApp.android.bundle" + entryFile = file("../../js/RNTesterApp.android.js") + reactRoot = rootDir + inputExcludes = ["android/**", "./**", ".gradle/**"] + composeSourceMapsPath = "$rootDir/scripts/compose-source-maps.js" + hermesCommand = "$rootDir/node_modules/hermes-engine/%OS-BIN%/hermesc" + enableHermesForVariant { def v -> v.name.contains("hermes") } +} + project.ext.react = [ - cliPath: "$rootDir/cli.js", - bundleAssetName: "RNTesterApp.android.bundle", - entryFile: file("../../js/RNTesterApp.android.js"), - root: "$rootDir", - inputExcludes: ["android/**", "./**", ".gradle/**"], - composeSourceMapsPath: "$rootDir/scripts/compose-source-maps.js", - hermesCommand: "$rootDir/node_modules/hermes-engine/%OS-BIN%/hermesc", - enableHermesForVariant: { def v -> v.name.contains("hermes") }, - jsRootDir: "$rootDir/RNTester", enableCodegen: true, // Keep this here until it's sync'ed to Android template. - enableFabric: (System.getenv('USE_FABRIC') ?: '0').toBoolean(), + enableFabric: (System.getenv('USE_FABRIC') ?: '0').toBoolean() ] -apply from: "../../../../react.gradle" +//apply from: "../../../../react.gradle" /** * Set this to true to create three separate APKs instead of one: diff --git a/settings.gradle.kts b/settings.gradle.kts index dd6b7adbec3064..1d838685e2591f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,3 +21,4 @@ include( // Include this to enable codegen Gradle plugin. includeBuild("packages/react-native-codegen/android") +includeBuild("packages/react-native-gradle-plugin/")