microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
d8cf3d51c1fa084e143ffa67a5540e6f399c4810

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

285lines · 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";
2d876061Ruslan Bikkinin8 years ago12import { ChromeDebugSession, IChromeDebugSessionOpts, ChromeDebugAdapter, logger } 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";
d124bf0eYuri Skorokhodov7 years ago21const localize = nls.loadMessageBundle();
0a68f8dbArtem Egorov8 years ago22
b8999098Dmitry Zinovyev9 years ago23export function makeSession(
0a68f8dbArtem Egorov8 years ago24debugSessionClass: typeof ChromeDebugSession,
25debugSessionOpts: IChromeDebugSessionOpts,
b8999098Dmitry Zinovyev9 years ago26telemetryReporter: ReassignableTelemetryReporter,
0a68f8dbArtem Egorov8 years ago27appName: string, version: string): typeof ChromeDebugSession {
e45838cbVladimir Kotikov9 years ago28
29return class extends debugSessionClass {
30
31private projectRootPath: string;
32private remoteExtension: RemoteExtension;
5c8365a6Artem Egorov8 years ago33private appWorker: MultipleLifetimesAppWorker | null = null;
e45838cbVladimir Kotikov9 years ago34
35constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
36super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
37}
38
39// Override ChromeDebugSession's sendEvent to control what we will send to client
0a68f8dbArtem Egorov8 years ago40public sendEvent(event: DebugProtocol.Event): void {
e45838cbVladimir Kotikov9 years ago41// Do not send "terminated" events signaling about session's restart to client as it would cause it
42// to restart adapter's process, while we want to stay alive and don't want to interrupt connection
6c75098eVladimir9 years ago43// to packager.
b8999098Dmitry Zinovyev9 years ago44
720e992dArtem Egorov8 years ago45if (event.event === "terminated" && event.body && event.body.restart) {
b8999098Dmitry Zinovyev9 years ago46
47// Worker has been reloaded and switched to "continue" state
48// So we have to send "continued" event to client instead of "terminated"
49// Otherwise client might mistakenly show "stopped" state
0a68f8dbArtem Egorov8 years ago50let continuedEvent: ContinuedEvent = {
b8999098Dmitry Zinovyev9 years ago51event: "continued",
52type: "event",
53seq: event["seq"], // tslint:disable-line
54body: { threadId: event.body.threadId },
55};
56
57super.sendEvent(continuedEvent);
e45838cbVladimir Kotikov9 years ago58return;
59}
60
61super.sendEvent(event);
62}
63
0a68f8dbArtem Egorov8 years ago64protected dispatchRequest(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago65if (request.command === "disconnect")
66return this.disconnect(request);
67
68if (request.command === "attach")
69return this.attach(request);
70
71if (request.command === "launch")
72return this.launch(request);
73
74return super.dispatchRequest(request);
75}
76
0a68f8dbArtem Egorov8 years ago77private launch(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago78this.requestSetup(request.arguments)
79.then(() => {
db6fd42aRuslan Bikkinin7 years ago80logger.verbose(`Handle launch request: ${JSON.stringify(request.arguments, null , 2)}`);
2d876061Ruslan Bikkinin8 years ago81return this.remoteExtension.launch(request);
82})
cbb0e869Artem Egorov8 years ago83.then(() => {
18ea7d15Yuri Skorokhodov7 years ago84return this.remoteExtension.getPackagerPort(request.arguments.cwd || request.arguments.program);
0a68f8dbArtem Egorov8 years ago85})
86.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago87this.attachRequest({
88...request,
89arguments: {
90...request.arguments,
91port: packagerPort,
92},
93});
0a68f8dbArtem Egorov8 years ago94})
95.catch(error => {
748105d9Artem Egorov8 years ago96this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago97});
e45838cbVladimir Kotikov9 years ago98}
99
0a68f8dbArtem Egorov8 years ago100private attach(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago101this.requestSetup(request.arguments)
102.then(() => {
dbfbebd9Yuri Skorokhodov7 years ago103logger.verbose(`Handle attach request: ${JSON.stringify(request.arguments, null , 2)}`);
18ea7d15Yuri Skorokhodov7 years ago104return this.remoteExtension.getPackagerPort(request.arguments.cwd || request.arguments.program);
2d876061Ruslan Bikkinin8 years ago105})
0a68f8dbArtem Egorov8 years ago106.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago107this.attachRequest({
108...request,
109arguments: {
110...request.arguments,
e92278b5Serge Svekolnikov8 years ago111port: request.arguments.port || packagerPort,
6eeec3c0Serge Svekolnikov8 years ago112},
113});
2d876061Ruslan Bikkinin8 years ago114})
115.catch(error => {
116this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago117});
e45838cbVladimir Kotikov9 years ago118}
119
0a68f8dbArtem Egorov8 years ago120private disconnect(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago121// The client is about to disconnect so first we need to stop app worker
f920e582Vladimir Kotikov9 years ago122if (this.appWorker) {
123this.appWorker.stop();
124}
e45838cbVladimir Kotikov9 years ago125
126// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
0a68f8dbArtem Egorov8 years ago127if (request.arguments.platform === "android") {
e45838cbVladimir Kotikov9 years ago128this.remoteExtension.stopMonitoringLogcat()
d124bf0eYuri Skorokhodov7 years ago129.catch(reason => logger.warn(localize("CouldNotStopMonitoringLogcat", "Couldn't stop monitoring logcat: {0}", reason.message || reason)))
b8999098Dmitry Zinovyev9 years ago130.finally(() => super.dispatchRequest(request));
e45838cbVladimir Kotikov9 years ago131} else {
132super.dispatchRequest(request);
133}
134}
135
2d876061Ruslan Bikkinin8 years ago136private requestSetup(args: any): Q.Promise<void> {
08722261Yuri Skorokhodov7 years ago137// If special env variables are defined, then write process outputs to file
138let chromeDebugCoreLogs = getLoggingDirectory();
139if (chromeDebugCoreLogs) {
140chromeDebugCoreLogs = path.join(chromeDebugCoreLogs, "ChromeDebugCoreLogs.txt");
141}
0a68f8dbArtem Egorov8 years ago142let logLevel: string = args.trace;
143if (logLevel) {
144logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
08722261Yuri Skorokhodov7 years ago145logger.setup(Logger.LogLevel[logLevel], chromeDebugCoreLogs || false);
0a68f8dbArtem Egorov8 years ago146} else {
08722261Yuri Skorokhodov7 years ago147logger.setup(Logger.LogLevel.Log, chromeDebugCoreLogs || false);
0a68f8dbArtem Egorov8 years ago148}
149
18ea7d15Yuri Skorokhodov7 years ago150if (args.program) {
151// TODO: Remove this warning when program property will be completely removed
152logger.warn(localize("ProgramPropertyDeprecationWarning", "Launched debug configuration contains 'program' property which is deprecated and will be removed soon. Please replace it with: \"cwd\": \"${workspaceFolder}\""));
153const useProgramEvent = TelemetryHelper.createTelemetryEvent("useProgramProperty");
154Telemetry.send(useProgramEvent);
155}
156if (args.cwd) {
157// To match count of 'cwd' users with 'program' users. TODO: Remove when program property will be removed
158const useCwdEvent = TelemetryHelper.createTelemetryEvent("useCwdProperty");
159Telemetry.send(useCwdEvent);
160}
161
135c493eYuri Skorokhodov7 years ago162if (!args.sourceMaps) {
163args.sourceMaps = true;
164}
2d876061Ruslan Bikkinin8 years ago165const projectRootPath = getProjectRoot(args);
166return ReactNativeProjectHelper.isReactNativeProject(projectRootPath)
167.then((result) => {
168if (!result) {
d124bf0eYuri Skorokhodov7 years ago169throw ErrorHelper.getInternalError(InternalErrorCode.NotInReactNativeFolderError);
2d876061Ruslan Bikkinin8 years ago170}
171this.projectRootPath = projectRootPath;
172this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
173
174// Start to send telemetry
175telemetryReporter.reassignTo(new RemoteTelemetryReporter(
176appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
177return void 0;
178});
e45838cbVladimir Kotikov9 years ago179}
180
181/**
182* Runs logic needed to attach.
183* Attach should:
184* - Enable js debugging
185*/
0a68f8dbArtem Egorov8 years ago186// tslint:disable-next-line:member-ordering
031832ffArtem Egorov8 years ago187protected attachRequest(request: DebugProtocol.Request): Q.Promise<void> {
188const extProps = {
189platform: {
190value: request.arguments.platform,
191isPii: false,
192},
193};
194
195return TelemetryHelper.generate("attach", extProps, (generator) => {
e45838cbVladimir Kotikov9 years ago196return Q({})
197.then(() => {
d124bf0eYuri Skorokhodov7 years ago198logger.log(localize("StartingDebuggerAppWorker", "Starting debugger app worker."));
e45838cbVladimir Kotikov9 years ago199// TODO: remove dependency on args.program - "program" property is technically
200// no more required in launch configuration and could be removed
18ea7d15Yuri Skorokhodov7 years ago201const workspaceRootPath = request.arguments.cwd ? path.resolve(request.arguments.cwd) : path.resolve(path.dirname(request.arguments.program), "..");
e45838cbVladimir Kotikov9 years ago202const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
203
204// If launch is invoked first time, appWorker is undefined, so create it here
6eeec3c0Serge Svekolnikov8 years ago205this.appWorker = new MultipleLifetimesAppWorker(
206request.arguments,
207sourcesStoragePath,
208this.projectRootPath,
209undefined);
e45838cbVladimir Kotikov9 years ago210this.appWorker.on("connected", (port: number) => {
d124bf0eYuri Skorokhodov7 years ago211logger.log(localize("DebuggerWorkerLoadedRuntimeOnPort", "Debugger worker loaded runtime on port {0}", port));
e45838cbVladimir Kotikov9 years ago212// Don't mutate original request to avoid side effects
6eeec3c0Serge Svekolnikov8 years ago213let attachArguments = Object.assign({}, request.arguments, {
214address: "localhost",
215port,
216restart: true,
217request: "attach",
218remoteRoot: undefined,
219localRoot: undefined,
220});
6c75098eVladimir9 years ago221// Reinstantiate debug adapter, as the current implementation of ChromeDebugAdapter
222// doesn't allow us to reattach to another debug target easily. As of now it's easier
223// to throw previous instance out and create a new one.
0a68f8dbArtem Egorov8 years ago224(this as any)._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
e3b0fb3bChance An8 years ago225
226// Explicity call _debugAdapter.attach() to prevent directly calling dispatchRequest()
227// yield a response as "attach" even for "launch" request. Because dispatchRequest() will
228// decide to do a sendResponse() aligning with the request parameter passed in.
229Q((this as any)._debugAdapter.attach(attachArguments, request.seq))
2d876061Ruslan Bikkinin8 years ago230.then((responseBody) => {
231const response: DebugProtocol.Response = new Response(request);
232response.body = responseBody;
233this.sendResponse(response);
234});
e45838cbVladimir Kotikov9 years ago235});
236
237return this.appWorker.start();
238})
239.catch(error => this.bailOut(error.message));
240});
241}
242
243/**
244* Logs error to user and finishes the debugging process.
245*/
246private bailOut(message: string): void {
d124bf0eYuri Skorokhodov7 years ago247logger.error(localize("CouldNotDebug", "Could not debug. {0}" , message));
0a68f8dbArtem Egorov8 years ago248this.sendEvent(new TerminatedEvent());
27710197Vladimir Kotikov8 years ago249}
e45838cbVladimir Kotikov9 years ago250};
251}
252
0a68f8dbArtem Egorov8 years ago253export function makeAdapter(debugAdapterClass: typeof ChromeDebugAdapter): typeof ChromeDebugAdapter {
e45838cbVladimir Kotikov9 years ago254return class extends debugAdapterClass {
4f7b3bc0Anna Kocheshkova8 years ago255public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
4b86d595Vladimir Kotikov9 years ago256// We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
e45838cbVladimir Kotikov9 years ago257// to set up breakpoints on initial pause event
0a68f8dbArtem Egorov8 years ago258(this as any)._attachMode = false;
e45838cbVladimir Kotikov9 years ago259return super.doAttach(port, targetUrl, address, timeout);
260}
639a73d7Artem Egorov7 years ago261
262public async terminate(args: DebugProtocol.TerminatedEvent) {
263return this.disconnect({
264terminateDebuggee: true,
265});
266}
e45838cbVladimir Kotikov9 years ago267};
268}
269
270/**
271* Parses settings.json file for workspace root property
272*/
273function getProjectRoot(args: any): string {
274try {
18ea7d15Yuri Skorokhodov7 years ago275let vsCodeRoot = args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
e45838cbVladimir Kotikov9 years ago276let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
277let settingsContent = fs.readFileSync(settingsPath, "utf8");
278settingsContent = stripJsonComments(settingsContent);
279let parsedSettings = JSON.parse(settingsContent);
9a364375Serge Svekolnikov8 years ago280let projectRootPath = parsedSettings["react-native-tools.projectRoot"] || parsedSettings["react-native-tools"].projectRoot;
e45838cbVladimir Kotikov9 years ago281return path.resolve(vsCodeRoot, projectRootPath);
282} catch (e) {
18ea7d15Yuri Skorokhodov7 years ago283return args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
e45838cbVladimir Kotikov9 years ago284}
285}