From bbc21869dd212bcbe16a4138ea1fcfe5d44d4229 Mon Sep 17 00:00:00 2001 From: ivanbuhov Date: Mon, 28 Nov 2016 11:48:05 +0200 Subject: [PATCH] Enable debugging with livesync --- src/debug-adapter/webKitDebugAdapter.ts | 46 +++++++++++-------------- src/project/androidProject.ts | 34 +++++++++--------- src/project/iosProject.ts | 21 +++++------ src/project/project.ts | 5 +-- src/project/streamScanner.ts | 8 +++++ 5 files changed, 59 insertions(+), 55 deletions(-) diff --git a/src/debug-adapter/webKitDebugAdapter.ts b/src/debug-adapter/webKitDebugAdapter.ts index 06e6229..cf8c053 100644 --- a/src/debug-adapter/webKitDebugAdapter.ts +++ b/src/debug-adapter/webKitDebugAdapter.ts @@ -63,6 +63,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { this._scriptsById = new Map(); this._committedBreakpointsByUrl = new Map(); this._setBreakpointsRequestQ = Promise.resolve(); + this._lastOutputEvent = null; this.fireEvent({ seq: 0, type: 'event', event: 'clearTargetContext'}); } @@ -121,7 +122,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { Services.logger.log(`${args.request}(${JSON.stringify(args)})`); } - private processRequest(args: DebugProtocol.IRequestArgs) { + private processRequest(args: DebugProtocol.IRequestArgs): Promise { this.configureLoggingForRequest(args); // Initialize the request Services.appRoot = args.appRoot; @@ -147,30 +148,23 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { cliCommand.tnsProcess.on('close', (code, signal) => { Services.logger.error(`The tns command finished its execution with code ${code}.`, Tags.FrontendMessage); }); } + let promiseResolve = null; + let promise: Promise = new Promise((res, rej) => { promiseResolve = res; }); // Attach to the running application - let connectionEstablished = cliCommand.backendIsReadyForConnection.then((connectionToken: string | number) => { - if (this._request.isAndroid) { - Services.logger.log(`Attaching to application on port ${connectionToken}`); - let androidConnection = new AndroidConnection(); - this.setConnection(androidConnection); - let port: number = connectionToken; - return androidConnection.attach(port, 'localhost'); - } - if (this._request.isIos) { - Services.logger.log(`Attaching to application on socket path ${connectionToken}`); - let iosConnection = new IosConnection(); - this.setConnection(iosConnection); - let socketFilePath: string = connectionToken; - return iosConnection.attach(socketFilePath); - } + cliCommand.tnsOutputEventEmitter.on('readyForConnection', (connectionToken: string | number) => { + connectionToken = this._request.isAndroid ? this._request.androidProject.getDebugPortSync() : connectionToken; + Services.logger.log(`Attaching to application on ${connectionToken}`); + let connection: INSDebugConnection = this._request.isAndroid ? new AndroidConnection() : new IosConnection(); + this.setConnection(connection); + let attachPromise = this._request.isAndroid ? (connection).attach(connectionToken, 'localhost') : (connection).attach(connectionToken); + attachPromise.then(() => { + // Send InitializedEvent + this.fireEvent(new InitializedEvent()); + promiseResolve(); + }); }); - // Send InitializedEvent - return connectionEstablished.then(() => this.fireEvent(new InitializedEvent()), e => { - Services.logger.error(`Error: ${e}`, Tags.FrontendMessage); - this.clearEverything(); - return utils.errP(e); - }); + return promise; } private setConnection(connection: INSDebugConnection) : INSDebugConnection { @@ -201,10 +195,12 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter { } private terminateSession(): void { - //this.fireEvent(new TerminatedEvent()); - - Services.logger.log("Terminating debug session"); this.clearEverything(); + // In case of a sync request the session is not terminated when the backend is detached + if (!this._request.isSync) { + Services.logger.log("Terminating debug session"); + this.fireEvent(new TerminatedEvent()); + } } private clearEverything(): void { diff --git a/src/project/androidProject.ts b/src/project/androidProject.ts index c1660d8..5202c1d 100644 --- a/src/project/androidProject.ts +++ b/src/project/androidProject.ts @@ -1,5 +1,6 @@ import {ChildProcess} from 'child_process'; import * as stream from 'stream'; +import {EventEmitter} from 'events'; import {Project, DebugResult} from './project'; import * as scanner from './streamScanner'; import {Version} from '../common/version'; @@ -18,11 +19,13 @@ export class AndroidProject extends Project { } public attach(tnsArgs?: string[]): DebugResult { - return { tnsProcess: null, backendIsReadyForConnection: this.getDebugPort().debugPort }; + let tnsOutputEventEmitter = new EventEmitter(); + setTimeout(() => tnsOutputEventEmitter.emit('readyForConnection')); // emit readyForConnection on the next tick + return { tnsProcess: null, tnsOutputEventEmitter: tnsOutputEventEmitter }; } public debugWithSync(options: { stopOnEntry: boolean, syncAllFiles: boolean }, tnsArgs?: string[]): DebugResult { - let args: string[] = ["--no-rebuild"]; + let args: string[] = ["--no-rebuild", "--watch"]; if (options.syncAllFiles) { args.push("--syncAllFiles"); } args = args.concat(tnsArgs); @@ -35,26 +38,21 @@ export class AndroidProject extends Project { args = args.concat(tnsArgs); let debugProcess : ChildProcess = super.executeDebugCommand(args); - let backendIsReady = new scanner.StringMatchingScanner(debugProcess.stdout).nextMatch('# NativeScript Debugger started #').then(_ => { - // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket - return new Promise((resolve, reject) => setTimeout(() => { resolve(); }, 500)); - }).then(() => this.getDebugPort().debugPort); - - return { tnsProcess: debugProcess, backendIsReadyForConnection: backendIsReady }; + let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); + return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; } - private getDebugPort(): GetDebugPortResult { - let debugProcess : ChildProcess = super.executeDebugCommand(["--get-port"]); - let portNumberPromise: Promise = this.waitForPortNumber(debugProcess.stdout); - return { tnsProcess: debugProcess, debugPort: portNumberPromise }; + public getDebugPortSync(): number { + let output = this.cli.executeSync(["debug", "android", "--get--port"], this.appRoot); + let port = parseInt(output.match("(?:debug port: )([\\d]{5})")[1]); + return port; } - private waitForPortNumber(readableStream: stream.Readable): Promise { - let streamScanner = new scanner.StringMatchingScanner(readableStream); - return streamScanner.nextMatch(new RegExp("(?:debug port: )([\\d]{5})")).then((match: scanner.MatchFound) => { - let portNumber = parseInt(match.matches[1]); - streamScanner.stop(); - return portNumber; + private configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter): void { + new scanner.StringMatchingScanner(readableStream).onEveryMatch('# NativeScript Debugger started #', (match: scanner.MatchFound) => { + // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket + setTimeout(() => { eventEmitter.emit('readyForConnection'); }, 500); }); } } diff --git a/src/project/iosProject.ts b/src/project/iosProject.ts index 4de4849..db040ae 100644 --- a/src/project/iosProject.ts +++ b/src/project/iosProject.ts @@ -1,5 +1,6 @@ import {ChildProcess} from 'child_process'; import * as stream from 'stream'; +import {EventEmitter} from 'events'; import {Project, DebugResult} from './project'; import * as scanner from './streamScanner'; import {Version} from '../common/version'; @@ -24,12 +25,13 @@ export class IosProject extends Project { args = args.concat(tnsArgs); let debugProcess : ChildProcess = super.executeDebugCommand(args); - let socketPathPromise = this.waitForSocketPath(debugProcess.stdout); - return { tnsProcess: debugProcess, backendIsReadyForConnection: socketPathPromise }; + let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); + return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; } public debugWithSync(options: { stopOnEntry: boolean, syncAllFiles: boolean }, tnsArgs?: string[]): DebugResult { - let args: string[] = ["--no-rebuild"]; + let args: string[] = ["--no-rebuild", "--watch"]; if (options.syncAllFiles) { args.push("--syncAllFiles"); } args = args.concat(tnsArgs); @@ -42,18 +44,17 @@ export class IosProject extends Project { args = args.concat(tnsArgs); let debugProcess : ChildProcess = super.executeDebugCommand(args); - let socketPathPromise = this.waitForSocketPath(debugProcess.stdout); - return { tnsProcess: debugProcess, backendIsReadyForConnection: socketPathPromise }; + let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); + return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; } - private waitForSocketPath(readableStream: stream.Readable): Promise { + private configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter): void { let socketPathPrefix = 'socket-file-location: '; let streamScanner = new scanner.StringMatchingScanner(readableStream); - - return streamScanner.nextMatch(new RegExp(socketPathPrefix + '.*\.sock')).then((match: scanner.MatchFound) => { + streamScanner.onEveryMatch(new RegExp(socketPathPrefix + '.*\.sock'), (match: scanner.MatchFound) => { let socketPath = (match.matches[0]).substr(socketPathPrefix.length); - streamScanner.stop(); - return socketPath; + eventEmitter.emit('readyForConnection', socketPath); }); } diff --git a/src/project/project.ts b/src/project/project.ts index 426ff3c..47cef5e 100644 --- a/src/project/project.ts +++ b/src/project/project.ts @@ -1,8 +1,9 @@ import {ChildProcess} from 'child_process'; +import {EventEmitter} from 'events'; import {Version} from '../common/version'; import {NativeScriptCli} from './nativeScriptCli'; -export type DebugResult = { tnsProcess: ChildProcess, backendIsReadyForConnection: Promise }; +export type DebugResult = { tnsProcess: ChildProcess, tnsOutputEventEmitter: EventEmitter }; export abstract class Project { private _appRoot: string; @@ -13,7 +14,7 @@ export abstract class Project { this._cli = cli; } - public get rootPath(): string { return this._appRoot; } + public get appRoot(): string { return this._appRoot; } public get cli(): NativeScriptCli { return this._cli; } diff --git a/src/project/streamScanner.ts b/src/project/streamScanner.ts index 9fc0b1f..3cc167d 100644 --- a/src/project/streamScanner.ts +++ b/src/project/streamScanner.ts @@ -61,6 +61,14 @@ export class StringMatchingScanner extends StreamScanner { this._metas = []; } + public onEveryMatch(test: string | RegExp, handler: (result: MatchFound) => void) { + let handlerWrapper = (result: MatchFound) => { + handler(result); + this.nextMatch(test).then(handlerWrapper); + }; + this.nextMatch(test).then(handlerWrapper); + } + public nextMatch(test: string | RegExp): Promise { let meta: MatchMeta = { test: test,