microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cc70057d4beada1315f86cbbf3246d5c065b9eda

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

249lines · modecode

1// 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
9import {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 {GeneralMobilePlatform} from "../common/generalMobilePlatform";
20
21import { MultipleLifetimesAppWorker } from "./appWorker";
22
23export function makeSession(debugSessionClass: typeof ChromeDebuggerCorePackage.ChromeDebugSession,
24 debugSessionOpts: ChromeDebuggerCorePackage.IChromeDebugSessionOpts,
25 debugAdapterPackage: typeof VSCodeDebugAdapterPackage,
26 telemetryReporter: ReassignableTelemetryReporter,
27 appName: string, version: string): typeof ChromeDebuggerCorePackage.ChromeDebugSession {
28
29 return class extends debugSessionClass {
30
31 private projectRootPath: string;
32 private remoteExtension: RemoteExtension;
33 private mobilePlatformOptions: IRunOptions;
34 private appWorker: MultipleLifetimesAppWorker = null;
35
36 constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
37 super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
38 }
39
40 // Override ChromeDebugSession's sendEvent to control what we will send to client
41 public sendEvent(event: VSCodeDebugAdapterPackage.Event): void {
42 // Do not send "terminated" events signaling about session's restart to client as it would cause it
43 // to restart adapter's process, while we want to stay alive and don't want to interrupt connection
44 // to packager. Also in this case we need to reinstantiate the debug adapter, as the current
45 // implementation of ChromeDebugAdapter doesn't allow us to reattach to another debug target easily.
46 // As of now it's easier to throw previous instance out and create a new one.
47 if (event.event === "terminated" && event.body && event.body.restart === true) {
48 // Casting debugAdapter to any to make this compile, as TS doesn't allow instantiating abstract classes
49 this._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
50 return;
51 }
52
53 super.sendEvent(event);
54 }
55
56 protected dispatchRequest(request: VSCodeDebugAdapterPackage.Request): void {
57 if (request.command === "disconnect")
58 return this.disconnect(request);
59
60 if (request.command === "attach")
61 return this.attach(request);
62
63 if (request.command === "launch")
64 return this.launch(request);
65
66 return super.dispatchRequest(request);
67 }
68
69 private launch(request: VSCodeDebugAdapterPackage.Request): void {
70 this.requestSetup(request.arguments);
71 this.mobilePlatformOptions.target = request.arguments.target || "simulator";
72 this.mobilePlatformOptions.iosRelativeProjectPath = !isNullOrUndefined(request.arguments.iosRelativeProjectPath) ?
73 request.arguments.iosRelativeProjectPath :
74 IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
75
76 // We add the parameter if it's defined (adapter crashes otherwise)
77 if (!isNullOrUndefined(request.arguments.logCatArguments)) {
78 this.mobilePlatformOptions.logCatArguments = [parseLogCatArguments(request.arguments.logCatArguments)];
79 }
80
81 if (!isNullOrUndefined(request.arguments.variant)) {
82 this.mobilePlatformOptions.variant = request.arguments.variant;
83 }
84
85 TelemetryHelper.generate("launch", (generator) => {
86 return this.remoteExtension.getPackagerPort()
87 .then((packagerPort: number) => {
88 this.mobilePlatformOptions.packagerPort = packagerPort;
89 const mobilePlatform = new PlatformResolver()
90 .resolveMobilePlatform(request.arguments.platform, this.mobilePlatformOptions);
91
92 generator.step("checkPlatformCompatibility");
93 TargetPlatformHelper.checkTargetPlatformSupport(this.mobilePlatformOptions.platform);
94 generator.step("startPackager");
95 return mobilePlatform.startPackager()
96 .then(() => {
97 // 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
98 // and the user needs to Reload JS manually. We prewarm it to prevent that issue
99 generator.step("prewarmBundleCache");
100 Log.logMessage("Prewarming bundle cache. This may take a while ...");
101 return mobilePlatform.prewarmBundleCache();
102 })
103 .then(() => {
104 generator.step("mobilePlatform.runApp");
105 Log.logMessage("Building and running application.");
106 return mobilePlatform.runApp();
107 })
108 .then(() => {
109 return this.attachRequest(request, packagerPort, mobilePlatform);
110 });
111 })
112 .catch(error => this.bailOut(error.message));
113 });
114
115 }
116
117 private attach(request: VSCodeDebugAdapterPackage.Request): void {
118 this.requestSetup(request.arguments);
119 this.remoteExtension.getPackagerPort()
120 .then((packagerPort: number) => this.attachRequest(request, packagerPort));
121 }
122
123 private disconnect(request: VSCodeDebugAdapterPackage.Request): void {
124 // The client is about to disconnect so first we need to stop app worker
125 if (this.appWorker) {
126 this.appWorker.stop();
127 }
128
129 // Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
130 if (this.mobilePlatformOptions.platform === "android") {
131 this.remoteExtension.stopMonitoringLogcat()
132 .catch(reason => Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
133 .finally(() => super.dispatchRequest(request));
134 } else {
135 super.dispatchRequest(request);
136 }
137 }
138
139 private requestSetup(args: any): void {
140 this.projectRootPath = getProjectRoot(args);
141 this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
142 this.mobilePlatformOptions = {
143 projectRoot: this.projectRootPath,
144 platform: args.platform,
145 };
146
147 // Start to send telemetry
148 telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
149 appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
150
151 Log.SetGlobalLogger(new NodeDebugAdapterLogger(debugAdapterPackage, this));
152 }
153
154 /**
155 * Runs logic needed to attach.
156 * Attach should:
157 * - Enable js debugging
158 */
159 private attachRequest(request: VSCodeDebugAdapterPackage.Request,
160 packagerPort: number,
161 mobilePlatform?: GeneralMobilePlatform): Q.Promise<void> {
162 return TelemetryHelper.generate("attach", (generator) => {
163 return Q({})
164 .then(() => {
165 generator.step("mobilePlatform.enableJSDebuggingMode");
166 if (mobilePlatform) {
167 return mobilePlatform.enableJSDebuggingMode();
168 } else {
169 Log.logMessage("Debugger ready. Enable remote debugging in app.");
170 }
171 })
172 .then(() => {
173
174 Log.logMessage("Starting debugger app worker.");
175 // TODO: remove dependency on args.program - "program" property is technically
176 // no more required in launch configuration and could be removed
177 const workspaceRootPath = path.resolve(path.dirname(request.arguments.program), "..");
178 const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
179
180 // If launch is invoked first time, appWorker is undefined, so create it here
181 this.appWorker = new MultipleLifetimesAppWorker(packagerPort, sourcesStoragePath);
182 this.appWorker.on("connected", (port: number) => {
183 Log.logMessage("Debugger worker loaded runtime on port " + port);
184 // Don't mutate original request to avoid side effects
185 let attachArguments = Object.assign({}, request.arguments, { port, restart: true, request: "attach" });
186 let attachRequest = Object.assign({}, request, { command: "attach", arguments: attachArguments });
187
188 super.dispatchRequest(attachRequest);
189 });
190
191 return this.appWorker.start();
192 })
193 .catch(error => this.bailOut(error.message));
194 });
195 }
196
197 /**
198 * Logs error to user and finishes the debugging process.
199 */
200 private bailOut(message: string): void {
201 Log.logError(`Could not debug. ${message}`);
202 this.sendEvent(new debugAdapterPackage.TerminatedEvent());
203 };
204 };
205}
206
207export function makeAdapter(debugAdapterClass: typeof Node2DebugAdapterPackage.Node2DebugAdapter): typeof Node2DebugAdapterPackage.Node2DebugAdapter {
208 return class extends debugAdapterClass {
209 public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
210 // We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
211 // to set up breakpoints on initial pause event
212 this._attachMode = false;
213 return super.doAttach(port, targetUrl, address, timeout);
214 }
215 };
216}
217
218/**
219 * Parses log cat arguments to a string
220 */
221function parseLogCatArguments(userProvidedLogCatArguments: any): string {
222 return Array.isArray(userProvidedLogCatArguments)
223 ? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
224 : userProvidedLogCatArguments; // If not, we leave it as-is
225}
226
227/**
228 * Helper method to know if a value is either null or undefined
229 */
230function isNullOrUndefined(value: any): boolean {
231 return typeof value === "undefined" || value === null;
232}
233
234/**
235 * Parses settings.json file for workspace root property
236 */
237function getProjectRoot(args: any): string {
238 try {
239 let vsCodeRoot = path.resolve(args.program, "../..");
240 let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
241 let settingsContent = fs.readFileSync(settingsPath, "utf8");
242 settingsContent = stripJsonComments(settingsContent);
243 let parsedSettings = JSON.parse(settingsContent);
244 let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
245 return path.resolve(vsCodeRoot, projectRootPath);
246 } catch (e) {
247 return path.resolve(args.program, "../..");
248 }
249}
250