diff --git a/gulpfile.js b/gulpfile.js
index 2dcb017e1..e18e28425 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -183,9 +183,8 @@ gulp.task("clean", () => {
"!test/resources/sampleReactNative022Project/**/*.js",
".vscode-test/",
"nls.*.json",
- "!test/smoke/resources/ReactNativeSample/App.js",
- "!test/smoke/resources/ExpoSample/App.js",
- "!test/smoke/resources/PureRNExpoSample/App.js",
+ "!test/smoke/**/*.js",
+ "!test/smoke/**/*.js.map",
]
return del(pathsToDelete, { force: true });
});
diff --git a/package.json b/package.json
index 3584e966d..dbb44da92 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
],
"activationEvents": [
"onDebugResolve:reactnative",
+ "onDebugResolve:reactnativedirect",
"onDebugInitialConfigurations",
"onCommand:reactNative.runAndroidSimulator",
"onCommand:reactNative.runAndroidDevice",
diff --git a/test/smoke/resources/HermesReactNativeSample/App.js b/test/smoke/resources/HermesReactNativeSample/App.js
new file mode 100644
index 000000000..50fe117f5
--- /dev/null
+++ b/test/smoke/resources/HermesReactNativeSample/App.js
@@ -0,0 +1,75 @@
+/**
+ * Sample React Native App
+ * https://github.com/facebook/react-native
+ *
+ * @format
+ * @flow
+ */
+
+import React from 'react';
+import { SafeAreaView, StyleSheet, ScrollView, View, Text, StatusBar } from 'react-native';
+import { Header, LearnMoreLinks, Colors, DebugInstructions, ReloadInstructions } from 'react-native/Libraries/NewAppScreen';
+import AppTestButton from './AppTestButton';
+
+const App: () => React$Node = () => {
+ console.log('Test output from debuggee');
+ return (
+ <>
+
+
+
+
+ {global.HermesInternal == null ? null : (
+
+ Engine: Hermes
+
+ )}
+
+
+
+ >
+ );
+};
+
+const styles = StyleSheet.create({
+ scrollView: {
+ backgroundColor: Colors.lighter,
+ },
+ engine: {
+ position: 'absolute',
+ right: 0,
+ },
+ body: {
+ backgroundColor: Colors.white,
+ },
+ sectionContainer: {
+ marginTop: 32,
+ paddingHorizontal: 24,
+ },
+ sectionTitle: {
+ fontSize: 24,
+ fontWeight: '600',
+ color: Colors.black,
+ },
+ sectionDescription: {
+ marginTop: 8,
+ fontSize: 18,
+ fontWeight: '400',
+ color: Colors.dark,
+ },
+ highlight: {
+ fontWeight: '700',
+ },
+ footer: {
+ color: Colors.dark,
+ fontSize: 12,
+ fontWeight: '600',
+ padding: 4,
+ paddingRight: 12,
+ textAlign: 'right',
+ },
+});
+
+export default App;
diff --git a/test/smoke/resources/HermesReactNativeSample/AppTestButton.js b/test/smoke/resources/HermesReactNativeSample/AppTestButton.js
new file mode 100644
index 000000000..d5c4a6bdb
--- /dev/null
+++ b/test/smoke/resources/HermesReactNativeSample/AppTestButton.js
@@ -0,0 +1,25 @@
+import React, { Component } from 'react';
+import { View, Button } from 'react-native';
+
+export default class AppTestButton extends Component {
+ constructor(props) {
+ super(props);
+ this.handleClick = this.handleClick.bind(this);
+ }
+ handleClick() {
+ let testBooleanValue = true;
+ console.log('Test output from Hermes debuggee');
+ }
+ render() {
+ return (
+
+
+
+ );
+ }
+ }
+
+
diff --git a/test/smoke/resources/HermesReactNativeSample/build.gradle b/test/smoke/resources/HermesReactNativeSample/build.gradle
new file mode 100644
index 000000000..d2952d243
--- /dev/null
+++ b/test/smoke/resources/HermesReactNativeSample/build.gradle
@@ -0,0 +1,201 @@
+apply plugin: "com.android.application"
+
+import com.android.build.OutputFile
+
+/**
+ * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
+ * and bundleReleaseJsAndAssets).
+ * These basically call `react-native bundle` with the correct arguments during the Android build
+ * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
+ * bundle directly from the development server. Below you can see all the possible configurations
+ * and their defaults. If you decide to add a configuration block, make sure to add it before the
+ * `apply from: "../../node_modules/react-native/react.gradle"` line.
+ *
+ * project.ext.react = [
+ * // the name of the generated asset file containing your JS bundle
+ * bundleAssetName: "index.android.bundle",
+ *
+ * // the entry file for bundle generation
+ * entryFile: "index.android.js",
+ *
+ * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format
+ * bundleCommand: "ram-bundle",
+ *
+ * // whether to bundle JS and assets in debug mode
+ * bundleInDebug: false,
+ *
+ * // whether to bundle JS and assets in release mode
+ * bundleInRelease: true,
+ *
+ * // whether to bundle JS and assets in another build variant (if configured).
+ * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
+ * // The configuration property can be in the following formats
+ * // 'bundleIn${productFlavor}${buildType}'
+ * // 'bundleIn${buildType}'
+ * // bundleInFreeDebug: true,
+ * // bundleInPaidRelease: true,
+ * // bundleInBeta: true,
+ *
+ * // whether to disable dev mode in custom build variants (by default only disabled in release)
+ * // for example: to disable dev mode in the staging build type (if configured)
+ * devDisabledInStaging: true,
+ * // The configuration property can be in the following formats
+ * // 'devDisabledIn${productFlavor}${buildType}'
+ * // 'devDisabledIn${buildType}'
+ *
+ * // the root of your project, i.e. where "package.json" lives
+ * root: "../../",
+ *
+ * // where to put the JS bundle asset in debug mode
+ * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
+ *
+ * // where to put the JS bundle asset in release mode
+ * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
+ *
+ * // where to put drawable resources / React Native assets, e.g. the ones you use via
+ * // require('./image.png')), in debug mode
+ * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
+ *
+ * // where to put drawable resources / React Native assets, e.g. the ones you use via
+ * // require('./image.png')), in release mode
+ * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
+ *
+ * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
+ * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
+ * // date; if you have any other folders that you want to ignore for performance reasons (gradle
+ * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
+ * // for example, you might want to remove it from here.
+ * inputExcludes: ["android/**", "ios/**"],
+ *
+ * // override which node gets called and with what additional arguments
+ * nodeExecutableAndArgs: ["node"],
+ *
+ * // supply additional arguments to the packager
+ * extraPackagerArgs: []
+ * ]
+ */
+
+project.ext.react = [
+ entryFile: "index.js",
+ enableHermes: true, // clean and rebuild if changing
+]
+
+apply from: "../../node_modules/react-native/react.gradle"
+
+/**
+ * Set this to true to create two separate APKs instead of one:
+ * - An APK that only works on ARM devices
+ * - An APK that only works on x86 devices
+ * The advantage is the size of the APK is reduced by about 4MB.
+ * Upload all the APKs to the Play Store and people will download
+ * the correct one based on the CPU architecture of their device.
+ */
+def enableSeparateBuildPerCPUArchitecture = false
+
+/**
+ * Run Proguard to shrink the Java bytecode in release builds.
+ */
+def enableProguardInReleaseBuilds = false
+
+/**
+ * The preferred build flavor of JavaScriptCore.
+ *
+ * For example, to use the international variant, you can use:
+ * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
+ *
+ * The international variant includes ICU i18n library and necessary data
+ * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
+ * give correct results when using with locales other than en-US. Note that
+ * this variant is about 6MiB larger per architecture than default.
+ */
+def jscFlavor = 'org.webkit:android-jsc:+'
+
+/**
+ * Whether to enable the Hermes VM.
+ *
+ * This should be set on project.ext.react and mirrored here. If it is not set
+ * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
+ * and the benefits of using Hermes will therefore be sharply reduced.
+ */
+def enableHermes = project.ext.react.get("enableHermes", false);
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ applicationId "com.latestrnapp"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 1
+ versionName "1.0"
+ }
+ splits {
+ abi {
+ reset()
+ enable enableSeparateBuildPerCPUArchitecture
+ universalApk false // If true, also generate a universal APK
+ include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
+ }
+ }
+ signingConfigs {
+ debug {
+ storeFile file('debug.keystore')
+ storePassword 'android'
+ keyAlias 'androiddebugkey'
+ keyPassword 'android'
+ }
+ }
+ buildTypes {
+ debug {
+ signingConfig signingConfigs.debug
+ }
+ release {
+ // Caution! In production, you need to generate your own keystore file.
+ // see https://facebook.github.io/react-native/docs/signed-apk-android.
+ signingConfig signingConfigs.debug
+ minifyEnabled enableProguardInReleaseBuilds
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ }
+ }
+ // applicationVariants are e.g. debug, release
+ applicationVariants.all { variant ->
+ variant.outputs.each { output ->
+ // For each separate APK per architecture, set a unique version code as described here:
+ // https://developer.android.com/studio/build/configure-apk-splits.html
+ def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
+ def abi = output.getFilter(OutputFile.ABI)
+ if (abi != null) { // null for the universal-debug, universal-release variants
+ output.versionCodeOverride =
+ versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
+ }
+
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation "com.facebook.react:react-native:+" // From node_modules
+
+ if (enableHermes) {
+ def hermesPath = "../../node_modules/hermes-engine/android/";
+ debugImplementation files(hermesPath + "hermes-debug.aar")
+ releaseImplementation files(hermesPath + "hermes-release.aar")
+ } else {
+ implementation jscFlavor
+ }
+}
+
+// Run this once to be able to run the application with BUCK
+// puts all compile dependencies into folder libs for BUCK to use
+task copyDownloadableDepsToLibs(type: Copy) {
+ from configurations.compile
+ into 'libs'
+}
+
+apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
diff --git a/test/smoke/resources/HermesReactNativeSample/react.gradle b/test/smoke/resources/HermesReactNativeSample/react.gradle
new file mode 100644
index 000000000..f52af3e4f
--- /dev/null
+++ b/test/smoke/resources/HermesReactNativeSample/react.gradle
@@ -0,0 +1,314 @@
+// 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.
+
+import org.apache.tools.ant.taskdefs.condition.Os
+
+def config = project.hasProperty("react") ? project.react : [];
+
+def cliPath = config.cliPath ?: "node_modules/react-native/cli.js"
+def composeSourceMapsPath = config.composeSourceMapsPath ?: "node_modules/react-native/scripts/compose-source-maps.js"
+def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
+def entryFile = config.entryFile ?: "index.android.js"
+def bundleCommand = config.bundleCommand ?: "bundle"
+def reactRoot = file(config.root ?: "../../")
+def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
+def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
+def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup
+def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermes"
+
+def reactNativeDevServerPort() {
+ def value = project.getProperties().get("reactNativeDevServerPort")
+ return value != null ? value : "8081"
+}
+
+def reactNativeInspectorProxyPort() {
+ def value = project.getProperties().get("reactNativeInspectorProxyPort")
+ return value != null ? value : reactNativeDevServerPort()
+}
+
+def getHermesOSBin() {
+ 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";
+ throw new Exception("OS not recognized. Please set project.ext.react.hermesCommand " +
+ "to the path of a working Hermes compiler.");
+}
+
+// Make sure not to inspect the Hermes config unless we need it,
+// to avoid breaking any JSC-only setups.
+def getHermesCommand = {
+ // 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
+ .replaceAll("%OS-BIN%", getHermesOSBin())
+ .replace('/' as char, File.separatorChar);
+}
+
+// Set enableHermesForVariant to a function to configure per variant,
+// or set `enableHermes` to True/False to set all of them
+def enableHermesForVariant = config.enableHermesForVariant ?: {
+ def variant -> config.enableHermes ?: false
+}
+
+android {
+ buildTypes.all {
+ resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
+ resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
+ }
+}
+
+afterEvaluate {
+ def isAndroidLibrary = plugins.hasPlugin("com.android.library")
+ def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants
+ variants.all { def variant ->
+ // Create variant and target names
+ def targetName = variant.name.capitalize()
+ def targetPath = variant.dirName
+
+ // React js bundle directories
+ def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
+ def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
+
+ def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
+ def jsSourceMapsDir = file("$buildDir/generated/sourcemaps/react/${targetPath}")
+ def jsIntermediateSourceMapsDir = file("$buildDir/intermediates/sourcemaps/react/${targetPath}")
+ def jsPackagerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.packager.map")
+ def jsCompilerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.compiler.map")
+ def jsOutputSourceMapFile = file("$jsSourceMapsDir/${bundleAssetName}.map")
+
+ // Additional node and packager commandline arguments
+ def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
+ def extraPackagerArgs = config.extraPackagerArgs ?: []
+
+ def enableHermes = enableHermesForVariant(variant)
+
+ def currentBundleTask = tasks.create(
+ name: "bundle${targetName}JsAndAssets",
+ type: Exec) {
+ group = "react"
+ description = "bundle JS and assets for ${targetName}."
+
+ // Create dirs if they are not there (e.g. the "clean" task just ran)
+ doFirst {
+ jsBundleDir.deleteDir()
+ jsBundleDir.mkdirs()
+ resourcesDir.deleteDir()
+ resourcesDir.mkdirs()
+ jsIntermediateSourceMapsDir.deleteDir()
+ jsIntermediateSourceMapsDir.mkdirs()
+ jsSourceMapsDir.deleteDir()
+ jsSourceMapsDir.mkdirs()
+ }
+
+ // Set up inputs and outputs so gradle can cache the result
+ inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
+ outputs.dir(jsBundleDir)
+ outputs.dir(resourcesDir)
+
+ // Set up the call to the react-native cli
+ workingDir(reactRoot)
+
+ // Set up dev mode
+ def devEnabled = !(config."devDisabledIn${targetName}"
+ || targetName.toLowerCase().contains("release"))
+
+ def extraArgs = extraPackagerArgs;
+
+ if (bundleConfig) {
+ extraArgs = extraArgs.clone()
+ extraArgs.add("--config");
+ extraArgs.add(bundleConfig);
+ }
+
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
+ "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
+ "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)
+ } else {
+ commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
+ "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
+ "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)
+ }
+
+ if (enableHermes) {
+ doLast {
+ def hermesFlags;
+ def hbcTempFile = file("${jsBundleFile}.hbc")
+ exec {
+ if (targetName.toLowerCase().contains("release")) {
+ // Can't use ?: since that will also substitute valid empty lists
+ hermesFlags = config.hermesFlagsRelease
+ if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
+ } else {
+ hermesFlags = config.hermesFlagsDebug
+ if (hermesFlags == null) hermesFlags = []
+ }
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
+ } else {
+ commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
+ }
+ }
+ ant.move(
+ file: hbcTempFile,
+ toFile: jsBundleFile
+ );
+ if (hermesFlags.contains("-output-source-map")) {
+ ant.move(
+ // Hermes will generate a source map with this exact name
+ file: "${jsBundleFile}.hbc.map",
+ tofile: jsCompilerSourceMapFile
+ );
+ exec {
+ // TODO: set task dependencies for caching
+
+ // Set up the call to the compose-source-maps script
+ workingDir(reactRoot)
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ commandLine("cmd", "/c", *nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
+ } else {
+ commandLine(*nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
+ }
+ }
+ }
+ }
+ }
+
+ enabled config."bundleIn${targetName}" != null
+ ? config."bundleIn${targetName}"
+ : config."bundleIn${variant.buildType.name.capitalize()}" != null
+ ? config."bundleIn${variant.buildType.name.capitalize()}"
+ : targetName.toLowerCase().contains("release")
+ }
+
+ // Expose a minimal interface on the application variant and the task itself:
+ variant.ext.bundleJsAndAssets = currentBundleTask
+ currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
+ currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
+
+ // registerGeneratedResFolders for Android plugin 3.x
+ if (variant.respondsTo("registerGeneratedResFolders")) {
+ variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
+ } else {
+ variant.registerResGeneratingTask(currentBundleTask)
+ }
+ variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)
+
+ // packageApplication for Android plugin 3.x
+ def packageTask = variant.hasProperty("packageApplication")
+ ? variant.packageApplicationProvider.get()
+ : tasks.findByName("package${targetName}")
+ if (variant.hasProperty("packageLibrary")) {
+ packageTask = variant.packageLibrary
+ }
+
+ // pre bundle build task for Android plugin 3.2+
+ def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle")
+
+ def resourcesDirConfigValue = config."resourcesDir${targetName}"
+ if (resourcesDirConfigValue) {
+ def currentCopyResTask = tasks.create(
+ name: "copy${targetName}BundledResources",
+ type: Copy) {
+ group = "react"
+ description = "copy bundled resources into custom location for ${targetName}."
+
+ from(resourcesDir)
+ into(file(resourcesDirConfigValue))
+
+ dependsOn(currentBundleTask)
+
+ enabled(currentBundleTask.enabled)
+ }
+
+ packageTask.dependsOn(currentCopyResTask)
+ if (buildPreBundleTask != null) {
+ buildPreBundleTask.dependsOn(currentCopyResTask)
+ }
+ }
+
+ def currentAssetsCopyTask = tasks.create(
+ name: "copy${targetName}BundledJs",
+ type: Copy) {
+ group = "react"
+ description = "copy bundled JS into ${targetName}."
+
+ if (config."jsBundleDir${targetName}") {
+ from(jsBundleDir)
+ into(file(config."jsBundleDir${targetName}"))
+ } else {
+ into ("$buildDir/intermediates")
+ into ("assets/${targetPath}") {
+ from(jsBundleDir)
+ }
+
+ // Workaround for Android Gradle Plugin 3.2+ new asset directory
+ into ("merged_assets/${variant.name}/merge${targetName}Assets/out") {
+ from(jsBundleDir)
+ }
+
+ // Workaround for Android Gradle Plugin 3.4+ new asset directory
+ into ("merged_assets/${variant.name}/out") {
+ from(jsBundleDir)
+ }
+ }
+
+ // mergeAssets must run first, as it clears the intermediates directory
+ dependsOn(variant.mergeAssetsProvider.get())
+
+ enabled(currentBundleTask.enabled)
+ }
+
+ packageTask.dependsOn(currentAssetsCopyTask)
+ if (buildPreBundleTask != null) {
+ buildPreBundleTask.dependsOn(currentAssetsCopyTask)
+ }
+
+ // 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 releated 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.
+ def isRelease = targetName.toLowerCase().contains("release")
+ def libDir = "$buildDir/intermediates/transforms/"
+ def vmSelectionAction = {
+ fileTree(libDir).matching {
+ 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 { details ->
+ def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*"
+ def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char)
+ if (path.matches(targetVariant) && details.file.isFile()) {
+ details.file.delete()
+ }
+ }
+ }
+
+ if (enableVmCleanup) {
+ def task = tasks.findByName("package${targetName}")
+ task.doFirst(vmSelectionAction)
+ }
+ }
+}
diff --git a/test/smoke/resources/launch.json b/test/smoke/resources/launch.json
index 7bdc095cc..f5b83be47 100644
--- a/test/smoke/resources/launch.json
+++ b/test/smoke/resources/launch.json
@@ -8,6 +8,19 @@
"request": "launch",
"platform": "android"
},
+ {
+ "name": "Debug Android (Hermes) - Experimental",
+ "cwd": "${workspaceFolder}",
+ "type": "reactnativedirect",
+ "request": "launch",
+ "platform": "android"
+ },
+ {
+ "name": "Attach to packager (Hermes) - Experimental",
+ "cwd": "${workspaceFolder}",
+ "type": "reactnativedirect",
+ "request": "attach"
+ },
{
"name": "Debug iOS",
"cwd": "${workspaceFolder}",
diff --git a/test/smoke/src/areas/debug/debug.ts b/test/smoke/src/areas/debug/debug.ts
index ddf58cd6d..f841fe781 100644
--- a/test/smoke/src/areas/debug/debug.ts
+++ b/test/smoke/src/areas/debug/debug.ts
@@ -12,6 +12,7 @@ const DEBUG_OPTIONS_COMBOBOX_OPENED = `${DEBUG_OPTIONS_COMBOBOX}.monaco-select-b
const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`;
const START = `.icon[title="Start Debugging"]`;
const STOP = `.debug-toolbar .action-label[title*=\"Stop\"]`;
+const DISCONNECT = `.debug-toolbar .action-label[title*=\"Disconnect\"]`;
const STEP_OVER = `.debug-toolbar .action-label[title*=\"Step Over\"]`;
const STEP_IN = `.debug-toolbar .action-label[title*=\"Step Into\"]`;
const STEP_OUT = `.debug-toolbar .action-label[title*=\"Step Out\"]`;
@@ -101,6 +102,11 @@ export class Debug extends Viewlet {
await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR);
}
+ public async disconnectFromDebugger(): Promise {
+ await this.spectron.client.waitAndClick(DISCONNECT);
+ await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR);
+ }
+
public async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise {
return await this.spectron.client.waitFor(async () => {
const stackFrames = await this.getStackFrames();
diff --git a/test/smoke/src/debugAndroid.test.ts b/test/smoke/src/debugAndroid.test.ts
index 369c3cb90..0686ef17d 100644
--- a/test/smoke/src/debugAndroid.test.ts
+++ b/test/smoke/src/debugAndroid.test.ts
@@ -7,7 +7,7 @@ import { AppiumHelper, Platform, AppiumClient } from "./helpers/appiumHelper";
import { AndroidEmulatorHelper } from "./helpers/androidEmulatorHelper";
import { sleep } from "./helpers/utilities";
import { SmokeTestsConstants } from "./helpers/smokeTestsConstants";
-import { ExpoWorkspacePath, pureRNWorkspacePath, RNworkspacePath, runVSCode } from "./main";
+import { ExpoWorkspacePath, pureRNWorkspacePath, RNworkspacePath, prepareReactNativeProjectForHermesTesting, runVSCode } from "./main";
import { SetupEnvironmentHelper } from "./helpers/setupEnvironmentHelper";
import { TestRunArguments } from "./helpers/configHelper";
@@ -16,8 +16,10 @@ const RN_APP_ACTIVITY_NAME = "com.latestrnapp.MainActivity";
const EXPO_APP_PACKAGE_NAME = SetupEnvironmentHelper.expoPackageName;
const EXPO_APP_ACTIVITY_NAME = `${EXPO_APP_PACKAGE_NAME}.experience.HomeActivity`;
const RNDebugConfigName = "Debug Android";
+const RNHermesDebugConfigName = "Debug Android (Hermes) - Experimental";
const ExpoDebugConfigName = "Debug in Exponent";
const RNSetBreakpointOnLine = 14;
+const RNHermesSetBreakpointOnLine = 11;
const ExpoSetBreakpointOnLine = 12;
const PureRNExpoSetBreakpointOnLine = 23;
// Time for Android Debug Test before it reaches timeout
@@ -72,6 +74,53 @@ export function setup(testParameters?: TestRunArguments) {
console.log("Android Debug test: Debugging is stopped");
});
+ it("Hermes RN app Debug test", async function () {
+ this.timeout(debugAndroidTestTime);
+ prepareReactNativeProjectForHermesTesting();
+ AndroidEmulatorHelper.uninstallTestAppFromEmulator(RN_APP_PACKAGE_NAME);
+ app = await runVSCode(RNworkspacePath);
+ await app.workbench.explorer.openExplorerView();
+ await app.workbench.explorer.openFile("AppTestButton.js");
+ await app.runCommand("cursorTop");
+ console.log("Android Debug Hermes test: AppTestButton.js file is opened");
+ await app.workbench.debug.setBreakpointOnLine(RNHermesSetBreakpointOnLine);
+ console.log(`Android Debug Hermes test: Breakpoint is set on line ${RNHermesSetBreakpointOnLine}`);
+ await app.workbench.debug.openDebugViewlet();
+ console.log(`Android Debug Hermes test: Debug Viewlet opened`);
+ await app.workbench.debug.chooseDebugConfiguration(RNHermesDebugConfigName);
+ console.log(`Android Debug Hermes test: Chosen debug configuration: ${RNHermesDebugConfigName}`);
+ console.log("Android Debug Hermes test: Starting debugging");
+ await app.workbench.debug.startDebugging();
+ const opts = AppiumHelper.prepareAttachOptsForAndroidActivity(RN_APP_PACKAGE_NAME, RN_APP_ACTIVITY_NAME, AndroidEmulatorHelper.androidEmulatorName);
+ await AndroidEmulatorHelper.checkIfAppIsInstalled(RN_APP_PACKAGE_NAME, SmokeTestsConstants.androidAppBuildAndInstallTimeout);
+ let client = AppiumHelper.webdriverAttach(opts);
+ clientInited = client.init();
+ await app.workbench.debug.waitForDebuggingToStart();
+ console.log("Android Debug Hermes test: Debugging started");
+ console.log("Android Debug Hermes test: Checking for Hermes mark");
+ let isHermesWorking = await AppiumHelper.isHermesWorking(clientInited);
+ assert.equal(isHermesWorking, true);
+ console.log("Android Debug Hermes test: Reattaching to Hermes app");
+ await app.workbench.debug.stopDebugging();
+ await app.workbench.debug.chooseDebugConfiguration("Attach to packager (Hermes) - Experimental");
+ await app.workbench.debug.startDebugging();
+ console.log("Android Debug Hermes test: Reattached successfully");
+ await sleep(7000);
+ console.log("Android Debug Hermes test: Click Test Button");
+ await AppiumHelper.clickTestButtonHermes(clientInited);
+ await app.workbench.debug.waitForStackFrame(sf => sf.name === "AppTestButton.js" && sf.lineNumber === RNHermesSetBreakpointOnLine, `looking for AppTestButton.js and line ${RNHermesSetBreakpointOnLine}`);
+ console.log("Android Debug Hermes test: Stack frame found");
+ await app.workbench.debug.continue();
+ // await for our debug string renders in debug console
+ await sleep(SmokeTestsConstants.debugConsoleSearchTimeout);
+ console.log("Android Debug Hermes test: Searching for \"Test output from Hermes debuggee\" string in console");
+ let found = await app.workbench.debug.findStringInConsole("Test output from Hermes debuggee", 10000);
+ assert.notStrictEqual(found, false, "\"Test output from Hermes debuggee\" string is missing in debug console");
+ console.log("Android Debug test: \"Test output from Hermes debuggee\" string is found");
+ await app.workbench.debug.disconnectFromDebugger();
+ console.log("Android Debug Hermes test: Debugging is stopped");
+ });
+
it("Expo app Debug test", async function () {
if (testParameters && testParameters.RunBasicTests) {
this.skip();
diff --git a/test/smoke/src/debugIos.test.ts b/test/smoke/src/debugIos.test.ts
index b47667d81..975374b32 100644
--- a/test/smoke/src/debugIos.test.ts
+++ b/test/smoke/src/debugIos.test.ts
@@ -15,7 +15,7 @@ import { TestRunArguments } from "./helpers/configHelper";
const RnAppBundleId = "org.reactjs.native.example.latestRNApp";
const RNDebugConfigName = "Debug iOS";
const ExpoDebugConfigName = "Debug in Exponent";
-const RNSetBreakpointOnLine = 14;
+const RNSetBreakpointOnLine = 15;
const ExpoSetBreakpointOnLine = 12;
const PureRNExpoSetBreakpointOnLine = 23;
// Time for OS Debug Test before it reaches timeout
diff --git a/test/smoke/src/helpers/androidEmulatorHelper.ts b/test/smoke/src/helpers/androidEmulatorHelper.ts
index 5dd95fcc0..677e02ee3 100644
--- a/test/smoke/src/helpers/androidEmulatorHelper.ts
+++ b/test/smoke/src/helpers/androidEmulatorHelper.ts
@@ -144,6 +144,15 @@ export class AndroidEmulatorHelper {
});
}
+ public static uninstallTestAppFromEmulator(appPackage: string) {
+ console.log(`*** Uninstalling test app ${appPackage}' from Emulator`);
+ try {
+ cp.spawnSync("adb", ["shell", "pm", "uninstall", appPackage], {stdio: "inherit"});
+ } catch (e) {
+ console.error(`Error occured while uninstalling test app:\n ${e}`);
+ }
+ }
+
public static async enableDrawPermitForApp(packageName: string) {
const drawPermitCommand = `adb -s ${AndroidEmulatorHelper.androidEmulatorName} shell appops set ${packageName} SYSTEM_ALERT_WINDOW allow`;
console.log(`*** Enabling permission for drawing over apps via: ${drawPermitCommand}`);
diff --git a/test/smoke/src/helpers/appiumHelper.ts b/test/smoke/src/helpers/appiumHelper.ts
index 27c46c968..2b159ab70 100644
--- a/test/smoke/src/helpers/appiumHelper.ts
+++ b/test/smoke/src/helpers/appiumHelper.ts
@@ -243,6 +243,19 @@ export class AppiumHelper {
}
}
+ public static async clickTestButtonHermes(client: AppiumClient) {
+ console.log(`*** Pressing button with text "Test Button"...`);
+ const TEST_BUTTON = "//*[@text='TEST BUTTON']";
+ await client.click(TEST_BUTTON);
+ }
+
+ public static async isHermesWorking(client: AppiumClient): Promise {
+ const HERMES_MARK = "//*[@text='Engine: Hermes']";
+ return await client
+ .waitForExist(HERMES_MARK, 30 * 1000)
+ .isExisting(HERMES_MARK);
+ }
+
private static async openExpoAppViaClipboardAndroid(client: AppiumClient, clipboard: Electron.Clipboard, expoURL: string) {
// Expo application automatically detects Expo URLs in the clipboard
// So we are copying expoURL to system clipboard and click on the special "Open from Clipboard" UI element
diff --git a/test/smoke/src/helpers/setupEnvironmentHelper.ts b/test/smoke/src/helpers/setupEnvironmentHelper.ts
index afdb5a031..40a0595f0 100644
--- a/test/smoke/src/helpers/setupEnvironmentHelper.ts
+++ b/test/smoke/src/helpers/setupEnvironmentHelper.ts
@@ -27,9 +27,9 @@ export class SetupEnvironmentHelper {
console.log(`*** Creating RN app via '${command}' in ${workspacePath}...`);
cp.execSync(command, { cwd: resourcesPath, stdio: "inherit" });
- let customEntryPointFile = path.join(resourcesPath, customEntryPointFolder, "App.js");
- let launchConfigFile = path.join(resourcesPath, "launch.json");
- let vsCodeConfigPath = path.join(workspacePath, ".vscode");
+ const customEntryPointFile = path.join(resourcesPath, customEntryPointFolder, "App.js");
+ const launchConfigFile = path.join(resourcesPath, "launch.json");
+ const vsCodeConfigPath = path.join(workspacePath, ".vscode");
console.log(`*** Copying ${customEntryPointFile} into ${workspaceFilePath}...`);
fs.writeFileSync(workspaceFilePath, fs.readFileSync(customEntryPointFile));
@@ -45,6 +45,24 @@ export class SetupEnvironmentHelper {
SetupEnvironmentHelper.patchMetroConfig(workspacePath);
}
+ public static prepareHermesReactNativeApplication(workspaceFilePath: string, resourcesPath: string, workspacePath: string, appName: string, customEntryPointFolder: string, version?: string) {
+ const commandClean = path.join(workspacePath, "android", "gradlew") + " clean";
+
+ console.log(`*** Executing ${commandClean} ...`);
+ cp.execSync(commandClean, { cwd: path.join(workspacePath, "android"), stdio: "inherit" });
+
+ const customEntryPointFile = path.join(resourcesPath, customEntryPointFolder, "App.js");
+ const testButtonPath = path.join(resourcesPath, customEntryPointFolder, "AppTestButton.js");
+
+ console.log(`*** Copying ${customEntryPointFile} into ${workspaceFilePath}...`);
+ fs.writeFileSync(workspaceFilePath, fs.readFileSync(customEntryPointFile));
+
+ SetupEnvironmentHelper.copyGradleFilesToHermesApp(workspacePath, resourcesPath, customEntryPointFolder);
+
+ console.log(`*** Copying ${testButtonPath} into ${workspacePath}`);
+ fs.copyFileSync(testButtonPath, path.join(workspacePath, "AppTestButton.js"));
+ }
+
public static prepareExpoApplication(workspaceFilePath: string, resourcesPath: string, workspacePath: string, appName: string) {
const command = `echo -ne '\\n' | expo init -t tabs --name ${appName} ${appName}`;
console.log(`*** Creating Expo app via '${command}' in ${workspacePath}...`);
@@ -278,4 +296,17 @@ module.exports.hasteMapCacheDirectory = ".cache";`;
const contentAfterPatching = fs.readFileSync(metroConfigPath);
console.log(`*** Content of a metro.config.js after patching: ${contentAfterPatching}`);
}
+
+ private static copyGradleFilesToHermesApp(workspacePath: string, resourcesPath: string, customEntryPointFolder: string) {
+ const appGradleBuildFilePath = path.join(workspacePath, "android", "app", "build.gradle");
+ const resGradleBuildFilePath = path.join(resourcesPath, customEntryPointFolder, "build.gradle");
+ const resReactGradleFilePath = path.join(resourcesPath, customEntryPointFolder, "react.gradle"); // TODO: remove after react-native Gradle configuration fix (https://github.com/facebook/react-native/issues/25599)
+ const projReactGradleFilePath = path.join(workspacePath, "node_modules", "react-native", "react.gradle"); // TODO: remove after react-native Gradle configuration fix (https://github.com/facebook/react-native/issues/25599)
+
+ console.log(`*** Copying ${resGradleBuildFilePath} into ${appGradleBuildFilePath}...`);
+ fs.writeFileSync(appGradleBuildFilePath, fs.readFileSync(resGradleBuildFilePath));
+
+ console.log(`*** Copying ${resReactGradleFilePath} into ${projReactGradleFilePath}...`); // TODO: remove after react-native Gradle configuration fix (https://github.com/facebook/react-native/issues/25599)
+ fs.writeFileSync(projReactGradleFilePath, fs.readFileSync(resReactGradleFilePath));
+ }
}
diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts
index 6129c6598..76eb4470d 100644
--- a/test/smoke/src/main.ts
+++ b/test/smoke/src/main.ts
@@ -167,6 +167,10 @@ function createApp(quality: Quality, workspaceOrFolder: string): SpectronApplica
});
}
+export function prepareReactNativeProjectForHermesTesting() {
+ SetupEnvironmentHelper.prepareHermesReactNativeApplication(RNworkspaceFilePath, resourcesPath, RNworkspacePath, SmokeTestsConstants.RNAppName, "HermesReactNativeSample", process.env.RN_VERSION);
+}
+
const testParams = TestConfigurator.parseTestArguments();
async function setup(): Promise {
console.log("*** Test VS Code directory:", testVSCodeDirectory);