Android emulator selection for launch scenarios (#1361)
* create androidEmulatorManager * fix path to commandExecutor * init LaunchScenariosManager * add selection for android emulator * polishing, fixing and add command for launch emulator * refactor resolveEmulator logic, add telemetry step and create abstract EmulatorManager class * move logic of updating launch scenraio to LaunchScenarioManager * added the ability to use device id * add unit tests for GeneralMobilePlatform * add unit tests for LaunchScenarioManager * rename EmulatorManager to VirtualDeviceManager * add emulator launch command to readme * update terminateAndroidEmulator function Co-authored-by: Yuri Skorokhodov <v-yuskor@microsoft.com> Co-authored-by: RedMickey <33267199+RedMickey@users.noreply.github.com> Co-authored-by: JiglioNero <admin@sierra.local>
This commit is contained in:
Родитель
b78a1aae0d
Коммит
68a5b8d5c6
|
@ -99,6 +99,7 @@ The full list of commands is:
|
|||
|
||||
|Name|Description|
|
||||
|---|---|
|
||||
|Launch Android Emulator|Prompts you to select the name of the available emulator and launch it. If only one emulator is installed in the system, it will be selected automatically|
|
||||
|Run Android on Emulator|Run an Android application on Emulator. Launch order: check target platform support, load run arguments, start Packager, run app in all connected emulators|
|
||||
|Run Android on Device|Run an Android application on Device. Launch order: check target platform support, load run arguments, start Packager, run app in all connected devices|
|
||||
|Run iOS on Simulator|Run an iOS application on Simulator. Launch order: load run arguments, check target platform support, start Packager, run app in only one connected emulator|
|
||||
|
@ -308,7 +309,7 @@ The following is a list of all the configuration properties the debugger accepts
|
|||
|`skipFiles`|An array of file or folder names, or glob patterns, to skip when debugging|`array`|`[]`|
|
||||
|`debuggerWorkerUrlPath`|Path to the app debugger worker to override. For example, if debugger tries to attach to http://localhost:8081/debugger-ui/debuggerWorker.js and you get 404 error from packager output then you may want to change debuggerWorkerUrlPath to another value suitable for your packager (\"debugger-ui\" will be replaced with the value you provide)|`string`|`debugger-ui/`|
|
||||
|`platform`|The platform to target. Possible values: `android`, `ios`, `exponent`, `windows`, `wpf`|`string`|n/a|
|
||||
|`target`|Target to run on. Possible values: `simulator`, `device`, `device=<iOS device name>`, [`<Android emulator/device id>`](https://github.com/react-native-community/cli/blob/master/docs/commands.md#--deviceid-string), `<iOS simulator name>`|`string`|`simulator`|
|
||||
|`target`|Target to run on. Possible values: `simulator`, `device`, `device=<iOS device name>`, [`<Android emulator/device id>`](https://github.com/react-native-community/cli/blob/master/docs/commands.md#--deviceid-string), `<Android emulator name>`, `<iOS simulator name>`. If the value is `simulator` then the quick pick window will be expanded with the names of the available virtual devices, then the target value in `launch.json` will be changed to the name of the selected virtual device. If you have only one virtual device available, it will be selected automatically.|`string`|`simulator`|
|
||||
|`logCatArguments`|Arguments to be used for LogCat (The LogCat output will appear on an Output Channel). It can be an array such as: `[":S", "ReactNative:V", "ReactNativeJS:V"]`|`array`|`["*:S", "ReactNative:V", "ReactNativeJS:V"]`|
|
||||
|`runArguments`|Run arguments to be passed to `react-native run-<platform>` command (override all other configuration params)|`array`|n/a|
|
||||
|`launchActivity`|The Android activity to be launched for debugging, e.g. it specifies [`--main-activity`](https://github.com/react-native-community/cli/blob/master/docs/commands.md#--main-activity-string) parameter in `react-native` run arguments|`string`|`MainActivity`|
|
||||
|
|
|
@ -295,8 +295,7 @@ gulp.task("clean", () => {
|
|||
"!test/resources/sampleReactNative022Project/**/*.js",
|
||||
".vscode-test/",
|
||||
"nls.*.json",
|
||||
"!test/smoke/**/*.js",
|
||||
"!test/smoke/**/*.js.map",
|
||||
"!test/smoke/**/*",
|
||||
]
|
||||
return del(pathsToDelete, { force: true });
|
||||
});
|
||||
|
|
|
@ -49,6 +49,11 @@
|
|||
"main": "./src/extension/rn-extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "reactNative.launchAndroidSimulator-preview",
|
||||
"title": "%reactNative.command.launchAndroidSimulator.title%",
|
||||
"category": "React Native (Preview)"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.runAndroidSimulator-preview",
|
||||
"title": "%reactNative.command.runAndroidSimulator.title%",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"reactNative.description":"Debugging and integrated commands for React Native",
|
||||
"reactNative.license":"SEE LICENSE IN LICENSE.txt",
|
||||
"reactNative.command.launchAndroidSimulator.title":"Launch Android Emulator",
|
||||
"reactNative.command.runAndroidSimulator.title":"Run Android on Emulator",
|
||||
"reactNative.command.runAndroidDevice.title":"Run Android on Device",
|
||||
"reactNative.command.runIosSimulator.title":"Run iOS on Simulator",
|
||||
|
|
|
@ -78,4 +78,5 @@ export const ERROR_STRINGS = {
|
|||
[InternalErrorCode.CouldntImportScriptAt]: localize("CouldntImportScriptAt", "Couldn't import script at <{0}>"),
|
||||
[InternalErrorCode.RNMessageWithMethodExecuteApplicationScriptDoesntHaveURLProperty]: localize("RNMessageWithMethodExecuteApplicationScriptDoesntHaveURLProperty", "RNMessage with method 'executeApplicationScript' doesn't have 'url' property"),
|
||||
[InternalErrorCode.CouldNotConnectToDebugTarget]: localize("CouldNotConnectToDebugTarget", "Could not connect to the debug target at {0}: {1}"),
|
||||
[InternalErrorCode.FailedToStartAndroidEmulator]: localize("FailedToStartAndroidEmulator", "The command \"emulator -avd {0}\" threw an exception: {1}"),
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ export enum InternalErrorCode {
|
|||
DeveloperDiskImgNotMountable = 302,
|
||||
ApplicationLaunchFailed = 303,
|
||||
ApplicationLaunchTimedOut = 304,
|
||||
FailedToStartAndroidEmulator = 305,
|
||||
|
||||
// iOS Platform errors
|
||||
IOSSimulatorNotLaunchable = 401,
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as nls from "vscode-nls";
|
||||
import { QuickPickOptions, window } from "vscode";
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface IVirtualDevice {
|
||||
name?: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export abstract class VirtualDeviceManager {
|
||||
|
||||
protected async selectVirtualDevice(filter?: (el: IVirtualDevice) => {}): Promise<string | undefined> {
|
||||
const emulatorsList = await this.getVirtualDevicesNamesList(filter);
|
||||
const quickPickOptions: QuickPickOptions = {
|
||||
ignoreFocusOut: true,
|
||||
canPickMany: false,
|
||||
placeHolder: localize("SelectVirtualDevice", "Select virtual device for launch application"),
|
||||
};
|
||||
let result: string | undefined = emulatorsList[0];
|
||||
if (emulatorsList.length > 1) {
|
||||
result = await window.showQuickPick(emulatorsList, quickPickOptions);
|
||||
}
|
||||
return result?.toString();
|
||||
}
|
||||
|
||||
public abstract async startSelection(): Promise<string | undefined>;
|
||||
|
||||
protected abstract async getVirtualDevicesNamesList(filter?: (el: IVirtualDevice) => {}): Promise<string[]>;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import { AdbHelper } from "./adb";
|
||||
import { ChildProcess } from "../../common/node/childProcess";
|
||||
import { IVirtualDevice, VirtualDeviceManager } from "../VirtualDeviceManager";
|
||||
import { OutputChannelLogger } from "../log/OutputChannelLogger";
|
||||
import * as nls from "vscode-nls";
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize = nls.loadMessageBundle();
|
||||
export interface IAndroidEmulator extends IVirtualDevice {
|
||||
}
|
||||
|
||||
export class AndroidEmulatorManager extends VirtualDeviceManager {
|
||||
private static readonly EMULATOR_COMMAND = "emulator";
|
||||
private static readonly EMULATOR_LIST_AVDS_COMMAND = `-list-avds`;
|
||||
private static readonly EMULATOR_AVD_START_COMMAND = `-avd`;
|
||||
|
||||
private static readonly EMULATOR_START_TIMEOUT = 120;
|
||||
|
||||
private logger: OutputChannelLogger = OutputChannelLogger.getChannel(OutputChannelLogger.MAIN_CHANNEL_NAME, true);
|
||||
|
||||
private adbHelper: AdbHelper;
|
||||
private childProcess: ChildProcess;
|
||||
|
||||
constructor(adbHelper: AdbHelper) {
|
||||
super();
|
||||
this.adbHelper = adbHelper;
|
||||
this.childProcess = new ChildProcess();
|
||||
}
|
||||
|
||||
public async startEmulator(target: string): Promise<IAndroidEmulator | null> {
|
||||
const onlineDevices = await this.adbHelper.getOnlineDevices();
|
||||
for (let i = 0; i < onlineDevices.length; i++){
|
||||
if (onlineDevices[i].id === target) {
|
||||
return {id: onlineDevices[i].id};
|
||||
}
|
||||
}
|
||||
if (target && (await this.adbHelper.getOnlineDevices()).length === 0) {
|
||||
if (target === "simulator") {
|
||||
const newEmulator = await this.startSelection();
|
||||
if (newEmulator) {
|
||||
const emulatorId = await this.tryLaunchEmulatorByName(newEmulator);
|
||||
return {name: newEmulator, id: emulatorId};
|
||||
}
|
||||
}
|
||||
else if (!target.includes("device")) {
|
||||
const emulatorId = await this.tryLaunchEmulatorByName(target);
|
||||
return {name: target, id: emulatorId};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async tryLaunchEmulatorByName(emulatorName: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const emulatorProcess = this.childProcess.spawn(AndroidEmulatorManager.EMULATOR_COMMAND, [AndroidEmulatorManager.EMULATOR_AVD_START_COMMAND, emulatorName], {
|
||||
detached: true,
|
||||
});
|
||||
emulatorProcess.outcome.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
emulatorProcess.spawnedProcess.unref();
|
||||
|
||||
const rejectTimeout = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(`Could not start the emulator within ${AndroidEmulatorManager.EMULATOR_START_TIMEOUT} seconds.`);
|
||||
}, AndroidEmulatorManager.EMULATOR_START_TIMEOUT * 1000);
|
||||
|
||||
const bootCheckInterval = setInterval(async () => {
|
||||
const connectedDevices = await this.adbHelper.getOnlineDevices();
|
||||
if (connectedDevices.length > 0) {
|
||||
this.logger.info(localize("EmulatorLaunched", "Launched emulator {0}", emulatorName));
|
||||
cleanup();
|
||||
resolve(connectedDevices[0].id);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(rejectTimeout);
|
||||
clearInterval(bootCheckInterval);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public startSelection(): Promise<string | undefined> {
|
||||
return this.selectVirtualDevice();
|
||||
}
|
||||
|
||||
protected async getVirtualDevicesNamesList(): Promise<string[]> {
|
||||
const res = await this.childProcess.execToString(`${AndroidEmulatorManager.EMULATOR_COMMAND} ${AndroidEmulatorManager.EMULATOR_LIST_AVDS_COMMAND}`);
|
||||
let emulatorsList: string[] = [];
|
||||
if (res) {
|
||||
emulatorsList = res.split(/\r?\n|\r/g);
|
||||
const indexOfBlank = emulatorsList.indexOf("");
|
||||
if (emulatorsList.indexOf("") >= 0) {
|
||||
emulatorsList.splice(indexOfBlank, 1);
|
||||
}
|
||||
}
|
||||
return emulatorsList;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import { InternalErrorCode } from "../../common/error/internalErrorCode";
|
|||
import { ErrorHelper } from "../../common/error/errorHelper";
|
||||
import { isNullOrUndefined } from "util";
|
||||
import { PromiseUtil } from "../../common/node/promise";
|
||||
import { AndroidEmulatorManager, IAndroidEmulator } from "./androidEmulatorManager";
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
@ -51,6 +52,7 @@ export class AndroidPlatform extends GeneralMobilePlatform {
|
|||
private packageName: string;
|
||||
private logCatMonitor: LogCatMonitor | null = null;
|
||||
private adbHelper: AdbHelper;
|
||||
private emulatorManager: AndroidEmulatorManager;
|
||||
|
||||
private needsToLaunchApps: boolean = false;
|
||||
|
||||
|
@ -66,6 +68,7 @@ export class AndroidPlatform extends GeneralMobilePlatform {
|
|||
constructor(protected runOptions: IAndroidRunOptions, platformDeps: MobilePlatformDeps = {}) {
|
||||
super(runOptions, platformDeps);
|
||||
this.adbHelper = new AdbHelper(this.runOptions.projectRoot, this.logger);
|
||||
this.emulatorManager = new AndroidEmulatorManager(this.adbHelper);
|
||||
}
|
||||
|
||||
// TODO: remove this method when sinon will be updated to upper version. Now it is used for tests only.
|
||||
|
@ -73,6 +76,21 @@ export class AndroidPlatform extends GeneralMobilePlatform {
|
|||
this.adbHelper = adbHelper;
|
||||
}
|
||||
|
||||
public resolveVirtualDevice(target: string): Promise<IAndroidEmulator | null> {
|
||||
if (!target.includes("device")) {
|
||||
return this.emulatorManager.startEmulator(target)
|
||||
.then((emulator: IAndroidEmulator | null) => {
|
||||
if (emulator) {
|
||||
GeneralMobilePlatform.setRunArgument(this.runArguments, "--deviceId", emulator.id);
|
||||
}
|
||||
return emulator;
|
||||
});
|
||||
}
|
||||
else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
public runApp(shouldLaunchInAllDevices: boolean = false): Promise<void> {
|
||||
let extProps: any = {
|
||||
platform: {
|
||||
|
|
|
@ -11,7 +11,7 @@ import {PackagerStatusIndicator} from "./packagerStatusIndicator";
|
|||
import {CommandExecutor} from "../common/commandExecutor";
|
||||
import {isNullOrUndefined} from "../common/utils";
|
||||
import {OutputChannelLogger} from "./log/OutputChannelLogger";
|
||||
import {MobilePlatformDeps} from "./generalMobilePlatform";
|
||||
import {MobilePlatformDeps, GeneralMobilePlatform} from "./generalMobilePlatform";
|
||||
import {PlatformResolver} from "./platformResolver";
|
||||
import {ProjectVersionHelper} from "../common/projectVersionHelper";
|
||||
import {TelemetryHelper} from "../common/telemetryHelper";
|
||||
|
@ -25,6 +25,8 @@ import {generateRandomPortNumber} from "../common/extensionHelper";
|
|||
import {DEBUG_TYPES} from "./debugConfigurationProvider";
|
||||
import * as nls from "vscode-nls";
|
||||
import { MultipleLifetimesAppWorker } from "../debugger/appWorker";
|
||||
import { LaunchScenariosManager } from "./launchScenariosManager";
|
||||
import { IVirtualDevice } from "./VirtualDeviceManager";
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
@ -41,6 +43,7 @@ export class AppLauncher {
|
|||
private rnCdpProxy: ReactNativeCDPProxy;
|
||||
private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
|
||||
private logCatMonitor: LogCatMonitor | null = null;
|
||||
private launchScenariosManager: LaunchScenariosManager;
|
||||
|
||||
public static getAppLauncherByProjectRootPath(projectRootPath: string): AppLauncher {
|
||||
const appLauncher = ProjectsStorage.projectsCache[projectRootPath.toLowerCase()];
|
||||
|
@ -57,6 +60,7 @@ export class AppLauncher {
|
|||
this.cdpProxyHostAddress = "127.0.0.1"; // localhost
|
||||
|
||||
const rootPath = workspaceFolder.uri.fsPath;
|
||||
this.launchScenariosManager = new LaunchScenariosManager(rootPath);
|
||||
const projectRootPath = SettingsHelper.getReactNativeProjectRoot(rootPath);
|
||||
this.exponentHelper = new ExponentHelper(rootPath, projectRootPath);
|
||||
const packagerStatusIndicator: PackagerStatusIndicator = new PackagerStatusIndicator(rootPath);
|
||||
|
@ -208,55 +212,55 @@ export class AppLauncher {
|
|||
TelemetryHelper.generate("launch", extProps, (generator) => {
|
||||
generator.step("checkPlatformCompatibility");
|
||||
TargetPlatformHelper.checkTargetPlatformSupport(mobilePlatformOptions.platform);
|
||||
return mobilePlatform.beforeStartPackager()
|
||||
.then(() => {
|
||||
generator.step("startPackager");
|
||||
return mobilePlatform.startPackager();
|
||||
})
|
||||
.then(() => {
|
||||
// We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
|
||||
// and the user needs to Reload JS manually. We prewarm it to prevent that issue
|
||||
generator.step("prewarmBundleCache");
|
||||
this.logger.info(localize("PrewarmingBundleCache", "Prewarming bundle cache. This may take a while ..."));
|
||||
return mobilePlatform.prewarmBundleCache();
|
||||
})
|
||||
.then(() => {
|
||||
generator.step("mobilePlatform.runApp").add("target", mobilePlatformOptions.target, false);
|
||||
this.logger.info(localize("BuildingAndRunningApplication", "Building and running application."));
|
||||
return mobilePlatform.runApp();
|
||||
})
|
||||
.then(() => {
|
||||
if (mobilePlatformOptions.isDirect || !mobilePlatformOptions.enableDebug) {
|
||||
if (mobilePlatformOptions.isDirect && launchArgs.platform === "android") {
|
||||
generator.step("mobilePlatform.enableDirectDebuggingMode");
|
||||
if (mobilePlatformOptions.enableDebug) {
|
||||
this.logger.info(localize("PrepareHermesDebugging", "Prepare Hermes debugging (experimental)"));
|
||||
} else {
|
||||
this.logger.info(localize("PrepareHermesLaunch", "Prepare Hermes launch (experimental)"));
|
||||
}
|
||||
generator.step("resolveEmulator");
|
||||
return this.resolveAndSaveVirtualDevice(mobilePlatform, launchArgs, mobilePlatformOptions)
|
||||
.then(() => mobilePlatform.beforeStartPackager())
|
||||
.then(() => {
|
||||
generator.step("startPackager");
|
||||
return mobilePlatform.startPackager();
|
||||
})
|
||||
.then(() => {
|
||||
// We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
|
||||
// and the user needs to Reload JS manually. We prewarm it to prevent that issue
|
||||
generator.step("prewarmBundleCache");
|
||||
this.logger.info(localize("PrewarmingBundleCache", "Prewarming bundle cache. This may take a while ..."));
|
||||
return mobilePlatform.prewarmBundleCache();
|
||||
})
|
||||
.then(() => {
|
||||
generator.step("mobilePlatform.runApp").add("target", mobilePlatformOptions.target, false);
|
||||
this.logger.info(localize("BuildingAndRunningApplication", "Building and running application."));
|
||||
return mobilePlatform.runApp();
|
||||
})
|
||||
.then(() => {
|
||||
if (mobilePlatformOptions.isDirect || !mobilePlatformOptions.enableDebug) {
|
||||
if (mobilePlatformOptions.isDirect && launchArgs.platform === "android") {
|
||||
generator.step("mobilePlatform.enableDirectDebuggingMode");
|
||||
if (mobilePlatformOptions.enableDebug) {
|
||||
this.logger.info(localize("PrepareHermesDebugging", "Prepare Hermes debugging (experimental)"));
|
||||
} else {
|
||||
generator.step("mobilePlatform.disableJSDebuggingMode");
|
||||
this.logger.info(localize("DisableJSDebugging", "Disable JS Debugging"));
|
||||
this.logger.info(localize("PrepareHermesLaunch", "Prepare Hermes launch (experimental)"));
|
||||
}
|
||||
return mobilePlatform.disableJSDebuggingMode();
|
||||
} else {
|
||||
generator.step("mobilePlatform.disableJSDebuggingMode");
|
||||
this.logger.info(localize("DisableJSDebugging", "Disable JS Debugging"));
|
||||
}
|
||||
generator.step("mobilePlatform.enableJSDebuggingMode");
|
||||
this.logger.info(localize("EnableJSDebugging", "Enable JS Debugging"));
|
||||
return mobilePlatform.enableJSDebuggingMode();
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
if (!mobilePlatformOptions.enableDebug && launchArgs.platform === "ios") {
|
||||
// If we disable debugging mode for iOS scenarios, we'll we ignore the error and run the 'run-ios' command anyway,
|
||||
// since the error doesn't affects an application launch process
|
||||
return resolve();
|
||||
}
|
||||
generator.addError(error);
|
||||
this.logger.error(error);
|
||||
reject(error);
|
||||
});
|
||||
return mobilePlatform.disableJSDebuggingMode();
|
||||
}
|
||||
generator.step("mobilePlatform.enableJSDebuggingMode");
|
||||
this.logger.info(localize("EnableJSDebugging", "Enable JS Debugging"));
|
||||
return mobilePlatform.enableJSDebuggingMode();
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(error => {
|
||||
if (!mobilePlatformOptions.enableDebug && launchArgs.platform === "ios") {
|
||||
// If we disable debugging mode for iOS scenarios, we'll we ignore the error and run the 'run-ios' command anyway,
|
||||
// since the error doesn't affects an application launch process
|
||||
return resolve();
|
||||
}
|
||||
generator.addError(error);
|
||||
this.logger.error(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -279,6 +283,25 @@ export class AppLauncher {
|
|||
});
|
||||
}
|
||||
|
||||
private resolveAndSaveVirtualDevice(mobilePlatform: GeneralMobilePlatform, launchArgs: any, mobilePlatformOptions: any): Promise<void> {
|
||||
if (launchArgs.target && mobilePlatformOptions.platform === "android") {
|
||||
return mobilePlatform.resolveVirtualDevice(launchArgs.target)
|
||||
.then((emulator: IVirtualDevice | null) => {
|
||||
if (emulator && emulator.name) {
|
||||
this.launchScenariosManager.updateLaunchScenario(launchArgs, {target: emulator.name});
|
||||
if (launchArgs.platform === "android") {
|
||||
mobilePlatformOptions.target = emulator.id;
|
||||
}
|
||||
}
|
||||
else if (!emulator && mobilePlatformOptions.target.indexOf("device") < 0) {
|
||||
mobilePlatformOptions.target = "simulator";
|
||||
mobilePlatform.runArguments = mobilePlatform.getRunArguments();
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private requestSetup(args: any): any {
|
||||
const workspaceFolder: vscode.WorkspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(vscode.Uri.file(args.cwd || args.program));
|
||||
const projectRootPath = this.getProjectRoot(args);
|
||||
|
|
|
@ -22,6 +22,8 @@ import * as nls from "vscode-nls";
|
|||
import {ErrorHelper} from "../common/error/errorHelper";
|
||||
import {InternalErrorCode} from "../common/error/internalErrorCode";
|
||||
import {AppLauncher} from "./appLauncher";
|
||||
import { AndroidEmulatorManager } from "./android/androidEmulatorManager";
|
||||
import { AdbHelper } from "./android/adb";
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
@ -99,6 +101,16 @@ export class CommandPaletteHandler {
|
|||
});
|
||||
}
|
||||
|
||||
public static async launchAndroidEmulator(): Promise<void> {
|
||||
const appLauncher = await this.selectProject();
|
||||
const adbHelper = new AdbHelper(appLauncher.getPackager().getProjectPath());
|
||||
const androidEmulatorManager = new AndroidEmulatorManager(adbHelper);
|
||||
const emulator = await androidEmulatorManager.startSelection();
|
||||
if (emulator) {
|
||||
androidEmulatorManager.tryLaunchEmulatorByName(emulator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the 'react-native run-android' command
|
||||
*/
|
||||
|
@ -111,16 +123,17 @@ export class CommandPaletteHandler {
|
|||
appLauncher.setReactNativeVersions(versions);
|
||||
return this.executeCommandInContext("runAndroid", appLauncher.getWorkspaceFolder(), () => {
|
||||
const platform = <AndroidPlatform>this.createPlatform(appLauncher, "android", AndroidPlatform, target);
|
||||
return platform.beforeStartPackager()
|
||||
.then(() => {
|
||||
return platform.startPackager();
|
||||
})
|
||||
.then(() => {
|
||||
return platform.runApp(/*shouldLaunchInAllDevices*/true);
|
||||
})
|
||||
.then(() => {
|
||||
return platform.disableJSDebuggingMode();
|
||||
});
|
||||
return platform.resolveVirtualDevice(target)
|
||||
.then(() => platform.beforeStartPackager())
|
||||
.then(() => {
|
||||
return platform.startPackager();
|
||||
})
|
||||
.then(() => {
|
||||
return platform.runApp(/*shouldLaunchInAllDevices*/true);
|
||||
})
|
||||
.then(() => {
|
||||
return platform.disableJSDebuggingMode();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -138,7 +151,8 @@ export class CommandPaletteHandler {
|
|||
TargetPlatformHelper.checkTargetPlatformSupport("ios");
|
||||
return this.executeCommandInContext("runIos", appLauncher.getWorkspaceFolder(), () => {
|
||||
const platform = <IOSPlatform>this.createPlatform(appLauncher, "ios", IOSPlatform, target);
|
||||
return platform.beforeStartPackager()
|
||||
return platform.resolveVirtualDevice(target)
|
||||
.then(() => platform.beforeStartPackager())
|
||||
.then(() => {
|
||||
return platform.startPackager();
|
||||
})
|
||||
|
|
|
@ -8,6 +8,8 @@ import {PackagerStatusIndicator, PackagerStatus} from "./packagerStatusIndicator
|
|||
import {SettingsHelper} from "./settingsHelper";
|
||||
import {OutputChannelLogger} from "./log/OutputChannelLogger";
|
||||
import * as nls from "vscode-nls";
|
||||
import { isBoolean } from "util";
|
||||
import { IVirtualDevice } from "./VirtualDeviceManager";
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
@ -39,6 +41,10 @@ export class GeneralMobilePlatform {
|
|||
this.runArguments = this.getRunArguments();
|
||||
}
|
||||
|
||||
public resolveVirtualDevice(target: string): Promise<IVirtualDevice | null> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
public runApp(): Promise<void> {
|
||||
this.logger.info(localize("ConnectedToPackager", "Connected to packager. You can now open your app in the simulator."));
|
||||
return Promise.resolve();
|
||||
|
@ -81,6 +87,40 @@ export class GeneralMobilePlatform {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public static removeRunArgument(runArguments: any[], optName: string, binary: boolean) {
|
||||
const optIdx = runArguments.indexOf(optName);
|
||||
if (optIdx > -1) {
|
||||
if (binary) {
|
||||
runArguments.splice(optIdx, 1);
|
||||
}
|
||||
else {
|
||||
runArguments.splice(optIdx, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static setRunArgument(runArguments: any[], optName: string, value: string | boolean) {
|
||||
const isBinary = isBoolean(value);
|
||||
const optIdx = runArguments.indexOf(optName);
|
||||
if (optIdx > -1) {
|
||||
if (isBinary && !value) {
|
||||
GeneralMobilePlatform.removeRunArgument(runArguments, optName, true);
|
||||
}
|
||||
if (!isBinary) {
|
||||
runArguments[optIdx + 1] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isBinary && value) {
|
||||
runArguments.push(optName);
|
||||
}
|
||||
if (!isBinary) {
|
||||
runArguments.push(optName);
|
||||
runArguments.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static getOptFromRunArgs(runArguments: any[], optName: string, binary: boolean = false): any {
|
||||
if (runArguments.length > 0) {
|
||||
const optIdx = runArguments.indexOf(optName);
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import stripJsonComments = require("strip-json-comments");
|
||||
|
||||
export interface IConfiguration {
|
||||
name: string;
|
||||
platform?: string;
|
||||
target?: string;
|
||||
type?: string;
|
||||
request?: string;
|
||||
}
|
||||
export interface ILaunchScenarios {
|
||||
configurations?: IConfiguration[];
|
||||
}
|
||||
|
||||
export class LaunchScenariosManager {
|
||||
private pathToLaunchFile: string;
|
||||
private launchScenarios: ILaunchScenarios;
|
||||
|
||||
constructor(rootPath: string) {
|
||||
this.pathToLaunchFile = path.resolve(rootPath, ".vscode", "launch.json");
|
||||
}
|
||||
|
||||
public getLaunchScenarios(): ILaunchScenarios {
|
||||
return this.launchScenarios;
|
||||
}
|
||||
|
||||
private getFirstScenarioIndexByParams(scenario: IConfiguration): number | null {
|
||||
if (this.launchScenarios.configurations) {
|
||||
for (let i = 0; i < this.launchScenarios.configurations.length; i++) {
|
||||
const config = this.launchScenarios.configurations[i];
|
||||
if (scenario.name === config.name &&
|
||||
scenario.platform === config.platform &&
|
||||
scenario.type === config.type &&
|
||||
scenario.request === config.request) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private writeLaunchScenarios(launch: ILaunchScenarios = this.launchScenarios): void {
|
||||
if (fs.existsSync(this.pathToLaunchFile)) {
|
||||
fs.writeFileSync(this.pathToLaunchFile, JSON.stringify(this.launchScenarios, null, 4));
|
||||
}
|
||||
}
|
||||
|
||||
public readLaunchScenarios(): void {
|
||||
if (fs.existsSync(this.pathToLaunchFile)) {
|
||||
const content = fs.readFileSync(this.pathToLaunchFile, "utf8");
|
||||
this.launchScenarios = JSON.parse(stripJsonComments(content));
|
||||
}
|
||||
}
|
||||
|
||||
public updateLaunchScenario(launchArgs: any, updates: any) {
|
||||
this.readLaunchScenarios();
|
||||
let launchConfigIndex = this.getFirstScenarioIndexByParams(launchArgs);
|
||||
const launchScenarios = this.getLaunchScenarios();
|
||||
if (launchConfigIndex !== null && launchScenarios.configurations) {
|
||||
Object.assign(launchScenarios.configurations[launchConfigIndex], updates);
|
||||
this.writeLaunchScenarios(launchScenarios);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -221,6 +221,7 @@ function isSupportedVersion(version: string): boolean {
|
|||
}
|
||||
|
||||
function registerReactNativeCommands(context: vscode.ExtensionContext): void {
|
||||
registerVSCodeCommand(context, "launchAndroidSimulator-preview", ErrorHelper.getInternalError(InternalErrorCode.FailedToStartAndroidEmulator), () => CommandPaletteHandler.launchAndroidEmulator());
|
||||
registerVSCodeCommand(context, "runAndroidSimulator-preview", ErrorHelper.getInternalError(InternalErrorCode.FailedToRunOnAndroid), () => CommandPaletteHandler.runAndroid("simulator"));
|
||||
registerVSCodeCommand(context, "runAndroidDevice-preview", ErrorHelper.getInternalError(InternalErrorCode.FailedToRunOnAndroid), () => CommandPaletteHandler.runAndroid("device"));
|
||||
registerVSCodeCommand(context, "runIosSimulator-preview", ErrorHelper.getInternalError(InternalErrorCode.FailedToRunOnIos), () => CommandPaletteHandler.runIos("simulator"));
|
||||
|
|
|
@ -8,30 +8,6 @@ import * as path from "path";
|
|||
|
||||
suite("generalMobilePlatform", function () {
|
||||
suite("extensionContext", function () {
|
||||
suite("getOptFromRunArgs", function() {
|
||||
test("should return undefined if arguments are empty", function () {
|
||||
const args: any[] = [];
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1", true), undefined);
|
||||
});
|
||||
|
||||
test("should return correct result for binary parameters", function () {
|
||||
const args: any[] = ["--param1", "param2"];
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1", true), true);
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "param2", true), true);
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "--unknown", true), undefined);
|
||||
});
|
||||
|
||||
test("should return correct result for non-binary parameters", function () {
|
||||
const args: any[] = ["--param1", "value1", "--param2=value2", "param3=value3", "param4value4"];
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1", false), "value1");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param2", false), "value2");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1"), "value1");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param2"), "value2");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "param3", false), "value3");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "param4", false), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
suite("getEnvArgument", function() {
|
||||
const origEnv: any = {"test1": "origEnv", "test2": "origEnv", "test3": "origEnv"};
|
||||
|
||||
|
@ -81,5 +57,90 @@ suite("generalMobilePlatform", function () {
|
|||
"test5": "envFile"});
|
||||
});
|
||||
});
|
||||
|
||||
suite("runArguments", function() {
|
||||
let mockRunArguments: any[] = [];
|
||||
const paramWithValue = "--paramWithValue";
|
||||
const binaryParam = "--binaryParam";
|
||||
|
||||
suite("getOptFromRunArgs", function() {
|
||||
test("should return undefined if arguments are empty", function () {
|
||||
const args: any[] = [];
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1", true), undefined);
|
||||
});
|
||||
|
||||
test("should return correct result for binary parameters", function () {
|
||||
const args: any[] = ["--param1", "param2"];
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1", true), true);
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "param2", true), true);
|
||||
assert.strictEqual(GeneralMobilePlatform.getOptFromRunArgs(args, "--unknown", true), undefined);
|
||||
});
|
||||
|
||||
test("should return correct result for non-binary parameters", function () {
|
||||
const args: any[] = ["--param1", "value1", "--param2=value2", "param3=value3", "param4value4"];
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1", false), "value1");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param2", false), "value2");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param1"), "value1");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "--param2"), "value2");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "param3", false), "value3");
|
||||
assert.equal(GeneralMobilePlatform.getOptFromRunArgs(args, "param4", false), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
suite("removeRunArgument", function() {
|
||||
setup(() => {
|
||||
mockRunArguments = [paramWithValue, "value", binaryParam];
|
||||
});
|
||||
|
||||
test("existing binary parameter should be removed from runArguments", function() {
|
||||
GeneralMobilePlatform.removeRunArgument(mockRunArguments, binaryParam, true);
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value"]);
|
||||
});
|
||||
|
||||
test("existing parameter and its value should be removed from runArguments", function() {
|
||||
GeneralMobilePlatform.removeRunArgument(mockRunArguments, paramWithValue, false);
|
||||
assert.deepEqual(mockRunArguments, [binaryParam]);
|
||||
});
|
||||
|
||||
test("nothing should happen if try to remove not existing parameter", function() {
|
||||
GeneralMobilePlatform.removeRunArgument(mockRunArguments, "--undefined", false);
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value", binaryParam]);
|
||||
GeneralMobilePlatform.removeRunArgument(mockRunArguments, "--undefined", true);
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value", binaryParam]);
|
||||
});
|
||||
});
|
||||
|
||||
suite("setRunArgument", function() {
|
||||
setup(() => {
|
||||
mockRunArguments = [paramWithValue, "value", binaryParam];
|
||||
});
|
||||
|
||||
test("new binary parameter should be added to runArguments", function() {
|
||||
GeneralMobilePlatform.setRunArgument(mockRunArguments, "--newBinaryParam", true);
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value", binaryParam, "--newBinaryParam"]);
|
||||
});
|
||||
|
||||
test("new parameter with value and its value should be added to runArguments", function() {
|
||||
GeneralMobilePlatform.setRunArgument(mockRunArguments, "--newParamWithValue", "itsValue");
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value", binaryParam, "--newParamWithValue", "itsValue"]);
|
||||
});
|
||||
|
||||
test("value of existing parameter with value should be overwritten by new value", function() {
|
||||
GeneralMobilePlatform.setRunArgument(mockRunArguments, paramWithValue, "newValue");
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "newValue", binaryParam]);
|
||||
});
|
||||
|
||||
test("new binary parameter should not be added to runArguments if its value if false", function() {
|
||||
GeneralMobilePlatform.setRunArgument(mockRunArguments, "--newBinaryParam", false);
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value", binaryParam]);
|
||||
});
|
||||
|
||||
test("existing binary parameter should be removed from runArguments if its value if false", function() {
|
||||
GeneralMobilePlatform.setRunArgument(mockRunArguments, binaryParam, false);
|
||||
assert.deepEqual(mockRunArguments, [paramWithValue, "value"]);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { LaunchScenariosManager } from "../../src/extension/launchScenariosManager";
|
||||
import * as assert from "assert";
|
||||
|
||||
suite("LaunchScenarioManager", function() {
|
||||
const tmpPath = path.resolve(__dirname, "..", "resources", "tmp");
|
||||
const launchPath = path.resolve(tmpPath, ".vscode", "launch.json");
|
||||
const launchContent = {
|
||||
version: "0.2.0",
|
||||
configurations: [
|
||||
{
|
||||
name: "Debug Android",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnative-preview",
|
||||
request: "launch",
|
||||
platform: "android",
|
||||
target: "simulator"
|
||||
},
|
||||
{
|
||||
name: "Debug Android (Hermes) - Experimental",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnativedirect-preview",
|
||||
request: "launch",
|
||||
platform: "android",
|
||||
env: {
|
||||
env1: "value1",
|
||||
env2: "value2"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Attach to Hermes application - Experimental",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnativedirect-preview",
|
||||
request: "attach"
|
||||
},
|
||||
{
|
||||
name: "Debug iOS",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnative-preview",
|
||||
request: "launch",
|
||||
platform: "ios"
|
||||
},
|
||||
{
|
||||
name: "Attach to packager",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnative-preview",
|
||||
request: "attach"
|
||||
},
|
||||
{
|
||||
name: "Debug in Exponent",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnative-preview",
|
||||
request: "launch",
|
||||
platform: "exponent"
|
||||
},
|
||||
{
|
||||
name: "Debug in Exponent (LAN)",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnative-preview",
|
||||
request: "launch",
|
||||
platform: "exponent",
|
||||
expoHostType: "lan"
|
||||
},
|
||||
{
|
||||
name: "Debug in Exponent (Local)",
|
||||
cwd: "${workspaceFolder}",
|
||||
type: "reactnative-preview",
|
||||
request: "launch",
|
||||
platform: "exponent",
|
||||
expoHostType: "local"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
suiteSetup(() => {
|
||||
fs.mkdirSync(tmpPath);
|
||||
fs.mkdirSync(path.resolve(tmpPath, ".vscode"));
|
||||
});
|
||||
|
||||
suiteTeardown(() => {
|
||||
fs.unlinkSync(launchPath);
|
||||
fs.rmdirSync(path.resolve(tmpPath, ".vscode"));
|
||||
fs.rmdirSync(tmpPath);
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
fs.writeFileSync(launchPath, JSON.stringify(launchContent, null, 4));
|
||||
});
|
||||
|
||||
suite("updateLaunchScenario", function() {
|
||||
|
||||
function autogenerateUpdateAndCheck(configIndex: number, updates: any) {
|
||||
const config = Object.assign({}, launchContent.configurations[configIndex]);
|
||||
Object.assign(config, {
|
||||
otherParam: "value1",
|
||||
otherObject: {
|
||||
innerParam: "value2"
|
||||
}
|
||||
});
|
||||
const result = Object.assign({}, launchContent);
|
||||
Object.assign(result.configurations[configIndex], updates);
|
||||
|
||||
tryUpdateAndCheck(config, updates, result);
|
||||
}
|
||||
|
||||
function tryUpdateAndCheck(config: any, updates: any, result: any) {
|
||||
const manager = new LaunchScenariosManager(tmpPath);
|
||||
manager.updateLaunchScenario(config, updates);
|
||||
const launchObject = JSON.parse(fs.readFileSync(launchPath).toString());
|
||||
console.log(launchObject);
|
||||
console.log(result);
|
||||
assert.deepStrictEqual(launchObject, result);
|
||||
}
|
||||
|
||||
test("should overwrite existing parameters for proper configuration", function() {
|
||||
autogenerateUpdateAndCheck(2, {env:{env1: "newValue"}});
|
||||
});
|
||||
|
||||
test("should add new parameters to proper configuration", function() {
|
||||
autogenerateUpdateAndCheck(5, {env:{env1: "newValue1", env2: "newValue2"}});
|
||||
});
|
||||
|
||||
test("should nothing happens if launch.json do not contains config", function() {
|
||||
const config = {
|
||||
name: "Debug Android",
|
||||
type: "reactnative-preview",
|
||||
request: "launch",
|
||||
platform: "android",
|
||||
};
|
||||
|
||||
let configCopy = Object.assign({}, config);
|
||||
tryUpdateAndCheck(Object.assign(configCopy, {name: "Other name"}), {param: "value1"}, launchContent);
|
||||
configCopy = Object.assign({}, config);
|
||||
tryUpdateAndCheck(Object.assign(configCopy, {type: "Other type"}), {param: "value2"}, launchContent);
|
||||
configCopy = Object.assign({}, config);
|
||||
tryUpdateAndCheck(Object.assign(configCopy, {request: "Other request"}), {param: "value3"}, launchContent);
|
||||
configCopy = Object.assign({}, config);
|
||||
tryUpdateAndCheck(Object.assign(configCopy, {platform: "Other platform"}), {param: "value4"}, launchContent);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,7 +6,8 @@
|
|||
"cwd": "${workspaceFolder}",
|
||||
"type": "reactnative-preview",
|
||||
"request": "launch",
|
||||
"platform": "android"
|
||||
"platform": "android",
|
||||
"target": "simulator"
|
||||
},
|
||||
{
|
||||
"name": "Debug Android (Hermes) - Experimental",
|
||||
|
|
|
@ -5,7 +5,12 @@ import * as cp from "child_process";
|
|||
import { SmokeTestsConstants } from "./smokeTestsConstants";
|
||||
import { sleep } from "./utilities";
|
||||
|
||||
export interface IDevice {
|
||||
id: string;
|
||||
isOnline: boolean;
|
||||
}
|
||||
export class AndroidEmulatorHelper {
|
||||
private static EMULATOR_START_TIMEOUT = 120;
|
||||
|
||||
public static androidEmulatorPort = 5554;
|
||||
public static androidEmulatorName = `emulator-${AndroidEmulatorHelper.androidEmulatorPort}`;
|
||||
|
@ -17,6 +22,50 @@ export class AndroidEmulatorHelper {
|
|||
return process.env.ANDROID_EMULATOR;
|
||||
}
|
||||
|
||||
public static getOnlineDevices(): IDevice[] {
|
||||
const devices = AndroidEmulatorHelper.getConnectedDevices();
|
||||
return devices.filter(device => device.isOnline);
|
||||
}
|
||||
|
||||
public static getConnectedDevices(): IDevice[] {
|
||||
const devices = cp.execSync("adb devices").toString();
|
||||
return AndroidEmulatorHelper.parseConnectedDevices(devices);
|
||||
}
|
||||
|
||||
private static parseConnectedDevices(input: string): IDevice[] {
|
||||
let result: IDevice[] = [];
|
||||
let regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg");
|
||||
let match = regex.exec(input);
|
||||
while (match != null) {
|
||||
result.push({ id: match[1], isOnline: match[2] === "device"});
|
||||
match = regex.exec(input);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async waitUntilEmulatorStarting(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rejectTimeout = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(`Could not start the emulator within ${AndroidEmulatorHelper.EMULATOR_START_TIMEOUT} seconds.`);
|
||||
}, AndroidEmulatorHelper.EMULATOR_START_TIMEOUT * 1000);
|
||||
|
||||
const bootCheckInterval = setInterval(async () => {
|
||||
const connectedDevices = AndroidEmulatorHelper.getOnlineDevices();
|
||||
if (connectedDevices.length > 0) {
|
||||
console.log(`*** Android emulator has been started.`);
|
||||
cleanup();
|
||||
resolve();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(rejectTimeout);
|
||||
clearInterval(bootCheckInterval);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public static async runAndroidEmulator() {
|
||||
this.terminateAndroidEmulator();
|
||||
// Boot options for emulator - https://developer.android.com/studio/run/emulator-commandline
|
||||
|
@ -67,12 +116,13 @@ export class AndroidEmulatorHelper {
|
|||
|
||||
// Terminates emulator "emulator-PORT" if it exists, where PORT is 5554 by default
|
||||
public static terminateAndroidEmulator() {
|
||||
let devices = cp.execSync("adb devices").toString().trim();
|
||||
let devices = this.getOnlineDevices();
|
||||
console.log("*** Checking for running android emulators...");
|
||||
if (devices !== "List of devices attached") {
|
||||
// Check if we already have a running emulator, and terminate it if it so
|
||||
console.log(`Terminating Android '${this.androidEmulatorName}'...`);
|
||||
cp.execSync(`adb -s ${this.androidEmulatorName} emu kill`, {stdio: "inherit"});
|
||||
if (devices.length !== 0) {
|
||||
devices.forEach((device) => {
|
||||
console.log(`Terminating Android '${device.id}'...`);
|
||||
cp.execSync(`adb -s ${device.id} emu kill`, {stdio: "inherit"});
|
||||
});
|
||||
} else {
|
||||
console.log("*** No running android emulators found");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as cp from "child_process";
|
||||
import * as request from "request";
|
||||
|
@ -176,6 +177,46 @@ export interface ExpoLaunch {
|
|||
failed: boolean;
|
||||
}
|
||||
|
||||
function getEmulatorsNamesList(): string[] {
|
||||
const res = cp.execSync("emulator -list-avds");
|
||||
let emulatorsList: string[] = [];
|
||||
if (res) {
|
||||
const resString = res.toString();
|
||||
emulatorsList = resString.split(/\r?\n|\r/g);
|
||||
}
|
||||
return emulatorsList;
|
||||
}
|
||||
|
||||
export function waitUntilLaunchScenarioTargetUpdate(workspaceRoot: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const LAUNCH_UPDATE_TIMEOUT = 30;
|
||||
const rejectTimeout = setTimeout(() => {
|
||||
cleanup();
|
||||
resolve(false);
|
||||
}, LAUNCH_UPDATE_TIMEOUT * 1000);
|
||||
|
||||
const bootCheckInterval = setInterval(async () => {
|
||||
const isUpdated = isLaunchScenarioTargetUpdate(workspaceRoot);
|
||||
if (isUpdated) {
|
||||
cleanup();
|
||||
resolve(true);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(rejectTimeout);
|
||||
clearInterval(bootCheckInterval);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function isLaunchScenarioTargetUpdate(workspaceRoot: string): boolean {
|
||||
const pathToLaunchFile = path.resolve(workspaceRoot, ".vscode", "launch.json");
|
||||
const emulatorsList = getEmulatorsNamesList();
|
||||
const firstEmulatorName = emulatorsList[0];
|
||||
return findStringInFile(pathToLaunchFile, `"target": "${firstEmulatorName}"`);
|
||||
}
|
||||
|
||||
export async function waitForRunningPackager(filePath: string) {
|
||||
let awaitRetries: number = 5;
|
||||
let retry = 1;
|
||||
|
|
Загрузка…
Ссылка в новой задаче