Skip to content

Commit 1b62fc6

Browse files
committed
Fixes LiveSync
Fixes LiveSync by restarting session. Each time we got terminate sessions event we wait 10 sec to get new port. If we get new port for debugging we restart the session. Move cli process to main process because each restart the session is created(but the tns process is not killed) and we don't have reference to the long running tns process. Also conceptual it's better to have the cli output in the extension output but not in the debug console. Fixes Debug Console messages and show exactly in which file is the console log.
1 parent 4bdd611 commit 1b62fc6

File tree

4 files changed

+242
-137
lines changed

4 files changed

+242
-137
lines changed

src/debug-adapter/nativeScriptDebugAdapter.ts

Lines changed: 70 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
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, logger } 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';
85
import { AndroidProject } from '../project/androidProject';
96
import { IosProject } from '../project/iosProject';
107
import { NativeScriptCli } from '../project/nativeScriptCli';
11-
import { IDebugResult } from '../project/project';
8+
9+
const reconnectAfterLiveSyncTimeout = 10 * 1000;
1210

1311
export function nativeScriptDebugAdapterGenerator(iosProject: typeof IosProject,
1412
androidProject: typeof AndroidProject,
1513
nativeScriptCli: typeof NativeScriptCli) {
1614
return class NativeScriptDebugAdapter extends ChromeDebugAdapter {
17-
private _tnsProcess: ChildProcess;
1815
private _idCounter = 0;
1916
private _pendingRequests: object = {};
17+
private isLiveSync: boolean = false;
18+
private portWaitingResolve: any;
2019

2120
public attach(args: any): Promise<void> {
2221
return this.processRequestAndAttach(args);
@@ -29,100 +28,94 @@ export function nativeScriptDebugAdapterGenerator(iosProject: typeof IosProject,
2928
public disconnect(args: any): void {
3029
super.disconnect(args);
3130

32-
if (this._tnsProcess) {
33-
this._tnsProcess.stdout.removeAllListeners();
34-
this._tnsProcess.stderr.removeAllListeners();
35-
this._tnsProcess.removeAllListeners();
36-
utils.killProcess(this._tnsProcess);
31+
if (!args.restart) {
32+
this.callRemoteMethod('buildService', 'disconnect');
3733
}
3834
}
3935

36+
public onPortReceived(port) {
37+
this.portWaitingResolve && this.portWaitingResolve(port);
38+
}
39+
4040
public onExtensionResponse(response) {
4141
this._pendingRequests[response.requestId](response.result);
4242
delete this._pendingRequests[response.requestId];
4343
}
4444

45-
private async processRequestAndAttach(args: any) {
46-
const transformedArgs = await this.processRequest(args);
47-
48-
(this.pathTransformer as any).setTargetPlatform(args.platform);
49-
(ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000;
50-
51-
return super.attach(transformedArgs);
52-
}
53-
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);
45+
protected async terminateSession(reason: string, disconnectArgs?: DebugProtocol.DisconnectArguments, restart?: IRestartRequestArgs): Promise<void> {
46+
let restartRequestArgs;
47+
let timeoutId;
6148

62-
const project = args.platform === 'ios' ?
63-
new iosProject(args.appRoot, cli) :
64-
new androidProject(args.appRoot, cli);
49+
if (this.isLiveSync) {
50+
const portProm = new Promise<any>((res, rej) => {
51+
this. portWaitingResolve = res;
6552

66-
this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform);
67-
68-
// Run CLI Command
69-
const version = project.cli.executeGetVersion();
53+
timeoutId = setTimeout(() => {
54+
res();
55+
}, reconnectAfterLiveSyncTimeout);
56+
});
7057

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;
58+
restartRequestArgs = await portProm;
59+
clearTimeout(timeoutId);
60+
}
7461

75-
if (args.request === 'launch') {
76-
let tnsArgs = args.tnsArgs;
62+
await super.terminateSession(reason, disconnectArgs, restartRequestArgs);
63+
}
7764

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);
65+
protected hookConnectionEvents(): void {
66+
super.hookConnectionEvents();
8267

83-
if (!teamId) {
84-
const selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam');
68+
this.chrome.Log.onEntryAdded((params) => this.onEntryAdded(params));
69+
}
8570

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-
}
71+
protected onEntryAdded(params: any): void {
72+
if (params && params.entry && params.entry.level) {
73+
const message = params.entry;
74+
75+
message.type = message.level;
76+
77+
const that = this as any;
78+
const script = that.getScriptByUrl && that.getScriptByUrl(params.entry.url);
79+
80+
if (script) {
81+
message.stack = {
82+
callFrames: [
83+
{
84+
columnNumber: 0,
85+
lineNumber: params.entry.lineNumber,
86+
scriptId: script.scriptId,
87+
url: params.entry.url,
88+
},
89+
],
90+
};
9291
}
9392

94-
cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs);
95-
} else if (args.request === 'attach') {
96-
cliCommand = project.attach(args.tnsArgs);
93+
this.onMessageAdded({
94+
message,
95+
});
9796
}
97+
}
9898

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()); });
99+
private async processRequestAndAttach(args: any) {
100+
const transformedArgs = this.translateArgs(args);
103101

104-
cliCommand.tnsProcess.on('close', (code, signal) => {
105-
this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`);
102+
if (args.__restart) {
103+
transformedArgs.port = args.__restart.port;
104+
} else {
105+
this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START));
106+
transformedArgs.port = await this.callRemoteMethod('buildService', 'processRequest', transformedArgs);
107+
}
106108

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-
});
109+
if (!transformedArgs.port) {
110+
this._session.sendEvent(new TerminatedEvent());
114111
}
115112

116-
this.log('[NSDebugAdapter] Watching the tns CLI output to receive a connection token\n');
113+
(this.pathTransformer as any).setTargetPlatform(args.platform);
114+
(ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000;
117115

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;
116+
this.isLiveSync = args.watch;
122117

123-
res(args);
124-
});
125-
});
118+
return super.attach(transformedArgs);
126119
}
127120

128121
private translateArgs(args): any {
@@ -137,65 +130,6 @@ export function nativeScriptDebugAdapterGenerator(iosProject: typeof IosProject,
137130
return args;
138131
}
139132

140-
private log(text: string): void {
141-
this._session.sendEvent(new OutputEvent(text));
142-
}
143-
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');
148-
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);
156-
157-
if (teamIdFromConfig) {
158-
return teamIdFromConfig;
159-
}
160-
161-
// we should get the Teams from the machine and ask the user if they are more than 1
162-
return null;
163-
}
164-
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;
193-
}
194-
195-
private readTeamId(appRoot): string {
196-
return this.readXCConfig(appRoot, 'DEVELOPMENT_TEAM');
197-
}
198-
199133
private callRemoteMethod<T>(service: string, method: string, ...args: any[]): Promise<T> {
200134
const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args };
201135

src/main.ts

Lines changed: 4 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);

0 commit comments

Comments
 (0)