Skip to content

Commit 68eab47

Browse files
authored
Merge pull request #199 from NativeScript/iiivanov/fix-livesync
Fixes LiveSync
2 parents 4bdd611 + b4616a1 commit 68eab47

File tree

6 files changed

+290
-262
lines changed

6 files changed

+290
-262
lines changed

src/debug-adapter/nativeScriptDebug.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import * as os from 'os';
22
import * as path from 'path';
33
import { chromeConnection, ChromeDebugSession } from 'vscode-chrome-debug-core';
4-
import { AndroidProject } from '../project/androidProject';
5-
import { IosProject } from '../project/iosProject';
6-
import { NativeScriptCli } from '../project/nativeScriptCli';
7-
import { nativeScriptDebugAdapterGenerator } from './nativeScriptDebugAdapter';
4+
import { NativeScriptDebugAdapter } from './nativeScriptDebugAdapter';
85
import { NativeScriptPathTransformer } from './nativeScriptPathTransformer';
96
import { NativeScriptTargetDiscovery } from './nativeScriptTargetDiscovery';
107

@@ -16,7 +13,7 @@ class NSAndroidConnection extends chromeConnection.ChromeConnection {
1613

1714
ChromeDebugSession.run(ChromeDebugSession.getSession(
1815
{
19-
adapter: nativeScriptDebugAdapterGenerator(IosProject, AndroidProject, NativeScriptCli),
16+
adapter: NativeScriptDebugAdapter,
2017
chromeConnection: NSAndroidConnection,
2118
extensionName: 'nativescript-extension',
2219
logFilePath: path.join(os.tmpdir(), 'nativescript-extension.txt'),
Lines changed: 103 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,209 +1,140 @@
1-
import { ChildProcess } from 'child_process';
2-
import * as fs from 'fs';
3-
import * as path from 'path';
4-
import { ChromeDebugAdapter, logger } from 'vscode-chrome-debug-core';
5-
import { Event, OutputEvent, TerminatedEvent } from 'vscode-debugadapter';
1+
import { ChromeDebugAdapter, IRestartRequestArgs } from 'vscode-chrome-debug-core';
2+
import { Event, TerminatedEvent } from 'vscode-debugadapter';
3+
import { DebugProtocol } from 'vscode-debugprotocol';
64
import * as extProtocol from '../common/extensionProtocol';
7-
import * as utils from '../common/utilities';
8-
import { AndroidProject } from '../project/androidProject';
9-
import { IosProject } from '../project/iosProject';
10-
import { NativeScriptCli } from '../project/nativeScriptCli';
11-
import { IDebugResult } from '../project/project';
12-
13-
export function nativeScriptDebugAdapterGenerator(iosProject: typeof IosProject,
14-
androidProject: typeof AndroidProject,
15-
nativeScriptCli: typeof NativeScriptCli) {
16-
return class NativeScriptDebugAdapter extends ChromeDebugAdapter {
17-
private _tnsProcess: ChildProcess;
18-
private _idCounter = 0;
19-
private _pendingRequests: object = {};
20-
21-
public attach(args: any): Promise<void> {
22-
return this.processRequestAndAttach(args);
23-
}
245

25-
public launch(args: any): Promise<void> {
26-
return this.processRequestAndAttach(args);
6+
const reconnectAfterLiveSyncTimeout = 10 * 1000;
7+
8+
export class NativeScriptDebugAdapter extends ChromeDebugAdapter {
9+
private _idCounter = 0;
10+
private _pendingRequests: object = {};
11+
private isLiveSync: boolean = false;
12+
private portWaitingResolve: any;
13+
private isDisconnecting: boolean = false;
14+
private isLiveSyncRestart: boolean = false;
15+
16+
public attach(args: any): Promise<void> {
17+
return this.processRequestAndAttach(args);
18+
}
19+
20+
public launch(args: any): Promise<void> {
21+
return this.processRequestAndAttach(args);
22+
}
23+
24+
public onPortReceived(port) {
25+
this.portWaitingResolve && this.portWaitingResolve(port);
26+
}
27+
28+
public onExtensionResponse(response) {
29+
this._pendingRequests[response.requestId](response.result);
30+
delete this._pendingRequests[response.requestId];
31+
}
32+
33+
public disconnect(args: any): void {
34+
this.isDisconnecting = true;
35+
if (!this.isLiveSyncRestart) {
36+
this.callRemoteMethod('buildService', 'disconnect');
2737
}
2838

29-
public disconnect(args: any): void {
30-
super.disconnect(args);
31-
32-
if (this._tnsProcess) {
33-
this._tnsProcess.stdout.removeAllListeners();
34-
this._tnsProcess.stderr.removeAllListeners();
35-
this._tnsProcess.removeAllListeners();
36-
utils.killProcess(this._tnsProcess);
37-
}
38-
}
39+
super.disconnect(args);
40+
}
3941

40-
public onExtensionResponse(response) {
41-
this._pendingRequests[response.requestId](response.result);
42-
delete this._pendingRequests[response.requestId];
43-
}
42+
protected async terminateSession(reason: string, disconnectArgs?: DebugProtocol.DisconnectArguments, restart?: IRestartRequestArgs): Promise<void> {
43+
let restartRequestArgs = restart;
44+
let timeoutId;
4445

45-
private async processRequestAndAttach(args: any) {
46-
const transformedArgs = await this.processRequest(args);
46+
if (!this.isDisconnecting && this.isLiveSync) {
47+
const portProm = new Promise<any>((res, rej) => {
48+
this.portWaitingResolve = res;
4749

48-
(this.pathTransformer as any).setTargetPlatform(args.platform);
49-
(ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000;
50+
timeoutId = setTimeout(() => {
51+
res();
52+
}, reconnectAfterLiveSyncTimeout);
53+
});
5054

51-
return super.attach(transformedArgs);
55+
restartRequestArgs = await portProm;
56+
this.isLiveSyncRestart = restartRequestArgs && !!restartRequestArgs.port;
57+
clearTimeout(timeoutId);
5258
}
5359

54-
private async processRequest(args: any): Promise<any> {
55-
args = this.translateArgs(args);
56-
57-
this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START));
58-
59-
const tnsPath = await this.callRemoteMethod<string>('workspaceConfigService', 'tnsPath');
60-
const cli = new nativeScriptCli(tnsPath, logger);
61-
62-
const project = args.platform === 'ios' ?
63-
new iosProject(args.appRoot, cli) :
64-
new androidProject(args.appRoot, cli);
65-
66-
this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform);
67-
68-
// Run CLI Command
69-
const version = project.cli.executeGetVersion();
60+
await super.terminateSession(reason, disconnectArgs, restartRequestArgs);
61+
}
7062

71-
this.log(`[NSDebugAdapter] Using tns CLI v${version} on path '${project.cli.path}'\n`);
72-
this.log('[NSDebugAdapter] Running tns command...\n');
73-
let cliCommand: IDebugResult;
63+
protected hookConnectionEvents(): void {
64+
super.hookConnectionEvents();
7465

75-
if (args.request === 'launch') {
76-
let tnsArgs = args.tnsArgs;
66+
this.chrome.Log.onEntryAdded((params) => this.onEntryAdded(params));
67+
}
7768

78-
// For iOS the TeamID is required if there's more than one.
79-
// Therefore if not set, show selection to the user.
80-
if (args.platform && args.platform.toLowerCase() === 'ios') {
81-
const teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs);
69+
protected onEntryAdded(params: any): void {
70+
if (params && params.entry && params.entry.level) {
71+
const message = params.entry;
8272

83-
if (!teamId) {
84-
const selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam');
73+
message.type = message.level;
8574

86-
if (selectedTeam) {
87-
// add the selected by the user Team Id
88-
tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]);
89-
this.log(`[NSDebugAdapter] Using iOS Team ID '${selectedTeam.id}', you can change this in the workspace settings.\n`);
90-
}
91-
}
92-
}
75+
const that = this as any;
76+
const script = that.getScriptByUrl && that.getScriptByUrl(params.entry.url);
9377

94-
cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs);
95-
} else if (args.request === 'attach') {
96-
cliCommand = project.attach(args.tnsArgs);
78+
if (script) {
79+
message.stack = {
80+
callFrames: [
81+
{
82+
columnNumber: 0,
83+
lineNumber: params.entry.lineNumber,
84+
scriptId: script.scriptId,
85+
url: params.entry.url,
86+
},
87+
],
88+
};
9789
}
9890

99-
if (cliCommand.tnsProcess) {
100-
this._tnsProcess = cliCommand.tnsProcess;
101-
cliCommand.tnsProcess.stdout.on('data', (data) => { this.log(data.toString()); });
102-
cliCommand.tnsProcess.stderr.on('data', (data) => { this.log(data.toString()); });
103-
104-
cliCommand.tnsProcess.on('close', (code, signal) => {
105-
this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`);
106-
107-
// Sometimes we execute "tns debug android --start" and the process finishes
108-
// which is totally fine. If there's an error we need to Terminate the session.
109-
if (code !== 0) {
110-
this.log(`The tns command finished its execution with code ${code}`);
111-
this._session.sendEvent(new TerminatedEvent());
112-
}
113-
});
114-
}
115-
116-
this.log('[NSDebugAdapter] Watching the tns CLI output to receive a connection token\n');
117-
118-
return new Promise<string | number> ((res, rej) => {
119-
cliCommand.tnsOutputEventEmitter.on('readyForConnection', (connectionToken: string | number) => {
120-
this.log(`[NSDebugAdapter] Ready to attach to application on ${connectionToken}\n`);
121-
args.port = connectionToken;
122-
123-
res(args);
124-
});
91+
this.onMessageAdded({
92+
message,
12593
});
12694
}
95+
}
12796

128-
private translateArgs(args): any {
129-
if (args.diagnosticLogging) {
130-
args.trace = args.diagnosticLogging;
131-
}
132-
133-
if (args.appRoot) {
134-
args.webRoot = args.appRoot;
135-
}
97+
private async processRequestAndAttach(args: any) {
98+
const transformedArgs = this.translateArgs(args);
13699

137-
return args;
100+
if (args.__restart) {
101+
transformedArgs.port = args.__restart.port;
102+
} else {
103+
this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START));
104+
transformedArgs.port = await this.callRemoteMethod('buildService', 'processRequest', transformedArgs);
138105
}
139106

140-
private log(text: string): void {
141-
this._session.sendEvent(new OutputEvent(text));
107+
if (!transformedArgs.port) {
108+
this._session.sendEvent(new TerminatedEvent());
142109
}
143110

144-
private getTeamId(appRoot: string, tnsArgs?: string[]): string {
145-
// try to get the TeamId from the TnsArgs
146-
if (tnsArgs) {
147-
const teamIdArgIndex = tnsArgs.indexOf('--teamId');
111+
(this.pathTransformer as any).setTargetPlatform(args.platform);
112+
(ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000;
148113

149-
if (teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) {
150-
return tnsArgs[ teamIdArgIndex + 1 ];
151-
}
152-
}
153-
154-
// try to get the TeamId from the buildxcconfig or teamid file
155-
const teamIdFromConfig = this.readTeamId(appRoot);
114+
this.isLiveSync = args.watch;
156115

157-
if (teamIdFromConfig) {
158-
return teamIdFromConfig;
159-
}
116+
return super.attach(transformedArgs);
117+
}
160118

161-
// we should get the Teams from the machine and ask the user if they are more than 1
162-
return null;
119+
private translateArgs(args): any {
120+
if (args.diagnosticLogging) {
121+
args.trace = args.diagnosticLogging;
163122
}
164123

165-
private readXCConfig(appRoot: string, flag: string): string {
166-
const xcconfigFile = path.join(appRoot, 'App_Resources/iOS/build.xcconfig');
167-
168-
if (fs.existsSync(xcconfigFile)) {
169-
const text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'});
170-
let teamId: string;
171-
172-
text.split(/\r?\n/).forEach((line) => {
173-
line = line.replace(/\/(\/)[^\n]*$/, '');
174-
if (line.indexOf(flag) >= 0) {
175-
teamId = line.split('=')[1].trim();
176-
if (teamId[teamId.length - 1] === ';') {
177-
teamId = teamId.slice(0, -1);
178-
}
179-
}
180-
});
181-
if (teamId) {
182-
return teamId;
183-
}
184-
}
185-
186-
const fileName = path.join(appRoot, 'teamid');
187-
188-
if (fs.existsSync(fileName)) {
189-
return fs.readFileSync(fileName, { encoding: 'utf8' });
190-
}
191-
192-
return null;
124+
if (args.appRoot) {
125+
args.webRoot = args.appRoot;
193126
}
194127

195-
private readTeamId(appRoot): string {
196-
return this.readXCConfig(appRoot, 'DEVELOPMENT_TEAM');
197-
}
128+
return args;
129+
}
198130

199-
private callRemoteMethod<T>(service: string, method: string, ...args: any[]): Promise<T> {
200-
const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args };
131+
private callRemoteMethod<T>(service: string, method: string, ...args: any[]): Promise<T> {
132+
const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args };
201133

202-
return new Promise<T>((res, rej) => {
203-
this._pendingRequests[request.id] = res;
134+
return new Promise<T>((res, rej) => {
135+
this._pendingRequests[request.id] = res;
204136

205-
this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request));
206-
});
207-
}
208-
};
137+
this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request));
138+
});
139+
}
209140
}

src/main.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ export function activate(context: vscode.ExtensionContext) {
8080
});
8181

8282
const disposable = {
83-
dispose: () => utils.killProcess(tnsProcess),
83+
dispose: () => {
84+
services.buildService.disconnect();
85+
utils.killProcess(tnsProcess);
86+
},
8487
};
8588

8689
context.subscriptions.push(disposable);
@@ -97,6 +100,7 @@ export function activate(context: vscode.ExtensionContext) {
97100

98101
context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((event) => {
99102
if (event.event === extProtocol.BEFORE_DEBUG_START) {
103+
channel.show();
100104
beforeBuildDisposables.forEach((disposable) => disposable.dispose());
101105
}
102106

0 commit comments

Comments
 (0)