Skip to content

Commit 19e6829

Browse files
Debug on emulator by default when multiple devices/emulators attached (#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.
1 parent e191e95 commit 19e6829

File tree

10 files changed

+398
-62
lines changed

10 files changed

+398
-62
lines changed

Gruntfile.js

-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ module.exports = function (grunt) {
133133
grunt.loadNpmTasks("grunt-contrib-watch");
134134
grunt.loadNpmTasks("grunt-shell");
135135
grunt.loadNpmTasks("grunt-ts");
136-
grunt.loadNpmTasks("grunt-tslint");
137136

138137
grunt.registerTask("set_package_version", function (version) {
139138
var buildVersion = version !== undefined ? version : buildNumber;

lib/commands/debug.ts

+71-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
export abstract class DebugPlatformCommand implements ICommand {
1+
import { CONNECTED_STATUS } from "../common/constants";
2+
import { isInteractive } from "../common/helpers";
3+
import { DebugCommandErrors } from "../constants";
4+
5+
export abstract class DebugPlatformCommand implements ICommand {
26
public allowedParameters: ICommandParameter[] = [];
37
public platform: string;
48

@@ -12,7 +16,8 @@
1216
protected $logger: ILogger,
1317
protected $errors: IErrors,
1418
private $debugLiveSyncService: IDebugLiveSyncService,
15-
private $config: IConfiguration) {
19+
private $config: IConfiguration,
20+
private $prompter: IPrompter) {
1621
this.$projectData.initializeProjectData();
1722
}
1823

@@ -29,11 +34,9 @@
2934

3035
this.$config.debugLivesync = true;
3136

32-
await this.$devicesService.detectCurrentlyAttachedDevices();
37+
const selectedDeviceForDebug = await this.getDeviceForDebug();
3338

34-
// Now let's take data for each device:
35-
const devices = this.$devicesService.getDeviceInstances();
36-
const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform)
39+
const deviceDescriptors: ILiveSyncDeviceInfo[] = [selectedDeviceForDebug]
3740
.map(d => {
3841
const info: ILiveSyncDeviceInfo = {
3942
identifier: d.deviceInfo.identifier,
@@ -70,6 +73,62 @@
7073
await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo);
7174
}
7275

76+
public async getDeviceForDebug(): Promise<Mobile.IDevice> {
77+
if (this.$options.forDevice && this.$options.emulator) {
78+
this.$errors.fail(DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR);
79+
}
80+
81+
await this.$devicesService.detectCurrentlyAttachedDevices();
82+
83+
if (this.$options.device) {
84+
const device = await this.$devicesService.getDevice(this.$options.device);
85+
return device;
86+
}
87+
88+
// Now let's take data for each device:
89+
const availableDevicesAndEmulators = this.$devicesService.getDeviceInstances()
90+
.filter(d => d.deviceInfo.status === CONNECTED_STATUS && (!this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()));
91+
92+
const selectedDevices = availableDevicesAndEmulators.filter(d => this.$options.emulator ? d.isEmulator : (this.$options.forDevice ? !d.isEmulator : true));
93+
94+
if (selectedDevices.length > 1) {
95+
if (isInteractive()) {
96+
const choices = selectedDevices.map(e => `${e.deviceInfo.identifier} - ${e.deviceInfo.displayName}`);
97+
98+
const selectedDeviceString = await this.$prompter.promptForChoice("Select device for debugging", choices);
99+
100+
const selectedDevice = _.find(selectedDevices, d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}` === selectedDeviceString);
101+
return selectedDevice;
102+
} else {
103+
const sortedInstances = _.sortBy(selectedDevices, e => e.deviceInfo.version);
104+
const emulators = sortedInstances.filter(e => e.isEmulator);
105+
const devices = sortedInstances.filter(d => !d.isEmulator);
106+
let selectedInstance: Mobile.IDevice;
107+
108+
if (this.$options.emulator || this.$options.forDevice) {
109+
// When --emulator or --forDevice is passed, the instances are already filtered
110+
// So we are sure we have exactly the type we need and we can safely return the last one (highest OS version).
111+
selectedInstance = _.last(sortedInstances);
112+
} else {
113+
if (emulators.length) {
114+
selectedInstance = _.last(emulators);
115+
} else {
116+
selectedInstance = _.last(devices);
117+
}
118+
}
119+
120+
this.$logger.warn(`Multiple devices/emulators found. Starting debugger on ${selectedInstance.deviceInfo.identifier}. ` +
121+
"If you want to debug on specific device/emulator, you can specify it with --device option.");
122+
123+
return selectedInstance;
124+
}
125+
} else if (selectedDevices.length === 1) {
126+
return _.head(selectedDevices);
127+
}
128+
129+
this.$errors.failWithoutHelp(DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS);
130+
}
131+
73132
public async canExecute(args: string[]): Promise<boolean> {
74133
if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) {
75134
this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`);
@@ -85,14 +144,6 @@
85144
emulator: this.$options.emulator,
86145
skipDeviceDetectionInterval: true
87146
});
88-
// Start emulator if --emulator is selected or no devices found.
89-
if (this.$options.emulator || this.$devicesService.deviceCount === 0) {
90-
return true;
91-
}
92-
93-
if (this.$devicesService.deviceCount > 1) {
94-
this.$errors.failWithoutHelp("Multiple devices found! To debug on specific device please select device with --device option.");
95-
}
96147

97148
return true;
98149
}
@@ -111,9 +162,10 @@ export class DebugIOSCommand extends DebugPlatformCommand {
111162
$projectData: IProjectData,
112163
$platformsData: IPlatformsData,
113164
$iosDeviceOperations: IIOSDeviceOperations,
114-
$debugLiveSyncService: IDebugLiveSyncService) {
165+
$debugLiveSyncService: IDebugLiveSyncService,
166+
$prompter: IPrompter) {
115167
super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger,
116-
$errors, $debugLiveSyncService, $config);
168+
$errors, $debugLiveSyncService, $config, $prompter);
117169
// 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.
118170
// 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.
119171
// That's why the `$ tns debug ios --justlaunch` command will not release the terminal.
@@ -146,9 +198,10 @@ export class DebugAndroidCommand extends DebugPlatformCommand {
146198
$options: IOptions,
147199
$projectData: IProjectData,
148200
$platformsData: IPlatformsData,
149-
$debugLiveSyncService: IDebugLiveSyncService) {
201+
$debugLiveSyncService: IDebugLiveSyncService,
202+
$prompter: IPrompter) {
150203
super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger,
151-
$errors, $debugLiveSyncService, $config);
204+
$errors, $debugLiveSyncService, $config, $prompter);
152205
}
153206

154207
public async canExecute(args: string[]): Promise<boolean> {

lib/constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,8 @@ export const CONNECTION_ERROR_EVENT_NAME = "connectionError";
8787
export const VERSION_STRING = "version";
8888
export const INSPECTOR_CACHE_DIRNAME = "ios-inspector";
8989
export const POST_INSTALL_COMMAND_NAME = "post-install-cli";
90+
91+
export class DebugCommandErrors {
92+
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.";
93+
public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options.";
94+
}

lib/definitions/debug.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ interface IDebugDataService {
8383
* @param {IOptions} options The options based on which debugData will be created
8484
* @returns {IDebugData} Data describing the required information for starting debug process.
8585
*/
86-
createDebugData(projectData: IProjectData, options: IOptions): IDebugData;
86+
createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData;
8787
}
8888

8989
/**

lib/services/debug-data-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export class DebugDataService implements IDebugDataService {
2-
public createDebugData(projectData: IProjectData, options: IOptions): IDebugData {
2+
public createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData {
33
return {
44
applicationIdentifier: projectData.projectId,
55
projectDir: projectData.projectDir,

lib/services/debug-service-base.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ export abstract class DebugServiceBase extends EventEmitter implements IPlatform
1414
protected getCanExecuteAction(deviceIdentifier: string): (device: Mobile.IDevice) => boolean {
1515
return (device: Mobile.IDevice): boolean => {
1616
if (deviceIdentifier) {
17-
return device.deviceInfo.identifier === deviceIdentifier
18-
|| device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier;
17+
let isSearchedDevice = device.deviceInfo.identifier === deviceIdentifier;
18+
if (!isSearchedDevice) {
19+
const deviceByDeviceOption = this.$devicesService.getDeviceByDeviceOption();
20+
isSearchedDevice = deviceByDeviceOption && device.deviceInfo.identifier === deviceByDeviceOption.deviceInfo.identifier;
21+
}
22+
23+
return isSearchedDevice;
1924
} else {
2025
return true;
2126
}

lib/services/livesync/debug-livesync-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS
4646
teamId: this.$options.teamId
4747
};
4848

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

5252
await this.$platformService.trackProjectType(this.$projectData);

0 commit comments

Comments
 (0)