mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Prepack Debugger+Nuclide integration (#2116)
Summary: Release Notes: Fixed inconsistencies in debugger+Nuclide launch process (handshake ordering). Also implemented stopping on compiler diagnostics while using the debugger in Nuclide. Demo Video: https://www.dropbox.com/s/4swkc186fjw74ol/nuclide-integration-demo.mov?dl=0 Summary of Handshake Process with Nuclide: https://fb.quip.com/0uQCAtkuPAHF Closes https://github.com/facebook/prepack/pull/2116 Differential Revision: D8456201 Pulled By: caiismyname fbshipit-source-id: 77277cfa0d595ef227336e8a377977911899e2c0
This commit is contained in:
parent
f2219fd33c
commit
03b7fd37ff
@ -34,7 +34,7 @@ export default function(
|
||||
// Presence of debugChannel indicates we wish to use debugger.
|
||||
if (debugChannel) {
|
||||
invariant(debugChannel.debuggerIsAttached(), "Debugger intends to be used but is not attached.");
|
||||
invariant(opts.debuggerConfigArgs !== undefined, "Debugger intends to be used but does not have launch arguments.");
|
||||
invariant(opts.debuggerConfigArgs !== undefined, "Debugger intends to be used but does not have config arguments.");
|
||||
r.debuggerInstance = new DebugServer(debugChannel, r, opts.debuggerConfigArgs);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ class PrepackDebugSession extends DebugSession {
|
||||
this.setDebuggerColumnsStartAt1(true);
|
||||
}
|
||||
_clientID: void | string;
|
||||
_adapterChannel: AdapterChannel;
|
||||
_adapterChannel: void | AdapterChannel;
|
||||
|
||||
_generateDebugFilePath(direction: "in" | "out") {
|
||||
let time = Date.now();
|
||||
@ -50,17 +50,21 @@ class PrepackDebugSession extends DebugSession {
|
||||
}
|
||||
|
||||
_registerMessageCallbacks() {
|
||||
this._adapterChannel.registerChannelEvent(DebugMessage.STOPPED_RESPONSE, (response: DebuggerResponse) => {
|
||||
this._ensureAdapterChannelCreated("registerMessageCallbacks");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
// Create local copy to ensure external functions don't modify the adapterChannel, satisfy flow.
|
||||
let localCopyAdapterChannel = this._adapterChannel;
|
||||
localCopyAdapterChannel.registerChannelEvent(DebugMessage.STOPPED_RESPONSE, (response: DebuggerResponse) => {
|
||||
let result = response.result;
|
||||
invariant(result.kind === "stopped");
|
||||
let message = `${result.reason}: ${result.filePath} ${result.line}:${result.column}`;
|
||||
// Append message if there exists one (for PP errors)
|
||||
// Append message if there exists one (for Prepack errors)
|
||||
if (result.message !== undefined) {
|
||||
message += `. ${result.message}`;
|
||||
}
|
||||
this.sendEvent(new StoppedEvent(message, DebuggerConstants.PREPACK_THREAD_ID));
|
||||
});
|
||||
this._adapterChannel.registerChannelEvent(DebugMessage.STEPINTO_RESPONSE, (response: DebuggerResponse) => {
|
||||
localCopyAdapterChannel.registerChannelEvent(DebugMessage.STEPINTO_RESPONSE, (response: DebuggerResponse) => {
|
||||
let result = response.result;
|
||||
invariant(result.kind === "stepInto");
|
||||
this.sendEvent(
|
||||
@ -78,10 +82,6 @@ class PrepackDebugSession extends DebugSession {
|
||||
*/
|
||||
// Override
|
||||
initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||
// Let the UI know that we can start accepting breakpoint requests.
|
||||
// The UI will end the configuration sequence by calling 'configurationDone' request.
|
||||
this.sendEvent(new InitializedEvent());
|
||||
|
||||
this._clientID = args.clientID;
|
||||
response.body = response.body || {};
|
||||
response.body.supportsConfigurationDoneRequest = true;
|
||||
@ -97,6 +97,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
): void {
|
||||
// initial handshake with UI is complete
|
||||
if (this._clientID !== DebuggerConstants.CLI_CLIENTID) {
|
||||
this._ensureAdapterChannelCreated("configurationDoneRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
// for all ui except the CLI, autosend the first run request
|
||||
this._adapterChannel.run(DebuggerConstants.DEFAULT_REQUEST_ID, (runResponse: DebuggerResponse) => {});
|
||||
}
|
||||
@ -107,8 +109,9 @@ class PrepackDebugSession extends DebugSession {
|
||||
launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
let inFilePath = this._generateDebugFilePath("in");
|
||||
let outFilePath = this._generateDebugFilePath("out");
|
||||
// set up the communication channel
|
||||
this._adapterChannel = new AdapterChannel(inFilePath, outFilePath);
|
||||
// Set up the communication channel to the debugger.
|
||||
let adapterChannel = new AdapterChannel(inFilePath, outFilePath);
|
||||
this._adapterChannel = adapterChannel;
|
||||
this._registerMessageCallbacks();
|
||||
let launchArgs: PrepackLaunchArguments = {
|
||||
kind: "launch",
|
||||
@ -126,9 +129,16 @@ class PrepackDebugSession extends DebugSession {
|
||||
process.exit();
|
||||
},
|
||||
};
|
||||
this._adapterChannel.launch(response.request_seq, launchArgs, (dbgResponse: DebuggerResponse) => {
|
||||
|
||||
adapterChannel.launch(response.request_seq, launchArgs, (dbgResponse: DebuggerResponse) => {
|
||||
this.sendResponse(response);
|
||||
});
|
||||
|
||||
// Important: InitializedEvent indicates to the protocol that further requests (e.g. breakpoints, execution control)
|
||||
// are ready to be received. Prepack debugger is not ready to receive these requests until the Adapter Channel
|
||||
// has been created and Prepack has been launched. Thus, the InitializedEvent is sent after Prepack launch and
|
||||
// the creation of the Adapter Channel.
|
||||
this.sendEvent(new InitializedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,6 +147,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
// Override
|
||||
continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
||||
// send a Run request to Prepack and try to send the next request
|
||||
this._ensureAdapterChannelCreated("continueRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.run(response.request_seq, (dbgResponse: DebuggerResponse) => {
|
||||
this.sendResponse(response);
|
||||
});
|
||||
@ -165,6 +177,9 @@ class PrepackDebugSession extends DebugSession {
|
||||
};
|
||||
breakpointInfos.push(breakpointInfo);
|
||||
}
|
||||
|
||||
this._ensureAdapterChannelCreated("setBreakPointsRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.setBreakpoints(response.request_seq, breakpointInfos, (dbgResponse: DebuggerResponse) => {
|
||||
let result = dbgResponse.result;
|
||||
invariant(result.kind === "breakpoint-add");
|
||||
@ -190,6 +205,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
||||
this._ensureAdapterChannelCreated("stackTraceRequest");
|
||||
invariant(this._adapterChannel !== undefined);
|
||||
this._adapterChannel.getStackFrames(response.request_seq, (dbgResponse: DebuggerResponse) => {
|
||||
let result = dbgResponse.result;
|
||||
invariant(result.kind === "stackframe");
|
||||
@ -230,6 +247,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||
this._ensureAdapterChannelCreated("scopesRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.getScopes(response.request_seq, args.frameId, (dbgResponse: DebuggerResponse) => {
|
||||
let result = dbgResponse.result;
|
||||
invariant(result.kind === "scopes");
|
||||
@ -252,6 +271,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
|
||||
this._ensureAdapterChannelCreated("variablesRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.getVariables(
|
||||
response.request_seq,
|
||||
args.variablesReference,
|
||||
@ -278,6 +299,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void {
|
||||
this._ensureAdapterChannelCreated("stepInRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.stepInto(response.request_seq, (dbgResponse: DebuggerResponse) => {
|
||||
this.sendResponse(response);
|
||||
});
|
||||
@ -285,6 +308,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||
this._ensureAdapterChannelCreated("nextRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.stepOver(response.request_seq, (dbgResponse: DebuggerResponse) => {
|
||||
this.sendResponse(response);
|
||||
});
|
||||
@ -292,6 +317,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void {
|
||||
this._ensureAdapterChannelCreated("stepOutRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.stepOut(response.request_seq, (dbgResponse: DebuggerResponse) => {
|
||||
this.sendResponse(response);
|
||||
});
|
||||
@ -299,6 +326,8 @@ class PrepackDebugSession extends DebugSession {
|
||||
|
||||
// Override
|
||||
evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
||||
this._ensureAdapterChannelCreated("evaluateRequest");
|
||||
invariant(this._adapterChannel !== undefined, "Adapter Channel used before it was created, in debugger.");
|
||||
this._adapterChannel.evaluate(
|
||||
response.request_seq,
|
||||
args.frameId,
|
||||
@ -315,6 +344,15 @@ class PrepackDebugSession extends DebugSession {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_ensureAdapterChannelCreated(callingRequest: string) {
|
||||
// All responses that involve the Adapter Channel should only be invoked
|
||||
// after the channel has been created. If this ordering is perturbed,
|
||||
// there was likely a change in the protocol implementation by Nuclide.
|
||||
if (this._adapterChannel !== undefined) {
|
||||
throw Error(`Adapter Channel in Debugger is being used before it has been created. Caused by ${callingRequest}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugSession.run(PrepackDebugSession);
|
||||
|
@ -524,8 +524,6 @@ export class UISession {
|
||||
clientID: DebuggerConstants.CLI_CLIENTID,
|
||||
// a unique name for each adapter
|
||||
adapterID: "Prepack-Debugger-Adapter",
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
supportsVariableType: true,
|
||||
supportsVariablePaging: false,
|
||||
supportsRunInTerminalRequest: false,
|
||||
|
@ -56,7 +56,9 @@ export class BreakpointManager {
|
||||
breakpointMap = new PerFileBreakpointMap(bp.filePath);
|
||||
this._breakpointMaps.set(bp.filePath, breakpointMap);
|
||||
}
|
||||
breakpointMap.addBreakpoint(bp.line, bp.column);
|
||||
// Nuclide doesn't support column debugging, so set every breakpoint
|
||||
// to column 0 for consistency.
|
||||
breakpointMap.addBreakpoint(bp.line, 0);
|
||||
}
|
||||
|
||||
getBreakpoint(filePath: string, lineNum: number, columnNum: number = 0): void | Breakpoint {
|
||||
|
@ -10,7 +10,8 @@
|
||||
/* @flow strict-local */
|
||||
|
||||
import { BreakpointManager } from "./BreakpointManager.js";
|
||||
import type { BabelNode, BabelNodeSourceLocation } from "babel-types";
|
||||
import { BabelNode } from "babel-types";
|
||||
import type { BabelNodeSourceLocation } from "babel-types";
|
||||
import invariant from "../common/invariant.js";
|
||||
import type { DebugChannel } from "./channel/DebugChannel.js";
|
||||
import { DebugMessage } from "./../common/channel/DebugMessage.js";
|
||||
@ -50,8 +51,8 @@ export class DebugServer {
|
||||
this._variableManager = new VariableManager(realm);
|
||||
this._stepManager = new SteppingManager(this._realm, /* default discard old steppers */ false);
|
||||
this._stopEventManager = new StopEventManager();
|
||||
this.waitForRun(undefined);
|
||||
this._diagnosticSeverity = configArgs.diagnosticSeverity || "FatalError";
|
||||
this.waitForRun(undefined);
|
||||
}
|
||||
// the collection of breakpoints
|
||||
_breakpointManager: BreakpointManager;
|
||||
@ -69,12 +70,12 @@ export class DebugServer {
|
||||
/* ast: the current ast node we are stopped on
|
||||
/* reason: the reason the debuggee is stopping
|
||||
*/
|
||||
waitForRun(ast: void | BabelNode) {
|
||||
waitForRun(loc: void | BabelNodeSourceLocation) {
|
||||
let keepRunning = false;
|
||||
let request;
|
||||
while (!keepRunning) {
|
||||
request = this._channel.readIn();
|
||||
keepRunning = this.processDebuggerCommand(request, ast);
|
||||
keepRunning = this.processDebuggerCommand(request, loc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,14 +89,15 @@ export class DebugServer {
|
||||
if (reason) {
|
||||
invariant(ast.loc && ast.loc.source);
|
||||
this._channel.sendStoppedResponse(reason, ast.loc.source, ast.loc.start.line, ast.loc.start.column);
|
||||
this.waitForRun(ast);
|
||||
invariant(ast.loc && ast.loc !== null);
|
||||
this.waitForRun(ast.loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process a command from a debugger. Returns whether Prepack should unblock
|
||||
// if it is blocked
|
||||
processDebuggerCommand(request: DebuggerRequest, ast: void | BabelNode) {
|
||||
processDebuggerCommand(request: DebuggerRequest, loc: void | BabelNodeSourceLocation) {
|
||||
let requestID = request.id;
|
||||
let command = request.command;
|
||||
let args = request.arguments;
|
||||
@ -126,7 +128,7 @@ export class DebugServer {
|
||||
return true;
|
||||
case DebugMessage.STACKFRAMES_COMMAND:
|
||||
invariant(args.kind === "stackframe");
|
||||
this.processStackframesCommand(requestID, args, ast);
|
||||
this.processStackframesCommand(requestID, args, loc);
|
||||
break;
|
||||
case DebugMessage.SCOPES_COMMAND:
|
||||
invariant(args.kind === "scopes");
|
||||
@ -137,18 +139,18 @@ export class DebugServer {
|
||||
this.processVariablesCommand(requestID, args);
|
||||
break;
|
||||
case DebugMessage.STEPINTO_COMMAND:
|
||||
invariant(ast !== undefined);
|
||||
this._stepManager.processStepCommand("in", ast);
|
||||
invariant(loc !== undefined);
|
||||
this._stepManager.processStepCommand("in", loc);
|
||||
this._onDebuggeeResume();
|
||||
return true;
|
||||
case DebugMessage.STEPOVER_COMMAND:
|
||||
invariant(ast !== undefined);
|
||||
this._stepManager.processStepCommand("over", ast);
|
||||
invariant(loc !== undefined);
|
||||
this._stepManager.processStepCommand("over", loc);
|
||||
this._onDebuggeeResume();
|
||||
return true;
|
||||
case DebugMessage.STEPOUT_COMMAND:
|
||||
invariant(ast !== undefined);
|
||||
this._stepManager.processStepCommand("out", ast);
|
||||
invariant(loc !== undefined);
|
||||
this._stepManager.processStepCommand("out", loc);
|
||||
this._onDebuggeeResume();
|
||||
return true;
|
||||
case DebugMessage.EVALUATE_COMMAND:
|
||||
@ -161,9 +163,9 @@ export class DebugServer {
|
||||
return false;
|
||||
}
|
||||
|
||||
processStackframesCommand(requestID: number, args: StackframeArguments, ast: void | BabelNode) {
|
||||
processStackframesCommand(requestID: number, args: StackframeArguments, astLoc: void | BabelNodeSourceLocation) {
|
||||
let frameInfos: Array<Stackframe> = [];
|
||||
let loc = this._getFrameLocation(ast ? ast.loc : null);
|
||||
let loc = this._getFrameLocation(astLoc ? astLoc : null);
|
||||
let fileName = loc.fileName;
|
||||
let line = loc.line;
|
||||
let column = loc.column;
|
||||
@ -303,6 +305,7 @@ export class DebugServer {
|
||||
*/
|
||||
handlePrepackError(diagnostic: CompilerDiagnostic) {
|
||||
invariant(diagnostic.location && diagnostic.location.source);
|
||||
// The following constructs the message and stop-instruction that is sent to the UI to actually stop the execution.
|
||||
let location = diagnostic.location;
|
||||
let message = `${diagnostic.severity} ${diagnostic.errorCode}: ${diagnostic.message}`;
|
||||
this._channel.sendStoppedResponse(
|
||||
@ -312,10 +315,10 @@ export class DebugServer {
|
||||
location.start.column,
|
||||
message
|
||||
);
|
||||
// No ast parameter b/c you cannot stepInto/Over a line that's causing an exception
|
||||
this.waitForRun();
|
||||
}
|
||||
|
||||
// The AST Node's location is needed to satisfy the subsequent stackTrace request.
|
||||
this.waitForRun(location);
|
||||
}
|
||||
// Return whether the debugger should stop on a CompilerDiagnostic of a given severity.
|
||||
shouldStopForSeverity(severity: Severity): boolean {
|
||||
switch (this._diagnosticSeverity) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/* @flow strict-local */
|
||||
|
||||
import { BabelNode } from "babel-types";
|
||||
import { BabelNodeSourceLocation } from "babel-types";
|
||||
import invariant from "./../common/invariant.js";
|
||||
import { Stepper, StepIntoStepper, StepOverStepper, StepOutStepper } from "./Stepper.js";
|
||||
import type { Realm } from "./../../realm.js";
|
||||
@ -26,45 +26,45 @@ export class SteppingManager {
|
||||
_keepOldSteppers: boolean;
|
||||
_steppers: Array<Stepper>;
|
||||
|
||||
processStepCommand(kind: "in" | "over" | "out", currentNode: BabelNode) {
|
||||
processStepCommand(kind: "in" | "over" | "out", currentNodeLocation: BabelNodeSourceLocation) {
|
||||
if (kind === "in") {
|
||||
this._processStepIn(currentNode);
|
||||
this._processStepIn(currentNodeLocation);
|
||||
} else if (kind === "over") {
|
||||
this._processStepOver(currentNode);
|
||||
this._processStepOver(currentNodeLocation);
|
||||
} else if (kind === "out") {
|
||||
this._processStepOut(currentNode);
|
||||
this._processStepOut(currentNodeLocation);
|
||||
} else {
|
||||
invariant(false, `Invalid step type: ${kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
_processStepIn(ast: BabelNode) {
|
||||
invariant(ast.loc && ast.loc.source);
|
||||
_processStepIn(loc: BabelNodeSourceLocation) {
|
||||
invariant(loc && loc.source);
|
||||
if (!this._keepOldSteppers) {
|
||||
this._steppers = [];
|
||||
}
|
||||
this._steppers.push(
|
||||
new StepIntoStepper(ast.loc.source, ast.loc.start.line, ast.loc.start.column, this._realm.contextStack.length)
|
||||
new StepIntoStepper(loc.source, loc.start.line, loc.start.column, this._realm.contextStack.length)
|
||||
);
|
||||
}
|
||||
|
||||
_processStepOver(ast: BabelNode) {
|
||||
invariant(ast.loc && ast.loc.source);
|
||||
_processStepOver(loc: BabelNodeSourceLocation) {
|
||||
invariant(loc && loc.source);
|
||||
if (!this._keepOldSteppers) {
|
||||
this._steppers = [];
|
||||
}
|
||||
this._steppers.push(
|
||||
new StepOverStepper(ast.loc.source, ast.loc.start.line, ast.loc.start.column, this._realm.contextStack.length)
|
||||
new StepOverStepper(loc.source, loc.start.line, loc.start.column, this._realm.contextStack.length)
|
||||
);
|
||||
}
|
||||
|
||||
_processStepOut(ast: BabelNode) {
|
||||
invariant(ast.loc && ast.loc.source);
|
||||
_processStepOut(loc: BabelNodeSourceLocation) {
|
||||
invariant(loc && loc.source);
|
||||
if (!this._keepOldSteppers) {
|
||||
this._steppers = [];
|
||||
}
|
||||
this._steppers.push(
|
||||
new StepOutStepper(ast.loc.source, ast.loc.start.line, ast.loc.start.column, this._realm.contextStack.length)
|
||||
new StepOutStepper(loc.source, loc.start.line, loc.start.column, this._realm.contextStack.length)
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user