microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
10136ee86a53d80ade52ebf07175f4acbed01d7f

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

316lines · 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");
b8999098Dmitry Zinovyev9 years ago8import { Telemetry } from "../common/telemetry";
9import { TelemetryHelper } from "../common/telemetryHelper";
10import { RemoteExtension } from "../common/remoteExtension";
3a155214Artem Egorov8 years ago11import { RemoteTelemetryReporter, ReassignableTelemetryReporter } from "../common/telemetryReporters";
e67ace8aYuri Skorokhodov6 years ago12import { ChromeDebugSession, IChromeDebugSessionOpts, ChromeDebugAdapter, logger, Crdp, stoppedEvent, IOnPausedResult } from "vscode-chrome-debug-core";
e3b0fb3bChance An8 years ago13import { ContinuedEvent, TerminatedEvent, Logger, Response } from "vscode-debugadapter";
0a68f8dbArtem Egorov8 years ago14import { DebugProtocol } from "vscode-debugprotocol";
e45838cbVladimir Kotikov9 years ago15import { MultipleLifetimesAppWorker } from "./appWorker";
2d876061Ruslan Bikkinin8 years ago16import { ReactNativeProjectHelper } from "../common/reactNativeProjectHelper";
d124bf0eYuri Skorokhodov7 years ago17import * as nls from "vscode-nls";
18import { ErrorHelper } from "../common/error/errorHelper";
19import { InternalErrorCode } from "../common/error/internalErrorCode";
08722261Yuri Skorokhodov7 years ago20import { getLoggingDirectory } from "../extension/log/LogHelper";
3955d20aYuri Skorokhodov6 years ago21import * as mkdirp from "mkdirp";
d124bf0eYuri Skorokhodov7 years ago22const localize = nls.loadMessageBundle();
0a68f8dbArtem Egorov8 years ago23
b8999098Dmitry Zinovyev9 years ago24export function makeSession(
0a68f8dbArtem Egorov8 years ago25debugSessionClass: typeof ChromeDebugSession,
26debugSessionOpts: IChromeDebugSessionOpts,
b8999098Dmitry Zinovyev9 years ago27telemetryReporter: ReassignableTelemetryReporter,
0a68f8dbArtem Egorov8 years ago28appName: string, version: string): typeof ChromeDebugSession {
e45838cbVladimir Kotikov9 years ago29
30return class extends debugSessionClass {
31
32private projectRootPath: string;
33private remoteExtension: RemoteExtension;
5c8365a6Artem Egorov8 years ago34private appWorker: MultipleLifetimesAppWorker | null = null;
e45838cbVladimir Kotikov9 years ago35
36constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
37super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
38}
39
40// Override ChromeDebugSession's sendEvent to control what we will send to client
0a68f8dbArtem Egorov8 years ago41public sendEvent(event: DebugProtocol.Event): void {
e45838cbVladimir Kotikov9 years ago42// 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
6c75098eVladimir9 years ago44// to packager.
b8999098Dmitry Zinovyev9 years ago45
720e992dArtem Egorov8 years ago46if (event.event === "terminated" && event.body && event.body.restart) {
b8999098Dmitry Zinovyev9 years ago47
48// Worker has been reloaded and switched to "continue" state
49// So we have to send "continued" event to client instead of "terminated"
50// Otherwise client might mistakenly show "stopped" state
0a68f8dbArtem Egorov8 years ago51let continuedEvent: ContinuedEvent = {
b8999098Dmitry Zinovyev9 years ago52event: "continued",
53type: "event",
54seq: event["seq"], // tslint:disable-line
55body: { threadId: event.body.threadId },
56};
57
58super.sendEvent(continuedEvent);
e45838cbVladimir Kotikov9 years ago59return;
60}
61
62super.sendEvent(event);
63}
64
0a68f8dbArtem Egorov8 years ago65protected dispatchRequest(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago66if (request.command === "disconnect")
67return this.disconnect(request);
68
69if (request.command === "attach")
70return this.attach(request);
71
72if (request.command === "launch")
73return this.launch(request);
74
75return super.dispatchRequest(request);
76}
77
0a68f8dbArtem Egorov8 years ago78private launch(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago79this.requestSetup(request.arguments)
80.then(() => {
db6fd42aRuslan Bikkinin7 years ago81logger.verbose(`Handle launch request: ${JSON.stringify(request.arguments, null , 2)}`);
2d876061Ruslan Bikkinin8 years ago82return this.remoteExtension.launch(request);
83})
cbb0e869Artem Egorov8 years ago84.then(() => {
18ea7d15Yuri Skorokhodov7 years ago85return this.remoteExtension.getPackagerPort(request.arguments.cwd || request.arguments.program);
0a68f8dbArtem Egorov8 years ago86})
87.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago88this.attachRequest({
89...request,
90arguments: {
91...request.arguments,
92port: packagerPort,
93},
94});
0a68f8dbArtem Egorov8 years ago95})
96.catch(error => {
748105d9Artem Egorov8 years ago97this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago98});
e45838cbVladimir Kotikov9 years ago99}
100
0a68f8dbArtem Egorov8 years ago101private attach(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago102this.requestSetup(request.arguments)
103.then(() => {
dbfbebd9Yuri Skorokhodov7 years ago104logger.verbose(`Handle attach request: ${JSON.stringify(request.arguments, null , 2)}`);
18ea7d15Yuri Skorokhodov7 years ago105return this.remoteExtension.getPackagerPort(request.arguments.cwd || request.arguments.program);
2d876061Ruslan Bikkinin8 years ago106})
0a68f8dbArtem Egorov8 years ago107.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago108this.attachRequest({
109...request,
110arguments: {
111...request.arguments,
e92278b5Serge Svekolnikov8 years ago112port: request.arguments.port || packagerPort,
6eeec3c0Serge Svekolnikov8 years ago113},
114});
2d876061Ruslan Bikkinin8 years ago115})
116.catch(error => {
117this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago118});
e45838cbVladimir Kotikov9 years ago119}
120
0a68f8dbArtem Egorov8 years ago121private disconnect(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago122// The client is about to disconnect so first we need to stop app worker
f920e582Vladimir Kotikov9 years ago123if (this.appWorker) {
124this.appWorker.stop();
125}
e45838cbVladimir Kotikov9 years ago126
127// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
0a68f8dbArtem Egorov8 years ago128if (request.arguments.platform === "android") {
e45838cbVladimir Kotikov9 years ago129this.remoteExtension.stopMonitoringLogcat()
d124bf0eYuri Skorokhodov7 years ago130.catch(reason => logger.warn(localize("CouldNotStopMonitoringLogcat", "Couldn't stop monitoring logcat: {0}", reason.message || reason)))
b8999098Dmitry Zinovyev9 years ago131.finally(() => super.dispatchRequest(request));
e45838cbVladimir Kotikov9 years ago132} else {
133super.dispatchRequest(request);
134}
135}
136
2d876061Ruslan Bikkinin8 years ago137private requestSetup(args: any): Q.Promise<void> {
08722261Yuri Skorokhodov7 years ago138// If special env variables are defined, then write process outputs to file
139let chromeDebugCoreLogs = getLoggingDirectory();
140if (chromeDebugCoreLogs) {
141chromeDebugCoreLogs = path.join(chromeDebugCoreLogs, "ChromeDebugCoreLogs.txt");
142}
0a68f8dbArtem Egorov8 years ago143let logLevel: string = args.trace;
144if (logLevel) {
145logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
08722261Yuri Skorokhodov7 years ago146logger.setup(Logger.LogLevel[logLevel], chromeDebugCoreLogs || false);
0a68f8dbArtem Egorov8 years ago147} else {
08722261Yuri Skorokhodov7 years ago148logger.setup(Logger.LogLevel.Log, chromeDebugCoreLogs || false);
0a68f8dbArtem Egorov8 years ago149}
150
18ea7d15Yuri Skorokhodov7 years ago151
135c493eYuri Skorokhodov7 years ago152if (!args.sourceMaps) {
153args.sourceMaps = true;
154}
2d876061Ruslan Bikkinin8 years ago155const projectRootPath = getProjectRoot(args);
156return ReactNativeProjectHelper.isReactNativeProject(projectRootPath)
157.then((result) => {
158if (!result) {
d124bf0eYuri Skorokhodov7 years ago159throw ErrorHelper.getInternalError(InternalErrorCode.NotInReactNativeFolderError);
2d876061Ruslan Bikkinin8 years ago160}
161this.projectRootPath = projectRootPath;
162this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
163
164// Start to send telemetry
165telemetryReporter.reassignTo(new RemoteTelemetryReporter(
166appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
23b5fa80Yuri Skorokhodov6 years ago167
168if (args.program) {
169// TODO: Remove this warning when program property will be completely removed
170logger.warn(localize("ProgramPropertyDeprecationWarning", "Launched debug configuration contains 'program' property which is deprecated and will be removed soon. Please replace it with: \"cwd\": \"${workspaceFolder}\""));
171const useProgramEvent = TelemetryHelper.createTelemetryEvent("useProgramProperty");
172Telemetry.send(useProgramEvent);
173}
174if (args.cwd) {
175// To match count of 'cwd' users with 'program' users. TODO: Remove when program property will be removed
176const useCwdEvent = TelemetryHelper.createTelemetryEvent("useCwdProperty");
177Telemetry.send(useCwdEvent);
178}
2d876061Ruslan Bikkinin8 years ago179return void 0;
180});
e45838cbVladimir Kotikov9 years ago181}
182
183/**
184* Runs logic needed to attach.
185* Attach should:
186* - Enable js debugging
187*/
0a68f8dbArtem Egorov8 years ago188// tslint:disable-next-line:member-ordering
031832ffArtem Egorov8 years ago189protected attachRequest(request: DebugProtocol.Request): Q.Promise<void> {
ba953e9fRedMickey6 years ago190let extProps = {
031832ffArtem Egorov8 years ago191platform: {
192value: request.arguments.platform,
193isPii: false,
194},
195};
196
ba953e9fRedMickey6 years ago197return ReactNativeProjectHelper.getReactNativeVersion(request.arguments.cwd)
198.then(version => {
199extProps = TelemetryHelper.addReactNativeVersionToEventProperties(version, extProps);
200return TelemetryHelper.generate("attach", extProps, (generator) => {
201return Q({})
202.then(() => {
203logger.log(localize("StartingDebuggerAppWorker", "Starting debugger app worker."));
204// TODO: remove dependency on args.program - "program" property is technically
205// no more required in launch configuration and could be removed
206const workspaceRootPath = request.arguments.cwd ? path.resolve(request.arguments.cwd) : path.resolve(path.dirname(request.arguments.program), "..");
207const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
208// Create folder if not exist to avoid problems if
209// RN project root is not a ${workspaceFolder}
210mkdirp.sync(sourcesStoragePath);
211
212// If launch is invoked first time, appWorker is undefined, so create it here
213this.appWorker = new MultipleLifetimesAppWorker(
214request.arguments,
215sourcesStoragePath,
216this.projectRootPath,
217undefined);
218this.appWorker.on("connected", (port: number) => {
219logger.log(localize("DebuggerWorkerLoadedRuntimeOnPort", "Debugger worker loaded runtime on port {0}", port));
220// Don't mutate original request to avoid side effects
221let attachArguments = Object.assign({}, request.arguments, {
222address: "localhost",
223port,
224restart: true,
225request: "attach",
226remoteRoot: undefined,
227localRoot: undefined,
228});
229// Reinstantiate debug adapter, as the current implementation of ChromeDebugAdapter
230// doesn't allow us to reattach to another debug target easily. As of now it's easier
231// to throw previous instance out and create a new one.
232(this as any)._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
233
234// Explicity call _debugAdapter.attach() to prevent directly calling dispatchRequest()
235// yield a response as "attach" even for "launch" request. Because dispatchRequest() will
236// decide to do a sendResponse() aligning with the request parameter passed in.
237Q((this as any)._debugAdapter.attach(attachArguments, request.seq))
238.then((responseBody) => {
239const response: DebugProtocol.Response = new Response(request);
240response.body = responseBody;
241this.sendResponse(response);
242});
2d876061Ruslan Bikkinin8 years ago243});
e45838cbVladimir Kotikov9 years ago244
ba953e9fRedMickey6 years ago245return this.appWorker.start();
246})
247.catch(error => this.bailOut(error.message));
248});
249});
e45838cbVladimir Kotikov9 years ago250}
251
252/**
253* Logs error to user and finishes the debugging process.
254*/
255private bailOut(message: string): void {
d124bf0eYuri Skorokhodov7 years ago256logger.error(localize("CouldNotDebug", "Could not debug. {0}" , message));
0a68f8dbArtem Egorov8 years ago257this.sendEvent(new TerminatedEvent());
27710197Vladimir Kotikov8 years ago258}
e45838cbVladimir Kotikov9 years ago259};
260}
261
0a68f8dbArtem Egorov8 years ago262export function makeAdapter(debugAdapterClass: typeof ChromeDebugAdapter): typeof ChromeDebugAdapter {
e45838cbVladimir Kotikov9 years ago263return class extends debugAdapterClass {
e67ace8aYuri Skorokhodov6 years ago264private firstStop: boolean = true;
4f7b3bc0Anna Kocheshkova8 years ago265public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
4b86d595Vladimir Kotikov9 years ago266// We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
e45838cbVladimir Kotikov9 years ago267// to set up breakpoints on initial pause event
0a68f8dbArtem Egorov8 years ago268(this as any)._attachMode = false;
e45838cbVladimir Kotikov9 years ago269return super.doAttach(port, targetUrl, address, timeout);
270}
639a73d7Artem Egorov7 years ago271
e67ace8aYuri Skorokhodov6 years ago272// Since the bundle runs inside the Node.js VM in debuggerWorker.js in runtime
273// Node debug adapter need time to parse new added code source maps
274// So we added 'debugger;' statement at the start of the bundle code
275// and wait for the adapter to receive a signal to stop on that statement
276// and then wait for code bundle to be processed and then send continue request to skip the code execution stop in VS Code UI
277public onPaused(notification: Crdp.Debugger.PausedEvent, expectingStopReason?: stoppedEvent.ReasonType): Promise<IOnPausedResult> {
278// When pause on 'debugger;' statement, notification contains reason with value "other" instead of "breakpoint"
279if (this.firstStop && notification.reason === "other") {
280return new Promise<IOnPausedResult>((resolve) => {
281setTimeout(() => {
282this.firstStop = false;
283this.continue();
284resolve({didPause: false});
285}, 50);
286});
287} else {
288return super.onPaused(notification, expectingStopReason);
289}
290}
291
639a73d7Artem Egorov7 years ago292public async terminate(args: DebugProtocol.TerminatedEvent) {
293return this.disconnect({
294terminateDebuggee: true,
295});
296}
e45838cbVladimir Kotikov9 years ago297};
298}
299
300/**
301* Parses settings.json file for workspace root property
302*/
549baae2RedMickey6 years ago303export function getProjectRoot(args: any): string {
31796719RedMickey6 years ago304const vsCodeRoot = args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
305const settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
e45838cbVladimir Kotikov9 years ago306try {
307let settingsContent = fs.readFileSync(settingsPath, "utf8");
308settingsContent = stripJsonComments(settingsContent);
309let parsedSettings = JSON.parse(settingsContent);
9a364375Serge Svekolnikov8 years ago310let projectRootPath = parsedSettings["react-native-tools.projectRoot"] || parsedSettings["react-native-tools"].projectRoot;
e45838cbVladimir Kotikov9 years ago311return path.resolve(vsCodeRoot, projectRootPath);
312} catch (e) {
31796719RedMickey6 years ago313logger.verbose(`${settingsPath} file doesn't exist or its content is incorrect. This file will be ignored.`);
18ea7d15Yuri Skorokhodov7 years ago314return args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
e45838cbVladimir Kotikov9 years ago315}
316}