microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.4.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

280lines · modeblame

e45838cbVladimir Kotikov9 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as Q from "q";
5import * as path from "path";
6import * as fs from "fs";
7import stripJsonComments = require("strip-json-comments");
8
b8999098Dmitry Zinovyev9 years ago9import { Telemetry } from "../common/telemetry";
10import { TelemetryHelper } from "../common/telemetryHelper";
11import { RemoteExtension } from "../common/remoteExtension";
12import { IOSPlatform } from "./ios/iOSPlatform";
13import { PlatformResolver } from "./platformResolver";
14import { IRunOptions } from "../common/launchArgs";
15import { TargetPlatformHelper } from "../common/targetPlatformHelper";
16import { ExtensionTelemetryReporter, ReassignableTelemetryReporter } from "../common/telemetryReporters";
17import { NodeDebugAdapterLogger } from "../common/log/loggers";
18import { Log } from "../common/log/log";
19import { LogLevel } from "../common/log/logHelper";
20import { GeneralMobilePlatform } from "../common/generalMobilePlatform";
e45838cbVladimir Kotikov9 years ago21
22import { MultipleLifetimesAppWorker } from "./appWorker";
23
b8999098Dmitry Zinovyev9 years ago24export function makeSession(
25debugSessionClass: typeof ChromeDebuggerCorePackage.ChromeDebugSession,
26debugSessionOpts: ChromeDebuggerCorePackage.IChromeDebugSessionOpts,
27debugAdapterPackage: typeof VSCodeDebugAdapterPackage,
28telemetryReporter: ReassignableTelemetryReporter,
29appName: string, version: string): typeof ChromeDebuggerCorePackage.ChromeDebugSession {
e45838cbVladimir Kotikov9 years ago30
31return class extends debugSessionClass {
32
33private projectRootPath: string;
34private remoteExtension: RemoteExtension;
35private mobilePlatformOptions: IRunOptions;
36private appWorker: MultipleLifetimesAppWorker = null;
37
38constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
39super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
40}
41
42// Override ChromeDebugSession's sendEvent to control what we will send to client
43public sendEvent(event: VSCodeDebugAdapterPackage.Event): void {
44// Do not send "terminated" events signaling about session's restart to client as it would cause it
45// to restart adapter's process, while we want to stay alive and don't want to interrupt connection
6c75098eVladimir9 years ago46// to packager.
b8999098Dmitry Zinovyev9 years ago47
720e992dArtem Egorov8 years ago48if (event.event === "terminated" && event.body && event.body.restart) {
b8999098Dmitry Zinovyev9 years ago49
50// Worker has been reloaded and switched to "continue" state
51// So we have to send "continued" event to client instead of "terminated"
52// Otherwise client might mistakenly show "stopped" state
53let continuedEvent: VSCodeDebugAdapterPackage.ContinuedEvent = {
54event: "continued",
55type: "event",
56seq: event["seq"], // tslint:disable-line
57body: { threadId: event.body.threadId },
58};
59
60super.sendEvent(continuedEvent);
e45838cbVladimir Kotikov9 years ago61return;
62}
63
64super.sendEvent(event);
65}
66
67protected dispatchRequest(request: VSCodeDebugAdapterPackage.Request): void {
68if (request.command === "disconnect")
69return this.disconnect(request);
70
71if (request.command === "attach")
72return this.attach(request);
73
74if (request.command === "launch")
75return this.launch(request);
76
77return super.dispatchRequest(request);
78}
79
80private launch(request: VSCodeDebugAdapterPackage.Request): void {
81this.requestSetup(request.arguments);
82this.mobilePlatformOptions.target = request.arguments.target || "simulator";
83this.mobilePlatformOptions.iosRelativeProjectPath = !isNullOrUndefined(request.arguments.iosRelativeProjectPath) ?
84request.arguments.iosRelativeProjectPath :
85IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
86
87// We add the parameter if it's defined (adapter crashes otherwise)
88if (!isNullOrUndefined(request.arguments.logCatArguments)) {
89this.mobilePlatformOptions.logCatArguments = [parseLogCatArguments(request.arguments.logCatArguments)];
90}
91
92if (!isNullOrUndefined(request.arguments.variant)) {
93this.mobilePlatformOptions.variant = request.arguments.variant;
94}
95
60d501abNick Hodapp9 years ago96if (!isNullOrUndefined(request.arguments.scheme)) {
97this.mobilePlatformOptions.scheme = request.arguments.scheme;
98}
99
e45838cbVladimir Kotikov9 years ago100TelemetryHelper.generate("launch", (generator) => {
101return this.remoteExtension.getPackagerPort()
b8999098Dmitry Zinovyev9 years ago102.then((packagerPort: number) => {
103this.mobilePlatformOptions.packagerPort = packagerPort;
104const mobilePlatform = new PlatformResolver()
105.resolveMobilePlatform(request.arguments.platform, this.mobilePlatformOptions);
106
107generator.step("checkPlatformCompatibility");
108TargetPlatformHelper.checkTargetPlatformSupport(this.mobilePlatformOptions.platform);
109generator.step("startPackager");
110return mobilePlatform.startPackager()
111.then(() => {
112// We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
113// and the user needs to Reload JS manually. We prewarm it to prevent that issue
114generator.step("prewarmBundleCache");
115Log.logMessage("Prewarming bundle cache. This may take a while ...");
116return mobilePlatform.prewarmBundleCache();
117})
118.then(() => {
119generator.step("mobilePlatform.runApp");
120Log.logMessage("Building and running application.");
121return mobilePlatform.runApp();
122})
123.then(() => {
124return this.attachRequest(request, packagerPort, mobilePlatform);
125});
e45838cbVladimir Kotikov9 years ago126})
b8999098Dmitry Zinovyev9 years ago127.catch(error => this.bailOut(error.message));
e45838cbVladimir Kotikov9 years ago128});
129
130}
131
132private attach(request: VSCodeDebugAdapterPackage.Request): void {
133this.requestSetup(request.arguments);
f920e582Vladimir Kotikov9 years ago134this.remoteExtension.getPackagerPort()
135.then((packagerPort: number) => this.attachRequest(request, packagerPort));
e45838cbVladimir Kotikov9 years ago136}
137
138private disconnect(request: VSCodeDebugAdapterPackage.Request): void {
139// The client is about to disconnect so first we need to stop app worker
f920e582Vladimir Kotikov9 years ago140if (this.appWorker) {
141this.appWorker.stop();
142}
e45838cbVladimir Kotikov9 years ago143
144// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
145if (this.mobilePlatformOptions.platform === "android") {
146this.remoteExtension.stopMonitoringLogcat()
b8999098Dmitry Zinovyev9 years ago147.catch(reason => Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
148.finally(() => super.dispatchRequest(request));
e45838cbVladimir Kotikov9 years ago149} else {
150super.dispatchRequest(request);
151}
152}
153
f920e582Vladimir Kotikov9 years ago154private requestSetup(args: any): void {
e45838cbVladimir Kotikov9 years ago155this.projectRootPath = getProjectRoot(args);
156this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
157this.mobilePlatformOptions = {
158projectRoot: this.projectRootPath,
159platform: args.platform,
160};
161
162// Start to send telemetry
163telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
164appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
165
166Log.SetGlobalLogger(new NodeDebugAdapterLogger(debugAdapterPackage, this));
167}
168
169/**
170* Runs logic needed to attach.
171* Attach should:
172* - Enable js debugging
173*/
b8999098Dmitry Zinovyev9 years ago174private attachRequest(
175request: VSCodeDebugAdapterPackage.Request,
176packagerPort: number,
177mobilePlatform?: GeneralMobilePlatform): Q.Promise<void> {
e45838cbVladimir Kotikov9 years ago178return TelemetryHelper.generate("attach", (generator) => {
179return Q({})
180.then(() => {
181generator.step("mobilePlatform.enableJSDebuggingMode");
182if (mobilePlatform) {
183return mobilePlatform.enableJSDebuggingMode();
184} else {
185Log.logMessage("Debugger ready. Enable remote debugging in app.");
186}
187})
188.then(() => {
189
190Log.logMessage("Starting debugger app worker.");
191// TODO: remove dependency on args.program - "program" property is technically
192// no more required in launch configuration and could be removed
193const workspaceRootPath = path.resolve(path.dirname(request.arguments.program), "..");
194const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
195
196// If launch is invoked first time, appWorker is undefined, so create it here
b05b5086Vladimir Kotikov9 years ago197this.appWorker = new MultipleLifetimesAppWorker(packagerPort, sourcesStoragePath);
e45838cbVladimir Kotikov9 years ago198this.appWorker.on("connected", (port: number) => {
f920e582Vladimir Kotikov9 years ago199Log.logMessage("Debugger worker loaded runtime on port " + port);
e45838cbVladimir Kotikov9 years ago200// Don't mutate original request to avoid side effects
201let attachArguments = Object.assign({}, request.arguments, { port, restart: true, request: "attach" });
202let attachRequest = Object.assign({}, request, { command: "attach", arguments: attachArguments });
203
6c75098eVladimir9 years ago204// Reinstantiate debug adapter, as the current implementation of ChromeDebugAdapter
205// doesn't allow us to reattach to another debug target easily. As of now it's easier
206// to throw previous instance out and create a new one.
207this._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
e45838cbVladimir Kotikov9 years ago208super.dispatchRequest(attachRequest);
209});
210
211return this.appWorker.start();
212})
213.catch(error => this.bailOut(error.message));
214});
215}
216
217/**
218* Logs error to user and finishes the debugging process.
219*/
220private bailOut(message: string): void {
221Log.logError(`Could not debug. ${message}`);
8049d420Vladimir Kotikov9 years ago222this.sendEvent(new debugAdapterPackage.TerminatedEvent());
e45838cbVladimir Kotikov9 years ago223};
224};
225}
226
227export function makeAdapter(debugAdapterClass: typeof Node2DebugAdapterPackage.Node2DebugAdapter): typeof Node2DebugAdapterPackage.Node2DebugAdapter {
228return class extends debugAdapterClass {
229public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
4b86d595Vladimir Kotikov9 years ago230// We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
e45838cbVladimir Kotikov9 years ago231// to set up breakpoints on initial pause event
232this._attachMode = false;
233return super.doAttach(port, targetUrl, address, timeout);
234}
471a26dfNikita Matrosov9 years ago235
236public setBreakpoints(args: any, requestSeq: number, ids?: number[]): Promise<Node2DebugAdapterPackage.ISetBreakpointsResponseBody> {
237// We need to overwrite ChromeDebug's setBreakpoints to get rid unhandled rejections
238// when breakpoints are being set up unsuccessfully
239return super.setBreakpoints(args, requestSeq, ids).catch((err) => {
240Log.logInternalMessage(LogLevel.Error, err.message);
241return {
242breakpoints: [],
243};
244});
245}
e45838cbVladimir Kotikov9 years ago246};
247}
248
249/**
250* Parses log cat arguments to a string
251*/
252function parseLogCatArguments(userProvidedLogCatArguments: any): string {
253return Array.isArray(userProvidedLogCatArguments)
254? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
255: userProvidedLogCatArguments; // If not, we leave it as-is
256}
257
258/**
259* Helper method to know if a value is either null or undefined
260*/
261function isNullOrUndefined(value: any): boolean {
262return typeof value === "undefined" || value === null;
263}
264
265/**
266* Parses settings.json file for workspace root property
267*/
268function getProjectRoot(args: any): string {
269try {
270let vsCodeRoot = path.resolve(args.program, "../..");
271let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
272let settingsContent = fs.readFileSync(settingsPath, "utf8");
273settingsContent = stripJsonComments(settingsContent);
274let parsedSettings = JSON.parse(settingsContent);
275let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
276return path.resolve(vsCodeRoot, projectRootPath);
277} catch (e) {
278return path.resolve(args.program, "../..");
279}
280}