Skip to content

Debug on emulator by default when multiple devices/emulators attached #2957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be we can add all new errors here? For consistency, not a merge stopper, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only error remaining in debug command is the:
Applications for platform ${this.platform} can not be built on this OS - it is used in several places other than debug command, so it will be handled in a separate PR.
But I totally agree that we should have the errors in such constants.

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