Skip to content

fix: enable debug-brk on real iOS devices #4296

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 2 commits into from
Jan 22, 2019
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
66 changes: 5 additions & 61 deletions lib/commands/debug.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { CONNECTED_STATUS } from "../common/constants";
import { isInteractive } from "../common/helpers";
import { cache } from "../common/decorators";
import { DebugCommandErrors } from "../constants";
import { ValidatePlatformCommandBase } from "./command-base";
import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper";
import { performanceLog } from "../common/decorators";

export class DebugPlatformCommand extends ValidatePlatformCommandBase implements ICommand {
public allowedParameters: ICommandParameter[] = [];
Expand All @@ -21,7 +17,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements
protected $errors: IErrors,
private $debugDataService: IDebugDataService,
private $liveSyncService: IDebugLiveSyncService,
private $prompter: IPrompter,
private $liveSyncCommandHelper: ILiveSyncCommandHelper,
private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) {
super($options, $platformsData, $platformService, $projectData);
Expand All @@ -37,7 +32,11 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements

const debugOptions = <IDebugOptions>_.cloneDeep(this.$options.argv);

const selectedDeviceForDebug = await this.getDeviceForDebug();
const selectedDeviceForDebug = await this.$devicesService.pickSingleDevice({
onlyEmulators: this.$options.emulator,
onlyDevices: this.$options.forDevice,
deviceId: this.$options.device
});

const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier });

Expand All @@ -56,61 +55,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements
});
}

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

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<ICanExecuteCommandOutput> {
this.$androidBundleValidatorHelper.validateNoAab();

Expand Down
19 changes: 0 additions & 19 deletions lib/commands/test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
import * as helpers from "../common/helpers";

function RunTestCommandFactory(platform: string) {
return function RunTestCommand(
$options: IOptions,
$testExecutionService: ITestExecutionService,
$projectData: IProjectData,
$analyticsService: IAnalyticsService,
$platformEnvironmentRequirements: IPlatformEnvironmentRequirements) {
$projectData.initializeProjectData();
$analyticsService.setShouldDispose($options.justlaunch || !$options.watch);
const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: $options.release });
this.execute = (args: string[]): Promise<void> => $testExecutionService.startTestRunner(platform, $projectData, projectFilesConfig);
this.canExecute = (args: string[]): Promise<boolean> => canExecute({ $platformEnvironmentRequirements, $projectData, $options, platform });
this.allowedParameters = [];
};
}

$injector.registerCommand("dev-test|android", RunTestCommandFactory('android'));
$injector.registerCommand("dev-test|ios", RunTestCommandFactory('iOS'));

function RunKarmaTestCommandFactory(platform: string) {
return function RunKarmaTestCommand($options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData, $analyticsService: IAnalyticsService, $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) {
$projectData.initializeProjectData();
Expand Down
61 changes: 34 additions & 27 deletions lib/common/definitions/mobile.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,8 @@ declare module Mobile {
}

interface IiOSDevice extends IDevice {
getLiveSyncSocket(appId: string): Promise<any>;
destroyLiveSyncSocket(appId: string): void;

getDebugSocket(appId: string): Promise<any>;
getDebugSocket(appId: string, projectName: string): Promise<any>;
destroyDebugSocket(appId: string): void;

openDeviceLogStream(options?: IiOSLogStreamOptions): Promise<void>;
destroyAllSockets(): void;
}
Expand Down Expand Up @@ -221,12 +217,6 @@ declare module Mobile {
* @param {string} projectName The project name of the currently running application for which we need the logs.
*/
setProjectNameForDevice(deviceIdentifier: string, projectName: string): void;

/**
* Disables logs on the specified device and does not print any logs on the console.
* @param {string} deviceIdentifier The unique identifier of the device.
*/
muteLogsForDevice(deviceIdentifier: string): void;
}

/**
Expand All @@ -248,10 +238,6 @@ declare module Mobile {
*/
projectName?: string;

/**
* Specifies if the logs will be printed on the console.
*/
muteLogs?: boolean;
}

/**
Expand Down Expand Up @@ -311,19 +297,27 @@ declare module Mobile {
justLaunch?: boolean;
}

interface IStartApplicationData extends IApplicationData {
waitForDebugger?: boolean;
}

interface IInstallAppData extends IApplicationData {
packagePath: string;
}

interface IRunningAppInfo {
pid: string;
}

interface IDeviceApplicationManager extends NodeJS.EventEmitter {
getInstalledApplications(): Promise<string[]>;
isApplicationInstalled(appIdentifier: string): Promise<boolean>;
installApplication(packageFilePath: string, appIdentifier?: string): Promise<void>;
uninstallApplication(appIdentifier: string): Promise<void>;
reinstallApplication(appIdentifier: string, packageFilePath: string): Promise<void>;
startApplication(appData: IApplicationData): Promise<void>;
startApplication(appData: IStartApplicationData): Promise<void>;
stopApplication(appData: IApplicationData): Promise<void>;
restartApplication(appData: IApplicationData): Promise<void>;
restartApplication(appData: IStartApplicationData): Promise<void>;
checkForApplicationUpdates(): Promise<void>;
isLiveSyncSupported(appIdentifier: string): Promise<boolean>;
getApplicationInfo(applicationIdentifier: string): Promise<Mobile.IApplicationInfo>;
Expand Down Expand Up @@ -550,6 +544,27 @@ declare module Mobile {
* @returns {Promise<string[]>} - Returns array of errors.
*/
startEmulator(options?: IStartEmulatorOptions): Promise<string[]>;

/**
* Returns a single device based on the specified options. If more than one devices are matching,
* prompts the user for a manual choice or returns the first one for non interactive terminals.
*/
pickSingleDevice(options: IPickSingleDeviceOptions): Promise<Mobile.IDevice>
}

interface IPickSingleDeviceOptions {
/**
* Pick from the connected emulators only
*/
onlyEmulators: boolean;
/**
* Pick from the connected real devices only
*/
onlyDevices: boolean;
/**
* Pick a specific device
*/
deviceId: string;
}

interface IListEmulatorsOptions {
Expand Down Expand Up @@ -941,14 +956,6 @@ declare module Mobile {
* @returns {net.Socket} Returns instance of net.Socket when connection is successful, otherwise undefined is returned.
*/
connectToPort(connectToPortData: IConnectToPortData): Promise<any>;

/**
* Runs an application on emulator
* @param app The path to executable .app
* @param emulatorOptions Emulator options that can be passed
* @returns {Promise<any>} Returns the appId with the process of the running application on the simulator. For example: org.nativescript.myapp 55434
*/
runApplicationOnEmulator(app: string, emulatorOptions?: IRunApplicationOnEmulatorOptions): Promise<any>;
}

interface IEmulatorSettingsService {
Expand Down Expand Up @@ -1251,9 +1258,9 @@ interface IIOSDeviceOperations extends IDisposable, NodeJS.EventEmitter {

deleteFiles(deleteArray: IOSDeviceLib.IDeleteFileData[], errorHandler?: DeviceOperationErrorHandler): Promise<IOSDeviceResponse>;

start(startArray: IOSDeviceLib.IDdiApplicationData[], errorHandler?: DeviceOperationErrorHandler): Promise<IOSDeviceResponse>;
start(startArray: IOSDeviceLib.IIOSApplicationData[], errorHandler?: DeviceOperationErrorHandler): Promise<IOSDeviceResponse>;

stop(stopArray: IOSDeviceLib.IDdiApplicationData[], errorHandler?: DeviceOperationErrorHandler): Promise<IOSDeviceResponse>;
stop(stopArray: IOSDeviceLib.IIOSApplicationData[], errorHandler?: DeviceOperationErrorHandler): Promise<IOSDeviceResponse>;

postNotification(postNotificationArray: IOSDeviceLib.IPostNotificationData[], errorHandler?: DeviceOperationErrorHandler): Promise<IOSDeviceResponse>;

Expand Down
10 changes: 9 additions & 1 deletion lib/common/mobile/android/android-application-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ export class AndroidApplicationManager extends ApplicationManagerBase {
return this.adb.executeShellCommand(["pm", "uninstall", `${appIdentifier}`], { treatErrorsAsWarnings: true });
}

public async startApplication(appData: Mobile.IApplicationData): Promise<void> {
public async startApplication(appData: Mobile.IStartApplicationData): Promise<void> {
if (appData.waitForDebugger) {
await this.adb.executeShellCommand([`cat /dev/null > ${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugbreak`]);
}

// If the app is debuggable, the Runtime will update the file when its ready for debugging
// and we will be able to take decisions and synchronize the debug experience based on the content
await this.adb.executeShellCommand([`cat /dev/null > ${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugger-started`]);

/*
Example "pm dump <app_identifier> | grep -A 1 MAIN" output"
android.intent.action.MAIN:
Expand Down
4 changes: 0 additions & 4 deletions lib/common/mobile/device-log-provider-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ export abstract class DeviceLogProviderBase extends EventEmitter implements Mobi
this.setLogLevel(logLevel, deviceIdentifier);
}

public muteLogsForDevice(deviceIdentifier: string): void {
this.setDeviceLogOptionsProperty(deviceIdentifier, (deviceLogOptions: Mobile.IDeviceLogOptions) => deviceLogOptions.muteLogs, true);
}

protected getApplicationPidForDevice(deviceIdentifier: string): string {
return this.devicesLogOptions[deviceIdentifier] && this.devicesLogOptions[deviceIdentifier].applicationPid;
}
Expand Down
8 changes: 3 additions & 5 deletions lib/common/mobile/device-log-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class DeviceLogProvider extends DeviceLogProviderBase {
const loggingOptions = this.getDeviceLogOptionsForDevice(deviceIdentifier);
const data = this.$logFilter.filterData(platform, lineText, loggingOptions);
if (data) {
this.logDataCore(data, loggingOptions);
this.logDataCore(data);
this.emit(DEVICE_LOG_EVENT_NAME, lineText, deviceIdentifier, platform);
}
}
Expand All @@ -20,10 +20,8 @@ export class DeviceLogProvider extends DeviceLogProviderBase {
this.$logFilter.loggingLevel = logLevel.toUpperCase();
}

private logDataCore(data: string, loggingOptions: Mobile.IDeviceLogOptions): void {
if (!loggingOptions || (loggingOptions && !loggingOptions.muteLogs)) {
this.$logger.write(data);
}
private logDataCore(data: string): void {
this.$logger.write(data);
}
}
$injector.register("deviceLogProvider", DeviceLogProvider);
10 changes: 5 additions & 5 deletions lib/common/mobile/ios/device/ios-application-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class IOSApplicationManager extends ApplicationManagerBase {
this.$logger.trace("Application %s has been uninstalled successfully.", appIdentifier);
}

public async startApplication(appData: Mobile.IApplicationData): Promise<void> {
public async startApplication(appData: Mobile.IStartApplicationData): Promise<void> {
if (!await this.isApplicationInstalled(appData.appId)) {
this.$errors.failWithoutHelp("Invalid application id: %s. All available application ids are: %s%s ", appData.appId, EOL, this.applicationsLiveSyncInfos.join(EOL));
}
Expand All @@ -89,7 +89,6 @@ export class IOSApplicationManager extends ApplicationManagerBase {
const { appId } = appData;

this.device.destroyDebugSocket(appId);
this.device.destroyLiveSyncSocket(appId);

const action = () => this.$iosDeviceOperations.stop([{ deviceId: this.device.deviceInfo.identifier, ddi: this.$options.ddi, appId }]);

Expand All @@ -101,7 +100,7 @@ export class IOSApplicationManager extends ApplicationManagerBase {
}
}

public async restartApplication(appData: Mobile.IApplicationData): Promise<void> {
public async restartApplication(appData: Mobile.IStartApplicationData): Promise<void> {
try {
await this.setDeviceLogData(appData);
await this.stopApplication(appData);
Expand All @@ -119,8 +118,9 @@ export class IOSApplicationManager extends ApplicationManagerBase {
}
}

private async runApplicationCore(appData: Mobile.IApplicationData): Promise<void> {
await this.$iosDeviceOperations.start([{ deviceId: this.device.deviceInfo.identifier, appId: appData.appId, ddi: this.$options.ddi }]);
private async runApplicationCore(appData: Mobile.IStartApplicationData): Promise<void> {
const waitForDebugger = appData.waitForDebugger && appData.waitForDebugger.toString();
await this.$iosDeviceOperations.start([{ deviceId: this.device.deviceInfo.identifier, appId: appData.appId, ddi: this.$options.ddi, waitForDebugger }]);
}

@cache()
Expand Down
7 changes: 4 additions & 3 deletions lib/common/mobile/ios/device/ios-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export class IOSDevice extends IOSDeviceBase {
protected $errors: IErrors,
private $injector: IInjector,
protected $iOSDebuggerPortService: IIOSDebuggerPortService,
protected $deviceLogProvider: Mobile.IDeviceLogProvider,
protected $lockService: ILockService,
private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor,
protected $processService: IProcessService,
private $deviceLogProvider: Mobile.IDeviceLogProvider,
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $iOSDeviceProductNameMapper: Mobile.IiOSDeviceProductNameMapper,
private $iosDeviceOperations: IIOSDeviceOperations,
Expand Down Expand Up @@ -60,9 +60,10 @@ export class IOSDevice extends IOSDeviceBase {
}
}

protected async getSocketCore(appId: string): Promise<net.Socket> {
protected async getDebugSocketCore(appId: string, projectName: string): Promise<net.Socket> {
await super.attachToDebuggerFoundEvent(projectName);
await this.$iOSSocketRequestExecutor.executeAttachRequest(this, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId);
const port = await this.getDebuggerPort(appId);
const port = await super.getDebuggerPort(appId);
const deviceId = this.deviceInfo.identifier;

const socket = await helpers.connectEventuallyUntilTimeout(
Expand Down
Loading