Skip to content

Enable debugging with livesync #96

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
Nov 28, 2016
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
46 changes: 21 additions & 25 deletions src/debug-adapter/webKitDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class WebKitDebugAdapter implements DebugProtocol.IDebugAdapter {
this._scriptsById = new Map<WebKitProtocol.Debugger.ScriptId, WebKitProtocol.Debugger.Script>();
this._committedBreakpointsByUrl = new Map<string, WebKitProtocol.Debugger.BreakpointId[]>();
this._setBreakpointsRequestQ = Promise.resolve<void>();
this._lastOutputEvent = null;
this.fireEvent({ seq: 0, type: 'event', event: 'clearTargetContext'});
}

Expand Down Expand Up @@ -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<void> {
this.configureLoggingForRequest(args);
// Initialize the request
Services.appRoot = args.appRoot;
Expand All @@ -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<void> = new Promise<void>((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 = <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 = <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 ? (<AndroidConnection>connection).attach(<number>connectionToken, 'localhost') : (<IosConnection>connection).attach(<string>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 {
Expand Down Expand Up @@ -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 {
Expand Down
34 changes: 16 additions & 18 deletions src/project/androidProject.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);

Expand All @@ -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<number> = 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<number> {
let streamScanner = new scanner.StringMatchingScanner(readableStream);
return streamScanner.nextMatch(new RegExp("(?:debug port: )([\\d]{5})")).then((match: scanner.MatchFound) => {
let portNumber = parseInt(<string>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);
});
}
}
21 changes: 11 additions & 10 deletions src/project/iosProject.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);

Expand All @@ -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<string> {
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 = (<string>match.matches[0]).substr(socketPathPrefix.length);
streamScanner.stop();
return socketPath;
eventEmitter.emit('readyForConnection', socketPath);
});
}

Expand Down
5 changes: 3 additions & 2 deletions src/project/project.ts
Original file line number Diff line number Diff line change
@@ -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<any> };
export type DebugResult = { tnsProcess: ChildProcess, tnsOutputEventEmitter: EventEmitter };

export abstract class Project {
private _appRoot: string;
Expand All @@ -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; }

Expand Down
8 changes: 8 additions & 0 deletions src/project/streamScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MatchFound> {
let meta: MatchMeta = {
test: test,
Expand Down