Skip to content

Commit

Permalink
Debug on emulator by default when multiple devices/emulators attached (
Browse files Browse the repository at this point in the history
…#2957)

When there are multiple devices/emulators, calling `tns debug android` will fail that multiple devices are attached.
This break VS Code as previous code of CLI has been using emulators by default.
Fix this by using the most recent version (highest API Level) of running emulators, i.e. in case you have three Android emulators with version 5.1, 6.0 and 7.0, the one with 7.0 will be used for debugging.

In case the terminal is interactive, CLI will prompt the user for selecting a device on which to start debug operation.
In case the terminal is not interactive and there's no emulator running, the device with highest API level will be used.
  • Loading branch information
rosen-vladimirov authored Jul 10, 2017
1 parent e191e95 commit 19e6829
Show file tree
Hide file tree
Showing 10 changed files with 398 additions and 62 deletions.
1 change: 0 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-shell");
grunt.loadNpmTasks("grunt-ts");
grunt.loadNpmTasks("grunt-tslint");

grunt.registerTask("set_package_version", function (version) {
var buildVersion = version !== undefined ? version : buildNumber;
Expand Down
89 changes: 71 additions & 18 deletions lib/commands/debug.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export abstract class DebugPlatformCommand implements ICommand {
import { CONNECTED_STATUS } from "../common/constants";
import { isInteractive } from "../common/helpers";
import { DebugCommandErrors } from "../constants";

export abstract class DebugPlatformCommand implements ICommand {
public allowedParameters: ICommandParameter[] = [];
public platform: string;

Expand All @@ -12,7 +16,8 @@
protected $logger: ILogger,
protected $errors: IErrors,
private $debugLiveSyncService: IDebugLiveSyncService,
private $config: IConfiguration) {
private $config: IConfiguration,
private $prompter: IPrompter) {
this.$projectData.initializeProjectData();
}

Expand All @@ -29,11 +34,9 @@

this.$config.debugLivesync = true;

await this.$devicesService.detectCurrentlyAttachedDevices();
const selectedDeviceForDebug = await this.getDeviceForDebug();

// Now let's take data for each device:
const devices = this.$devicesService.getDeviceInstances();
const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform)
const deviceDescriptors: ILiveSyncDeviceInfo[] = [selectedDeviceForDebug]
.map(d => {
const info: ILiveSyncDeviceInfo = {
identifier: d.deviceInfo.identifier,
Expand Down Expand Up @@ -70,6 +73,62 @@
await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo);
}

public async getDeviceForDebug(): Promise<Mobile.IDevice> {
if (this.$options.forDevice && this.$options.emulator) {
this.$errors.fail(DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR);
}

await this.$devicesService.detectCurrentlyAttachedDevices();

if (this.$options.device) {
const device = await this.$devicesService.getDevice(this.$options.device);
return device;
}

// Now let's take data for each device:
const availableDevicesAndEmulators = this.$devicesService.getDeviceInstances()
.filter(d => d.deviceInfo.status === CONNECTED_STATUS && (!this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()));

const selectedDevices = availableDevicesAndEmulators.filter(d => this.$options.emulator ? d.isEmulator : (this.$options.forDevice ? !d.isEmulator : true));

if (selectedDevices.length > 1) {
if (isInteractive()) {
const choices = selectedDevices.map(e => `${e.deviceInfo.identifier} - ${e.deviceInfo.displayName}`);

const selectedDeviceString = await this.$prompter.promptForChoice("Select device for debugging", choices);

const selectedDevice = _.find(selectedDevices, d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}` === selectedDeviceString);
return selectedDevice;
} else {
const sortedInstances = _.sortBy(selectedDevices, e => e.deviceInfo.version);
const emulators = sortedInstances.filter(e => e.isEmulator);
const devices = sortedInstances.filter(d => !d.isEmulator);
let selectedInstance: Mobile.IDevice;

if (this.$options.emulator || this.$options.forDevice) {
// When --emulator or --forDevice is passed, the instances are already filtered
// So we are sure we have exactly the type we need and we can safely return the last one (highest OS version).
selectedInstance = _.last(sortedInstances);
} else {
if (emulators.length) {
selectedInstance = _.last(emulators);
} else {
selectedInstance = _.last(devices);
}
}

this.$logger.warn(`Multiple devices/emulators found. Starting debugger on ${selectedInstance.deviceInfo.identifier}. ` +
"If you want to debug on specific device/emulator, you can specify it with --device option.");

return selectedInstance;
}
} else if (selectedDevices.length === 1) {
return _.head(selectedDevices);
}

this.$errors.failWithoutHelp(DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS);
}

public async canExecute(args: string[]): Promise<boolean> {
if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) {
this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`);
Expand All @@ -85,14 +144,6 @@
emulator: this.$options.emulator,
skipDeviceDetectionInterval: true
});
// Start emulator if --emulator is selected or no devices found.
if (this.$options.emulator || this.$devicesService.deviceCount === 0) {
return true;
}

if (this.$devicesService.deviceCount > 1) {
this.$errors.failWithoutHelp("Multiple devices found! To debug on specific device please select device with --device option.");
}

return true;
}
Expand All @@ -111,9 +162,10 @@ export class DebugIOSCommand extends DebugPlatformCommand {
$projectData: IProjectData,
$platformsData: IPlatformsData,
$iosDeviceOperations: IIOSDeviceOperations,
$debugLiveSyncService: IDebugLiveSyncService) {
$debugLiveSyncService: IDebugLiveSyncService,
$prompter: IPrompter) {
super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger,
$errors, $debugLiveSyncService, $config);
$errors, $debugLiveSyncService, $config, $prompter);
// Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket.
// In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket.
// That's why the `$ tns debug ios --justlaunch` command will not release the terminal.
Expand Down Expand Up @@ -146,9 +198,10 @@ export class DebugAndroidCommand extends DebugPlatformCommand {
$options: IOptions,
$projectData: IProjectData,
$platformsData: IPlatformsData,
$debugLiveSyncService: IDebugLiveSyncService) {
$debugLiveSyncService: IDebugLiveSyncService,
$prompter: IPrompter) {
super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger,
$errors, $debugLiveSyncService, $config);
$errors, $debugLiveSyncService, $config, $prompter);
}

public async canExecute(args: string[]): Promise<boolean> {
Expand Down
2 changes: 1 addition & 1 deletion lib/common
5 changes: 5 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,8 @@ export const CONNECTION_ERROR_EVENT_NAME = "connectionError";
export const VERSION_STRING = "version";
export const INSPECTOR_CACHE_DIRNAME = "ios-inspector";
export const POST_INSTALL_COMMAND_NAME = "post-install-cli";

export class DebugCommandErrors {
public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them.";
public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options.";
}
2 changes: 1 addition & 1 deletion lib/definitions/debug.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ interface IDebugDataService {
* @param {IOptions} options The options based on which debugData will be created
* @returns {IDebugData} Data describing the required information for starting debug process.
*/
createDebugData(projectData: IProjectData, options: IOptions): IDebugData;
createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/services/debug-data-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export class DebugDataService implements IDebugDataService {
public createDebugData(projectData: IProjectData, options: IOptions): IDebugData {
public createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData {
return {
applicationIdentifier: projectData.projectId,
projectDir: projectData.projectDir,
Expand Down
9 changes: 7 additions & 2 deletions lib/services/debug-service-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ export abstract class DebugServiceBase extends EventEmitter implements IPlatform
protected getCanExecuteAction(deviceIdentifier: string): (device: Mobile.IDevice) => boolean {
return (device: Mobile.IDevice): boolean => {
if (deviceIdentifier) {
return device.deviceInfo.identifier === deviceIdentifier
|| device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier;
let isSearchedDevice = device.deviceInfo.identifier === deviceIdentifier;
if (!isSearchedDevice) {
const deviceByDeviceOption = this.$devicesService.getDeviceByDeviceOption();
isSearchedDevice = deviceByDeviceOption && device.deviceInfo.identifier === deviceByDeviceOption.deviceInfo.identifier;
}

return isSearchedDevice;
} else {
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/services/livesync/debug-livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS
teamId: this.$options.teamId
};

let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options);
let debugData = this.$debugDataService.createDebugData(this.$projectData, { device: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier });
const debugService = this.$debugService.getDebugService(liveSyncResultInfo.deviceAppData.device);

await this.$platformService.trackProjectType(this.$projectData);
Expand Down
Loading

0 comments on commit 19e6829

Please sign in to comment.