microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
b42a1c408b75107dddba9ba6683ead305366755d

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

249lines · 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
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,
24debugSessionOpts: ChromeDebuggerCorePackage.IChromeDebugSessionOpts,
25debugAdapterPackage: typeof VSCodeDebugAdapterPackage,
26telemetryReporter: ReassignableTelemetryReporter,
27appName: string, version: string): typeof ChromeDebuggerCorePackage.ChromeDebugSession {
28
29return class extends debugSessionClass {
30
31private projectRootPath: string;
32private remoteExtension: RemoteExtension;
33private mobilePlatformOptions: IRunOptions;
34private appWorker: MultipleLifetimesAppWorker = null;
35
36constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
37super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
38}
39
40// Override ChromeDebugSession's sendEvent to control what we will send to client
41public 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.
47if (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
49this._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
50return;
51}
52
53super.sendEvent(event);
54}
55
56protected dispatchRequest(request: VSCodeDebugAdapterPackage.Request): void {
57if (request.command === "disconnect")
58return this.disconnect(request);
59
60if (request.command === "attach")
61return this.attach(request);
62
63if (request.command === "launch")
64return this.launch(request);
65
66return super.dispatchRequest(request);
67}
68
69private launch(request: VSCodeDebugAdapterPackage.Request): void {
70this.requestSetup(request.arguments);
71this.mobilePlatformOptions.target = request.arguments.target || "simulator";
72this.mobilePlatformOptions.iosRelativeProjectPath = !isNullOrUndefined(request.arguments.iosRelativeProjectPath) ?
73request.arguments.iosRelativeProjectPath :
74IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
75
76// We add the parameter if it's defined (adapter crashes otherwise)
77if (!isNullOrUndefined(request.arguments.logCatArguments)) {
78this.mobilePlatformOptions.logCatArguments = [parseLogCatArguments(request.arguments.logCatArguments)];
79}
80
81if (!isNullOrUndefined(request.arguments.variant)) {
82this.mobilePlatformOptions.variant = request.arguments.variant;
83}
84
85TelemetryHelper.generate("launch", (generator) => {
86return this.remoteExtension.getPackagerPort()
87.then((packagerPort: number) => {
88this.mobilePlatformOptions.packagerPort = packagerPort;
89const mobilePlatform = new PlatformResolver()
90.resolveMobilePlatform(request.arguments.platform, this.mobilePlatformOptions);
91
92generator.step("checkPlatformCompatibility");
93TargetPlatformHelper.checkTargetPlatformSupport(this.mobilePlatformOptions.platform);
94generator.step("startPackager");
95return 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
99generator.step("prewarmBundleCache");
100Log.logMessage("Prewarming bundle cache. This may take a while ...");
101return mobilePlatform.prewarmBundleCache();
102})
103.then(() => {
104generator.step("mobilePlatform.runApp");
105Log.logMessage("Building and running application.");
106return mobilePlatform.runApp();
107})
108.then(() => {
f920e582Vladimir Kotikov9 years ago109return this.attachRequest(request, packagerPort, mobilePlatform);
e45838cbVladimir Kotikov9 years ago110});
f920e582Vladimir Kotikov9 years ago111})
112.catch(error => this.bailOut(error.message));
e45838cbVladimir Kotikov9 years ago113});
114
115}
116
117private attach(request: VSCodeDebugAdapterPackage.Request): void {
118this.requestSetup(request.arguments);
f920e582Vladimir Kotikov9 years ago119this.remoteExtension.getPackagerPort()
120.then((packagerPort: number) => this.attachRequest(request, packagerPort));
e45838cbVladimir Kotikov9 years ago121}
122
123private disconnect(request: VSCodeDebugAdapterPackage.Request): void {
124// The client is about to disconnect so first we need to stop app worker
f920e582Vladimir Kotikov9 years ago125if (this.appWorker) {
126this.appWorker.stop();
127}
e45838cbVladimir Kotikov9 years ago128
129// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
130if (this.mobilePlatformOptions.platform === "android") {
131this.remoteExtension.stopMonitoringLogcat()
132.catch(reason => Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
133.finally(() => super.dispatchRequest(request));
134} else {
135super.dispatchRequest(request);
136}
137}
138
f920e582Vladimir Kotikov9 years ago139private requestSetup(args: any): void {
e45838cbVladimir Kotikov9 years ago140this.projectRootPath = getProjectRoot(args);
141this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
142this.mobilePlatformOptions = {
143projectRoot: this.projectRootPath,
144platform: args.platform,
145};
146
147// Start to send telemetry
148telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
149appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
150
151Log.SetGlobalLogger(new NodeDebugAdapterLogger(debugAdapterPackage, this));
152}
153
154/**
155* Runs logic needed to attach.
156* Attach should:
157* - Enable js debugging
158*/
f920e582Vladimir Kotikov9 years ago159private attachRequest(request: VSCodeDebugAdapterPackage.Request,
160packagerPort: number,
161mobilePlatform?: GeneralMobilePlatform): Q.Promise<void> {
e45838cbVladimir Kotikov9 years ago162return TelemetryHelper.generate("attach", (generator) => {
163return Q({})
164.then(() => {
165generator.step("mobilePlatform.enableJSDebuggingMode");
166if (mobilePlatform) {
167return mobilePlatform.enableJSDebuggingMode();
168} else {
169Log.logMessage("Debugger ready. Enable remote debugging in app.");
170}
171})
172.then(() => {
173
174Log.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
177const workspaceRootPath = path.resolve(path.dirname(request.arguments.program), "..");
178const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
179
180// If launch is invoked first time, appWorker is undefined, so create it here
b05b5086Vladimir Kotikov9 years ago181this.appWorker = new MultipleLifetimesAppWorker(packagerPort, sourcesStoragePath);
e45838cbVladimir Kotikov9 years ago182this.appWorker.on("connected", (port: number) => {
f920e582Vladimir Kotikov9 years ago183Log.logMessage("Debugger worker loaded runtime on port " + port);
e45838cbVladimir Kotikov9 years ago184// Don't mutate original request to avoid side effects
185let attachArguments = Object.assign({}, request.arguments, { port, restart: true, request: "attach" });
186let attachRequest = Object.assign({}, request, { command: "attach", arguments: attachArguments });
187
188super.dispatchRequest(attachRequest);
189});
190
191return 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*/
200private bailOut(message: string): void {
201Log.logError(`Could not debug. ${message}`);
8049d420Vladimir Kotikov9 years ago202this.sendEvent(new debugAdapterPackage.TerminatedEvent());
e45838cbVladimir Kotikov9 years ago203};
204};
205}
206
207export function makeAdapter(debugAdapterClass: typeof Node2DebugAdapterPackage.Node2DebugAdapter): typeof Node2DebugAdapterPackage.Node2DebugAdapter {
208return class extends debugAdapterClass {
209public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
4b86d595Vladimir Kotikov9 years ago210// We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
e45838cbVladimir Kotikov9 years ago211// to set up breakpoints on initial pause event
212this._attachMode = false;
213return 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 {
222return 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 {
231return typeof value === "undefined" || value === null;
232}
233
234/**
235* Parses settings.json file for workspace root property
236*/
237function getProjectRoot(args: any): string {
238try {
239let vsCodeRoot = path.resolve(args.program, "../..");
240let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
241let settingsContent = fs.readFileSync(settingsPath, "utf8");
242settingsContent = stripJsonComments(settingsContent);
243let parsedSettings = JSON.parse(settingsContent);
244let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
245return path.resolve(vsCodeRoot, projectRootPath);
246} catch (e) {
247return path.resolve(args.program, "../..");
248}
249}