From 5437f7cfb4cbc92be2d3f31296f01967a29f517f Mon Sep 17 00:00:00 2001 From: Paul Code Johnston Date: Sun, 1 Nov 2020 20:07:55 -0700 Subject: [PATCH 1/4] Bump version to 0.6.6 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be19c28f..83225363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 0.6.6 (PENDING) + +- Auto-renew license if within 1 week of expiration. + ## 0.6.5 (Sun Nov 1 2020) - Update bzl to include codesearch fixes/improvements. diff --git a/package-lock.json b/package-lock.json index 170822d9..3f68deba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bazel-stack-vscode", - "version": "0.6.5", + "version": "0.6.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 04284ece..c9c95d1e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bazel-stack-vscode", "displayName": "bazel-stack-vscode", "description": "Bazel Support for Visual Studio Code", - "version": "0.6.5", + "version": "0.6.6", "publisher": "StackBuild", "license": "Apache-2.0", "icon": "stackb-full.png", From 3c191b0f6c02be12493c95209e13b519e9af6fe1 Mon Sep 17 00:00:00 2001 From: Paul Code Johnston Date: Sun, 1 Nov 2020 20:12:38 -0700 Subject: [PATCH 2/4] Refactor GrpcClient into separate file --- src/bzl/bzlclient.ts | 58 +----------------------------------------- src/bzl/grpcclient.ts | 59 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 57 deletions(-) create mode 100644 src/bzl/grpcclient.ts diff --git a/src/bzl/bzlclient.ts b/src/bzl/bzlclient.ts index 0b7d19f0..d9f394c8 100644 --- a/src/bzl/bzlclient.ts +++ b/src/bzl/bzlclient.ts @@ -37,63 +37,7 @@ import { ProtoGrpcType as BzlProtoGrpcType } from '../proto/bzl'; import { ProtoGrpcType as CodesearchProtoGrpcType } from '../proto/codesearch'; import { CodeSearchResult } from '../proto/livegrep/CodeSearchResult'; import { ButtonName } from './constants'; - -export interface Closeable { - close(): void; -} - -export class GRPCClient implements vscode.Disposable { - private disposables: vscode.Disposable[] = []; - private closeables: Closeable[] = []; - - constructor( - readonly address: string, - protected defaultDeadlineSeconds = 30, - ) { - } - - protected getCredentials(address: string): grpc.ChannelCredentials { - if (address.endsWith(':443')) { - return grpc.credentials.createSsl(); - } - return grpc.credentials.createInsecure(); - } - - protected getDeadline(seconds?: number): grpc.Deadline { - const deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() - + (seconds || this.defaultDeadlineSeconds)); - return deadline; - } - - protected handleError(err: grpc.ServiceError): grpc.ServiceError { - if (err.code === grpc.status.UNAVAILABLE) { - return this.handleErrorUnavailable(err); - } - return err; - } - - protected handleErrorUnavailable(err: grpc.ServiceError): grpc.ServiceError { - return err; - } - - protected add(client: T): T { - this.closeables.push(client); - return client; - } - - public dispose() { - for (const closeable of this.closeables) { - closeable.close(); - } - this.closeables.length = 0; - for (const disposable of this.disposables) { - disposable.dispose(); - } - this.disposables.length = 0; - } - -} +import { GRPCClient } from './grpcclient'; export interface BzlCodesearch { createScope(request: CreateScopeRequest, callback: (response: CreateScopeResponse) => void): Promise; diff --git a/src/bzl/grpcclient.ts b/src/bzl/grpcclient.ts new file mode 100644 index 00000000..433a5a4e --- /dev/null +++ b/src/bzl/grpcclient.ts @@ -0,0 +1,59 @@ +import * as grpc from '@grpc/grpc-js'; +import * as vscode from 'vscode'; + +export interface Closeable { + close(): void; +} + +export class GRPCClient implements vscode.Disposable { + private disposables: vscode.Disposable[] = []; + private closeables: Closeable[] = []; + + constructor( + readonly address: string, + protected defaultDeadlineSeconds = 30, + ) { + } + + protected getCredentials(address: string): grpc.ChannelCredentials { + if (address.endsWith(':443')) { + return grpc.credentials.createSsl(); + } + return grpc.credentials.createInsecure(); + } + + protected getDeadline(seconds?: number): grpc.Deadline { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + + (seconds || this.defaultDeadlineSeconds)); + return deadline; + } + + protected handleError(err: grpc.ServiceError): grpc.ServiceError { + if (err.code === grpc.status.UNAVAILABLE) { + return this.handleErrorUnavailable(err); + } + return err; + } + + protected handleErrorUnavailable(err: grpc.ServiceError): grpc.ServiceError { + return err; + } + + protected add(client: T): T { + this.closeables.push(client); + return client; + } + + public dispose() { + for (const closeable of this.closeables) { + closeable.close(); + } + this.closeables.length = 0; + for (const disposable of this.disposables) { + disposable.dispose(); + } + this.disposables.length = 0; + } + +} From 9fdc86bcb585bb69f259fc110af99583d23c2fb6 Mon Sep 17 00:00:00 2001 From: Paul Code Johnston Date: Mon, 2 Nov 2020 23:04:26 -0700 Subject: [PATCH 3/4] Add starlark_debugging.proto --- proto/starlark_debugging.proto | 337 ++++++++++++++++++ src/proto/starlark_debugging.ts | 40 +++ src/proto/starlark_debugging/Breakpoint.ts | 45 +++ .../ContinueExecutionRequest.ts | 38 ++ .../ContinueExecutionResponse.ts | 14 + src/proto/starlark_debugging/DebugEvent.ts | 71 ++++ src/proto/starlark_debugging/DebugRequest.ts | 58 +++ src/proto/starlark_debugging/Error.ts | 24 ++ .../starlark_debugging/EvaluateRequest.ts | 33 ++ .../starlark_debugging/EvaluateResponse.ts | 23 ++ src/proto/starlark_debugging/Frame.ts | 42 +++ .../starlark_debugging/GetChildrenRequest.ts | 39 ++ .../starlark_debugging/GetChildrenResponse.ts | 17 + .../starlark_debugging/ListFramesRequest.ts | 23 ++ .../starlark_debugging/ListFramesResponse.ts | 25 ++ src/proto/starlark_debugging/Location.ts | 40 +++ src/proto/starlark_debugging/PauseReason.ts | 36 ++ .../starlark_debugging/PauseThreadRequest.ts | 31 ++ .../starlark_debugging/PauseThreadResponse.ts | 18 + src/proto/starlark_debugging/PausedThread.ts | 58 +++ src/proto/starlark_debugging/Scope.ts | 31 ++ .../SetBreakpointsRequest.ts | 25 ++ .../SetBreakpointsResponse.ts | 14 + .../StartDebuggingRequest.ts | 18 + .../StartDebuggingResponse.ts | 14 + src/proto/starlark_debugging/Stepping.ts | 27 ++ .../ThreadContinuedEvent.ts | 23 ++ .../starlark_debugging/ThreadPausedEvent.ts | 23 ++ src/proto/starlark_debugging/Value.ts | 83 +++++ 29 files changed, 1270 insertions(+) create mode 100644 proto/starlark_debugging.proto create mode 100644 src/proto/starlark_debugging.ts create mode 100644 src/proto/starlark_debugging/Breakpoint.ts create mode 100644 src/proto/starlark_debugging/ContinueExecutionRequest.ts create mode 100644 src/proto/starlark_debugging/ContinueExecutionResponse.ts create mode 100644 src/proto/starlark_debugging/DebugEvent.ts create mode 100644 src/proto/starlark_debugging/DebugRequest.ts create mode 100644 src/proto/starlark_debugging/Error.ts create mode 100644 src/proto/starlark_debugging/EvaluateRequest.ts create mode 100644 src/proto/starlark_debugging/EvaluateResponse.ts create mode 100644 src/proto/starlark_debugging/Frame.ts create mode 100644 src/proto/starlark_debugging/GetChildrenRequest.ts create mode 100644 src/proto/starlark_debugging/GetChildrenResponse.ts create mode 100644 src/proto/starlark_debugging/ListFramesRequest.ts create mode 100644 src/proto/starlark_debugging/ListFramesResponse.ts create mode 100644 src/proto/starlark_debugging/Location.ts create mode 100644 src/proto/starlark_debugging/PauseReason.ts create mode 100644 src/proto/starlark_debugging/PauseThreadRequest.ts create mode 100644 src/proto/starlark_debugging/PauseThreadResponse.ts create mode 100644 src/proto/starlark_debugging/PausedThread.ts create mode 100644 src/proto/starlark_debugging/Scope.ts create mode 100644 src/proto/starlark_debugging/SetBreakpointsRequest.ts create mode 100644 src/proto/starlark_debugging/SetBreakpointsResponse.ts create mode 100644 src/proto/starlark_debugging/StartDebuggingRequest.ts create mode 100644 src/proto/starlark_debugging/StartDebuggingResponse.ts create mode 100644 src/proto/starlark_debugging/Stepping.ts create mode 100644 src/proto/starlark_debugging/ThreadContinuedEvent.ts create mode 100644 src/proto/starlark_debugging/ThreadPausedEvent.ts create mode 100644 src/proto/starlark_debugging/Value.ts diff --git a/proto/starlark_debugging.proto b/proto/starlark_debugging.proto new file mode 100644 index 00000000..534b6c29 --- /dev/null +++ b/proto/starlark_debugging.proto @@ -0,0 +1,337 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package starlark_debugging; + +option java_package = "com.google.devtools.build.lib.starlarkdebugging"; +option java_outer_classname = "StarlarkDebuggingProtos"; + +// A request sent by the debug client to the debug server. +message DebugRequest { + // A number (intended to be sequentially generated by the client) that + // identifies the request. The response sent by the server will contain the + // same sequence number so that the client can synchronize its activity if + // desired. + int64 sequence_number = 1; + + // The payload describes the type of the request and its arguments, if any. + oneof payload { + SetBreakpointsRequest set_breakpoints = 101; + ContinueExecutionRequest continue_execution = 102; + EvaluateRequest evaluate = 103; + ListFramesRequest list_frames = 104; + StartDebuggingRequest start_debugging = 105; + PauseThreadRequest pause_thread = 106; + GetChildrenRequest get_children = 107; + } +} + +// A request to update the breakpoints used by the debug server. +message SetBreakpointsRequest { + // The breakpoints that describe where the debug server should pause + // evaluation. + repeated Breakpoint breakpoint = 1; +} + +// A request to continue execution on a paused or stepping thread. (A stepping +// thread is a thread that is running as the result of a previous +// ContinueExecutionRequest with non-NONE stepping.) +// +// A paused thread will be resumed with the given stepping, unless thread_id is +// 0. A stepping thread will continue to run with its stepping condition +// removed, as if it were already paused. +message ContinueExecutionRequest { + // The identifier of the thread to continue. The thread must be paused or + // stepping. + // + // If this field is not set (i.e., zero), then all threads will be continued + // without stepping; the stepping field in this message will be ignored. This + // is typically used when the debugger disconnects from the server. + + int64 thread_id = 1; + + // Describes the stepping behavior to use when continuing execution. + Stepping stepping = 2; +} + +// A request to evaluate a Starlark statement in a thread's current environment. +message EvaluateRequest { + // The identifier of the thread in whose execution context the expression + // should be evaluated. + int64 thread_id = 1; + + // The Starlark statement to evaluate. + string statement = 2; +} + +// A request to list the stack frames of a thread. +message ListFramesRequest { + // The identifier of the thread whose stack frames should be listed. + int64 thread_id = 1; +} + +// A request to begin the debugging session. Starlark execution will block until +// this request is made, to allow initial setup after the connection is +// established (e.g. setting breakpoints). +message StartDebuggingRequest {} + +// A request to pause execution of a thread, or all threads. +message PauseThreadRequest { + // The identifier of the thread to be paused. + // + // If not set (i.e. zero), all current Starlark threads will be paused, and + // until a ContinueExecutionRequest is sent, any future Starlark threads will + // also start off paused. + int64 thread_id = 1; +} + +// A request to list the children of a previously-communicated Value, such as +// its elements (for a list or dictionary), its fields (for a struct), and so +// forth. +message GetChildrenRequest { + // The identifier of the relevant thread. + int64 thread_id = 1; + + // The identifier of the value for which children are being requested. If the + // value has no children, an empty list will be returned in + // GetChildrenResponse. + int64 value_id = 2; +} + +// There are two kinds of events: "responses", which correspond to a +// DebugRequest sent by the client, and other asynchronous events that may be +// sent by the server to notify the client of activity in the Starlark code +// being debugged. +message DebugEvent { + // If non-zero, this event is a response to a DebugRequest with the same + // sequence number. + int64 sequence_number = 1; + + // The payload describes the type of event and any additional information + // about the event. + oneof payload { + Error error = 99; + + SetBreakpointsResponse set_breakpoints = 101; + ContinueExecutionResponse continue_execution = 102; + EvaluateResponse evaluate = 103; + ListFramesResponse list_frames = 104; + StartDebuggingResponse start_debugging = 105; + PauseThreadResponse pause_thread = 106; + GetChildrenResponse get_children = 107; + + ThreadPausedEvent thread_paused = 1001; + ThreadContinuedEvent thread_continued = 1002; + } +} + +// A response that indicates that an error occurred while handling a debugging +// request. +message Error { + // A message describing the error that occurred. + string message = 1; +} + +// The response to a SetBreakpointsRequest. +message SetBreakpointsResponse {} + +// The response to a ContinueExecutionRequest. +message ContinueExecutionResponse {} + +// The response to an EvaluateRequest. +message EvaluateResponse { + // The result of evaluating a statement. + Value result = 1; +} + +// The response to a ListFramesRequest. +message ListFramesResponse { + // The list of stack frames. The first element in the list represents the + // topmost frame (that is, the current innermost function). + repeated Frame frame = 1; +} + +// The response to a StartDebuggingRequest. +message StartDebuggingResponse {} + +// The response to a PauseThreadRequest. This is an acknowledgement that the +// request was received. Actual pausing of individual threads happens +// asynchronously, and will be communicated via ThreadPausedEvent(s). +message PauseThreadResponse {} + +// The response to a GetChildrenRequest. +message GetChildrenResponse { + repeated Value children = 1; +} + +// An event indicating that a thread was paused during execution. +message ThreadPausedEvent { + // The thread that was paused. + PausedThread thread = 1; +} + +// An event indicating that a thread has continued execution after being paused. +message ThreadContinuedEvent { + // The identifier of the thread that continued executing. + int64 thread_id = 1; +} + +// A location where the debug server will pause execution. +message Breakpoint { + oneof condition { + // A breakpoint that is triggered when a particular line is reached. + // Column index will be ignored for breakpoints. The debugger only supports + // one breakpoint per line. If multiple breakpoints are supplied for a + // single line, only the last such breakpoint is accepted. + Location location = 1; + } + // An optional condition for the breakpoint. When present, the breakpoint will + // be triggered iff both the primary condition holds and this expression + // evaluates to True. It is unspecified how many times this expression will be + // evaluated, so it should be free of side-effects. + string expression = 2; +} + +// A single frame in a thread's stack trace. +message Frame { + // The name of the function that this frame represents. + string function_name = 1; + + // The scopes that contain value bindings accessible in this frame. + repeated Scope scope = 2; + + // The source location where the frame is currently paused. May not be set in + // some situations. + Location location = 3; +} + +// A location in Starlark source code. +message Location { + // The path of the Starlark source file. + string path = 1; + + // A 1-indexed line number in the file denoted by path. + uint32 line_number = 2; + + // A 1-indexed column number in the file denoted by path. 0 (/unset) indicates + // column number is unknown or irrelevant. + uint32 column_number = 3; +} + +// A scope that contains value bindings accessible in a frame. +message Scope { + // A human-readable name of the scope, such as "global" or "local". + string name = 1; + + // The variable bindings that are defined in this scope. + repeated Value binding = 2; +} + +// Describes the stepping behavior that should occur when execution of a thread +// is continued. +enum Stepping { + // Do not step; continue execution until it completes or is paused for some + // other reason (such as hitting another breakpoint). + NONE = 0; + + // If the thread is paused on a statement that contains a function call, + // step into that function. Otherwise, this is the same as OVER. + INTO = 1; + + // Step over the next statement and any functions that it may call. + OVER = 2; + + // Continue execution until the current function has been exited and then + // pause. + OUT = 3; +} + +// Information about a paused Starlark thread. +message PausedThread { + // The identifier of the thread. + int64 id = 1; + + // A descriptive name for the thread that can be displayed in the debugger's + // UI. + string name = 2; + + PauseReason pause_reason = 3; + + // The location in Starlark code of the next statement or expression that will + // be executed. + Location location = 4; + + // An error that occurred while evaluating a breakpoint condition. Present if + // and only if pause_reason is CONDITIONAL_BREAKPOINT_ERROR. + Error conditional_breakpoint_error = 5; +} + +// The reason why a thread was paused. +enum PauseReason { + // The debug server hasn't set any reason. + UNSET = 0; + + // The stepping condition in a ContinueExecutionRequest was hit. + STEPPING = 1; + + // A PauseThreadRequest was sent with thread_id=0. + ALL_THREADS_PAUSED = 2; + + // A PauseThreadRequest was sent with thread_id matching this thread. + PAUSE_THREAD_REQUEST = 3; + + // A breakpoint was hit. + HIT_BREAKPOINT = 4; + + // An error occurred while evaluating a breakpoint condition. + CONDITIONAL_BREAKPOINT_ERROR = 5; + + // Debugging just started, and a StartDebuggingRequest has not yet been + // received and processed. + INITIALIZING = 6; +} + +// The debugger representation of a Starlark value. +message Value { + // A label that describes this value's location or source in a value + // hierarchy. + // + // For example, in a stack frame, the label would be the name of the variable + // to which the value is bound. For a value that is an element of a list, its + // its label would be its subscript, such as "[4]". A value that is a field in + // a struct would use the field's name as its label, and so forth. + string label = 1; + + // A string description of the value. + string description = 2; + + // A string describing the type of the value. + // + // This field may be omitted if the value does not correspond to a "real" type + // as far as the debugging view is concerned; for example, dictionaries will + // be rendered as sequences of key/value pairs ("entries") but the entries + // themselves do not have a meaningful type with respect to our rendering. + string type = 3; + + // Will be false if the value is known to have no children. May sometimes be + // true if this isn't yet known, in which case GetChildrenResponse#children + // will be empty. + bool has_children = 4; + + // An identifier for this value, used to request its children. The same value + // may be known by multiple ids. Not set for values without children. + int64 id = 5; +} \ No newline at end of file diff --git a/src/proto/starlark_debugging.ts b/src/proto/starlark_debugging.ts new file mode 100644 index 00000000..4ae90ac1 --- /dev/null +++ b/src/proto/starlark_debugging.ts @@ -0,0 +1,40 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + starlark_debugging: { + Breakpoint: MessageTypeDefinition + ContinueExecutionRequest: MessageTypeDefinition + ContinueExecutionResponse: MessageTypeDefinition + DebugEvent: MessageTypeDefinition + DebugRequest: MessageTypeDefinition + Error: MessageTypeDefinition + EvaluateRequest: MessageTypeDefinition + EvaluateResponse: MessageTypeDefinition + Frame: MessageTypeDefinition + GetChildrenRequest: MessageTypeDefinition + GetChildrenResponse: MessageTypeDefinition + ListFramesRequest: MessageTypeDefinition + ListFramesResponse: MessageTypeDefinition + Location: MessageTypeDefinition + PauseReason: EnumTypeDefinition + PauseThreadRequest: MessageTypeDefinition + PauseThreadResponse: MessageTypeDefinition + PausedThread: MessageTypeDefinition + Scope: MessageTypeDefinition + SetBreakpointsRequest: MessageTypeDefinition + SetBreakpointsResponse: MessageTypeDefinition + StartDebuggingRequest: MessageTypeDefinition + StartDebuggingResponse: MessageTypeDefinition + Stepping: EnumTypeDefinition + ThreadContinuedEvent: MessageTypeDefinition + ThreadPausedEvent: MessageTypeDefinition + Value: MessageTypeDefinition + } +} + diff --git a/src/proto/starlark_debugging/Breakpoint.ts b/src/proto/starlark_debugging/Breakpoint.ts new file mode 100644 index 00000000..5314d5fe --- /dev/null +++ b/src/proto/starlark_debugging/Breakpoint.ts @@ -0,0 +1,45 @@ +// Original file: proto/starlark_debugging.proto + +import type { Location as _starlark_debugging_Location, Location__Output as _starlark_debugging_Location__Output } from '../starlark_debugging/Location'; + +/** + * A location where the debug server will pause execution. + */ +export interface Breakpoint { + /** + * A breakpoint that is triggered when a particular line is reached. + * Column index will be ignored for breakpoints. The debugger only supports + * one breakpoint per line. If multiple breakpoints are supplied for a + * single line, only the last such breakpoint is accepted. + */ + 'location'?: (_starlark_debugging_Location); + /** + * An optional condition for the breakpoint. When present, the breakpoint will + * be triggered iff both the primary condition holds and this expression + * evaluates to True. It is unspecified how many times this expression will be + * evaluated, so it should be free of side-effects. + */ + 'expression'?: (string); + 'condition'?: "location"; +} + +/** + * A location where the debug server will pause execution. + */ +export interface Breakpoint__Output { + /** + * A breakpoint that is triggered when a particular line is reached. + * Column index will be ignored for breakpoints. The debugger only supports + * one breakpoint per line. If multiple breakpoints are supplied for a + * single line, only the last such breakpoint is accepted. + */ + 'location'?: (_starlark_debugging_Location__Output); + /** + * An optional condition for the breakpoint. When present, the breakpoint will + * be triggered iff both the primary condition holds and this expression + * evaluates to True. It is unspecified how many times this expression will be + * evaluated, so it should be free of side-effects. + */ + 'expression': (string); + 'condition': "location"; +} diff --git a/src/proto/starlark_debugging/ContinueExecutionRequest.ts b/src/proto/starlark_debugging/ContinueExecutionRequest.ts new file mode 100644 index 00000000..5322fed9 --- /dev/null +++ b/src/proto/starlark_debugging/ContinueExecutionRequest.ts @@ -0,0 +1,38 @@ +// Original file: proto/starlark_debugging.proto + +import type { Stepping as _starlark_debugging_Stepping } from '../starlark_debugging/Stepping'; +import type { Long } from '@grpc/proto-loader'; + +/** + * A request to continue execution on a paused or stepping thread. (A stepping + * thread is a thread that is running as the result of a previous + * ContinueExecutionRequest with non-NONE stepping.) + * + * A paused thread will be resumed with the given stepping, unless thread_id is + * 0. A stepping thread will continue to run with its stepping condition + * removed, as if it were already paused. + */ +export interface ContinueExecutionRequest { + 'threadId'?: (number | string | Long); + /** + * Describes the stepping behavior to use when continuing execution. + */ + 'stepping'?: (_starlark_debugging_Stepping | keyof typeof _starlark_debugging_Stepping); +} + +/** + * A request to continue execution on a paused or stepping thread. (A stepping + * thread is a thread that is running as the result of a previous + * ContinueExecutionRequest with non-NONE stepping.) + * + * A paused thread will be resumed with the given stepping, unless thread_id is + * 0. A stepping thread will continue to run with its stepping condition + * removed, as if it were already paused. + */ +export interface ContinueExecutionRequest__Output { + 'threadId': (Long); + /** + * Describes the stepping behavior to use when continuing execution. + */ + 'stepping': (_starlark_debugging_Stepping); +} diff --git a/src/proto/starlark_debugging/ContinueExecutionResponse.ts b/src/proto/starlark_debugging/ContinueExecutionResponse.ts new file mode 100644 index 00000000..e4be8937 --- /dev/null +++ b/src/proto/starlark_debugging/ContinueExecutionResponse.ts @@ -0,0 +1,14 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * The response to a ContinueExecutionRequest. + */ +export interface ContinueExecutionResponse { +} + +/** + * The response to a ContinueExecutionRequest. + */ +export interface ContinueExecutionResponse__Output { +} diff --git a/src/proto/starlark_debugging/DebugEvent.ts b/src/proto/starlark_debugging/DebugEvent.ts new file mode 100644 index 00000000..7821e753 --- /dev/null +++ b/src/proto/starlark_debugging/DebugEvent.ts @@ -0,0 +1,71 @@ +// Original file: proto/starlark_debugging.proto + +import type { Error as _starlark_debugging_Error, Error__Output as _starlark_debugging_Error__Output } from '../starlark_debugging/Error'; +import type { SetBreakpointsResponse as _starlark_debugging_SetBreakpointsResponse, SetBreakpointsResponse__Output as _starlark_debugging_SetBreakpointsResponse__Output } from '../starlark_debugging/SetBreakpointsResponse'; +import type { ContinueExecutionResponse as _starlark_debugging_ContinueExecutionResponse, ContinueExecutionResponse__Output as _starlark_debugging_ContinueExecutionResponse__Output } from '../starlark_debugging/ContinueExecutionResponse'; +import type { EvaluateResponse as _starlark_debugging_EvaluateResponse, EvaluateResponse__Output as _starlark_debugging_EvaluateResponse__Output } from '../starlark_debugging/EvaluateResponse'; +import type { ListFramesResponse as _starlark_debugging_ListFramesResponse, ListFramesResponse__Output as _starlark_debugging_ListFramesResponse__Output } from '../starlark_debugging/ListFramesResponse'; +import type { StartDebuggingResponse as _starlark_debugging_StartDebuggingResponse, StartDebuggingResponse__Output as _starlark_debugging_StartDebuggingResponse__Output } from '../starlark_debugging/StartDebuggingResponse'; +import type { PauseThreadResponse as _starlark_debugging_PauseThreadResponse, PauseThreadResponse__Output as _starlark_debugging_PauseThreadResponse__Output } from '../starlark_debugging/PauseThreadResponse'; +import type { GetChildrenResponse as _starlark_debugging_GetChildrenResponse, GetChildrenResponse__Output as _starlark_debugging_GetChildrenResponse__Output } from '../starlark_debugging/GetChildrenResponse'; +import type { ThreadPausedEvent as _starlark_debugging_ThreadPausedEvent, ThreadPausedEvent__Output as _starlark_debugging_ThreadPausedEvent__Output } from '../starlark_debugging/ThreadPausedEvent'; +import type { ThreadContinuedEvent as _starlark_debugging_ThreadContinuedEvent, ThreadContinuedEvent__Output as _starlark_debugging_ThreadContinuedEvent__Output } from '../starlark_debugging/ThreadContinuedEvent'; +import type { Long } from '@grpc/proto-loader'; + +/** + * There are two kinds of events: "responses", which correspond to a + * DebugRequest sent by the client, and other asynchronous events that may be + * sent by the server to notify the client of activity in the Starlark code + * being debugged. + */ +export interface DebugEvent { + /** + * If non-zero, this event is a response to a DebugRequest with the same + * sequence number. + */ + 'sequenceNumber'?: (number | string | Long); + 'error'?: (_starlark_debugging_Error); + 'setBreakpoints'?: (_starlark_debugging_SetBreakpointsResponse); + 'continueExecution'?: (_starlark_debugging_ContinueExecutionResponse); + 'evaluate'?: (_starlark_debugging_EvaluateResponse); + 'listFrames'?: (_starlark_debugging_ListFramesResponse); + 'startDebugging'?: (_starlark_debugging_StartDebuggingResponse); + 'pauseThread'?: (_starlark_debugging_PauseThreadResponse); + 'getChildren'?: (_starlark_debugging_GetChildrenResponse); + 'threadPaused'?: (_starlark_debugging_ThreadPausedEvent); + 'threadContinued'?: (_starlark_debugging_ThreadContinuedEvent); + /** + * The payload describes the type of event and any additional information + * about the event. + */ + 'payload'?: "error"|"setBreakpoints"|"continueExecution"|"evaluate"|"listFrames"|"startDebugging"|"pauseThread"|"getChildren"|"threadPaused"|"threadContinued"; +} + +/** + * There are two kinds of events: "responses", which correspond to a + * DebugRequest sent by the client, and other asynchronous events that may be + * sent by the server to notify the client of activity in the Starlark code + * being debugged. + */ +export interface DebugEvent__Output { + /** + * If non-zero, this event is a response to a DebugRequest with the same + * sequence number. + */ + 'sequenceNumber': (Long); + 'error'?: (_starlark_debugging_Error__Output); + 'setBreakpoints'?: (_starlark_debugging_SetBreakpointsResponse__Output); + 'continueExecution'?: (_starlark_debugging_ContinueExecutionResponse__Output); + 'evaluate'?: (_starlark_debugging_EvaluateResponse__Output); + 'listFrames'?: (_starlark_debugging_ListFramesResponse__Output); + 'startDebugging'?: (_starlark_debugging_StartDebuggingResponse__Output); + 'pauseThread'?: (_starlark_debugging_PauseThreadResponse__Output); + 'getChildren'?: (_starlark_debugging_GetChildrenResponse__Output); + 'threadPaused'?: (_starlark_debugging_ThreadPausedEvent__Output); + 'threadContinued'?: (_starlark_debugging_ThreadContinuedEvent__Output); + /** + * The payload describes the type of event and any additional information + * about the event. + */ + 'payload': "error"|"setBreakpoints"|"continueExecution"|"evaluate"|"listFrames"|"startDebugging"|"pauseThread"|"getChildren"|"threadPaused"|"threadContinued"; +} diff --git a/src/proto/starlark_debugging/DebugRequest.ts b/src/proto/starlark_debugging/DebugRequest.ts new file mode 100644 index 00000000..42ae27f2 --- /dev/null +++ b/src/proto/starlark_debugging/DebugRequest.ts @@ -0,0 +1,58 @@ +// Original file: proto/starlark_debugging.proto + +import type { SetBreakpointsRequest as _starlark_debugging_SetBreakpointsRequest, SetBreakpointsRequest__Output as _starlark_debugging_SetBreakpointsRequest__Output } from '../starlark_debugging/SetBreakpointsRequest'; +import type { ContinueExecutionRequest as _starlark_debugging_ContinueExecutionRequest, ContinueExecutionRequest__Output as _starlark_debugging_ContinueExecutionRequest__Output } from '../starlark_debugging/ContinueExecutionRequest'; +import type { EvaluateRequest as _starlark_debugging_EvaluateRequest, EvaluateRequest__Output as _starlark_debugging_EvaluateRequest__Output } from '../starlark_debugging/EvaluateRequest'; +import type { ListFramesRequest as _starlark_debugging_ListFramesRequest, ListFramesRequest__Output as _starlark_debugging_ListFramesRequest__Output } from '../starlark_debugging/ListFramesRequest'; +import type { StartDebuggingRequest as _starlark_debugging_StartDebuggingRequest, StartDebuggingRequest__Output as _starlark_debugging_StartDebuggingRequest__Output } from '../starlark_debugging/StartDebuggingRequest'; +import type { PauseThreadRequest as _starlark_debugging_PauseThreadRequest, PauseThreadRequest__Output as _starlark_debugging_PauseThreadRequest__Output } from '../starlark_debugging/PauseThreadRequest'; +import type { GetChildrenRequest as _starlark_debugging_GetChildrenRequest, GetChildrenRequest__Output as _starlark_debugging_GetChildrenRequest__Output } from '../starlark_debugging/GetChildrenRequest'; +import type { Long } from '@grpc/proto-loader'; + +/** + * A request sent by the debug client to the debug server. + */ +export interface DebugRequest { + /** + * A number (intended to be sequentially generated by the client) that + * identifies the request. The response sent by the server will contain the + * same sequence number so that the client can synchronize its activity if + * desired. + */ + 'sequenceNumber'?: (number | string | Long); + 'setBreakpoints'?: (_starlark_debugging_SetBreakpointsRequest); + 'continueExecution'?: (_starlark_debugging_ContinueExecutionRequest); + 'evaluate'?: (_starlark_debugging_EvaluateRequest); + 'listFrames'?: (_starlark_debugging_ListFramesRequest); + 'startDebugging'?: (_starlark_debugging_StartDebuggingRequest); + 'pauseThread'?: (_starlark_debugging_PauseThreadRequest); + 'getChildren'?: (_starlark_debugging_GetChildrenRequest); + /** + * The payload describes the type of the request and its arguments, if any. + */ + 'payload'?: "setBreakpoints"|"continueExecution"|"evaluate"|"listFrames"|"startDebugging"|"pauseThread"|"getChildren"; +} + +/** + * A request sent by the debug client to the debug server. + */ +export interface DebugRequest__Output { + /** + * A number (intended to be sequentially generated by the client) that + * identifies the request. The response sent by the server will contain the + * same sequence number so that the client can synchronize its activity if + * desired. + */ + 'sequenceNumber': (Long); + 'setBreakpoints'?: (_starlark_debugging_SetBreakpointsRequest__Output); + 'continueExecution'?: (_starlark_debugging_ContinueExecutionRequest__Output); + 'evaluate'?: (_starlark_debugging_EvaluateRequest__Output); + 'listFrames'?: (_starlark_debugging_ListFramesRequest__Output); + 'startDebugging'?: (_starlark_debugging_StartDebuggingRequest__Output); + 'pauseThread'?: (_starlark_debugging_PauseThreadRequest__Output); + 'getChildren'?: (_starlark_debugging_GetChildrenRequest__Output); + /** + * The payload describes the type of the request and its arguments, if any. + */ + 'payload': "setBreakpoints"|"continueExecution"|"evaluate"|"listFrames"|"startDebugging"|"pauseThread"|"getChildren"; +} diff --git a/src/proto/starlark_debugging/Error.ts b/src/proto/starlark_debugging/Error.ts new file mode 100644 index 00000000..4ec11776 --- /dev/null +++ b/src/proto/starlark_debugging/Error.ts @@ -0,0 +1,24 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * A response that indicates that an error occurred while handling a debugging + * request. + */ +export interface Error { + /** + * A message describing the error that occurred. + */ + 'message'?: (string); +} + +/** + * A response that indicates that an error occurred while handling a debugging + * request. + */ +export interface Error__Output { + /** + * A message describing the error that occurred. + */ + 'message': (string); +} diff --git a/src/proto/starlark_debugging/EvaluateRequest.ts b/src/proto/starlark_debugging/EvaluateRequest.ts new file mode 100644 index 00000000..4ed36e24 --- /dev/null +++ b/src/proto/starlark_debugging/EvaluateRequest.ts @@ -0,0 +1,33 @@ +// Original file: proto/starlark_debugging.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * A request to evaluate a Starlark statement in a thread's current environment. + */ +export interface EvaluateRequest { + /** + * The identifier of the thread in whose execution context the expression + * should be evaluated. + */ + 'threadId'?: (number | string | Long); + /** + * The Starlark statement to evaluate. + */ + 'statement'?: (string); +} + +/** + * A request to evaluate a Starlark statement in a thread's current environment. + */ +export interface EvaluateRequest__Output { + /** + * The identifier of the thread in whose execution context the expression + * should be evaluated. + */ + 'threadId': (Long); + /** + * The Starlark statement to evaluate. + */ + 'statement': (string); +} diff --git a/src/proto/starlark_debugging/EvaluateResponse.ts b/src/proto/starlark_debugging/EvaluateResponse.ts new file mode 100644 index 00000000..5cc545d9 --- /dev/null +++ b/src/proto/starlark_debugging/EvaluateResponse.ts @@ -0,0 +1,23 @@ +// Original file: proto/starlark_debugging.proto + +import type { Value as _starlark_debugging_Value, Value__Output as _starlark_debugging_Value__Output } from '../starlark_debugging/Value'; + +/** + * The response to an EvaluateRequest. + */ +export interface EvaluateResponse { + /** + * The result of evaluating a statement. + */ + 'result'?: (_starlark_debugging_Value); +} + +/** + * The response to an EvaluateRequest. + */ +export interface EvaluateResponse__Output { + /** + * The result of evaluating a statement. + */ + 'result'?: (_starlark_debugging_Value__Output); +} diff --git a/src/proto/starlark_debugging/Frame.ts b/src/proto/starlark_debugging/Frame.ts new file mode 100644 index 00000000..95940b10 --- /dev/null +++ b/src/proto/starlark_debugging/Frame.ts @@ -0,0 +1,42 @@ +// Original file: proto/starlark_debugging.proto + +import type { Scope as _starlark_debugging_Scope, Scope__Output as _starlark_debugging_Scope__Output } from '../starlark_debugging/Scope'; +import type { Location as _starlark_debugging_Location, Location__Output as _starlark_debugging_Location__Output } from '../starlark_debugging/Location'; + +/** + * A single frame in a thread's stack trace. + */ +export interface Frame { + /** + * The name of the function that this frame represents. + */ + 'functionName'?: (string); + /** + * The scopes that contain value bindings accessible in this frame. + */ + 'scope'?: (_starlark_debugging_Scope)[]; + /** + * The source location where the frame is currently paused. May not be set in + * some situations. + */ + 'location'?: (_starlark_debugging_Location); +} + +/** + * A single frame in a thread's stack trace. + */ +export interface Frame__Output { + /** + * The name of the function that this frame represents. + */ + 'functionName': (string); + /** + * The scopes that contain value bindings accessible in this frame. + */ + 'scope': (_starlark_debugging_Scope__Output)[]; + /** + * The source location where the frame is currently paused. May not be set in + * some situations. + */ + 'location'?: (_starlark_debugging_Location__Output); +} diff --git a/src/proto/starlark_debugging/GetChildrenRequest.ts b/src/proto/starlark_debugging/GetChildrenRequest.ts new file mode 100644 index 00000000..5d206b00 --- /dev/null +++ b/src/proto/starlark_debugging/GetChildrenRequest.ts @@ -0,0 +1,39 @@ +// Original file: proto/starlark_debugging.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * A request to list the children of a previously-communicated Value, such as + * its elements (for a list or dictionary), its fields (for a struct), and so + * forth. + */ +export interface GetChildrenRequest { + /** + * The identifier of the relevant thread. + */ + 'threadId'?: (number | string | Long); + /** + * The identifier of the value for which children are being requested. If the + * value has no children, an empty list will be returned in + * GetChildrenResponse. + */ + 'valueId'?: (number | string | Long); +} + +/** + * A request to list the children of a previously-communicated Value, such as + * its elements (for a list or dictionary), its fields (for a struct), and so + * forth. + */ +export interface GetChildrenRequest__Output { + /** + * The identifier of the relevant thread. + */ + 'threadId': (Long); + /** + * The identifier of the value for which children are being requested. If the + * value has no children, an empty list will be returned in + * GetChildrenResponse. + */ + 'valueId': (Long); +} diff --git a/src/proto/starlark_debugging/GetChildrenResponse.ts b/src/proto/starlark_debugging/GetChildrenResponse.ts new file mode 100644 index 00000000..e457bf18 --- /dev/null +++ b/src/proto/starlark_debugging/GetChildrenResponse.ts @@ -0,0 +1,17 @@ +// Original file: proto/starlark_debugging.proto + +import type { Value as _starlark_debugging_Value, Value__Output as _starlark_debugging_Value__Output } from '../starlark_debugging/Value'; + +/** + * The response to a GetChildrenRequest. + */ +export interface GetChildrenResponse { + 'children'?: (_starlark_debugging_Value)[]; +} + +/** + * The response to a GetChildrenRequest. + */ +export interface GetChildrenResponse__Output { + 'children': (_starlark_debugging_Value__Output)[]; +} diff --git a/src/proto/starlark_debugging/ListFramesRequest.ts b/src/proto/starlark_debugging/ListFramesRequest.ts new file mode 100644 index 00000000..e83a7a89 --- /dev/null +++ b/src/proto/starlark_debugging/ListFramesRequest.ts @@ -0,0 +1,23 @@ +// Original file: proto/starlark_debugging.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * A request to list the stack frames of a thread. + */ +export interface ListFramesRequest { + /** + * The identifier of the thread whose stack frames should be listed. + */ + 'threadId'?: (number | string | Long); +} + +/** + * A request to list the stack frames of a thread. + */ +export interface ListFramesRequest__Output { + /** + * The identifier of the thread whose stack frames should be listed. + */ + 'threadId': (Long); +} diff --git a/src/proto/starlark_debugging/ListFramesResponse.ts b/src/proto/starlark_debugging/ListFramesResponse.ts new file mode 100644 index 00000000..961af449 --- /dev/null +++ b/src/proto/starlark_debugging/ListFramesResponse.ts @@ -0,0 +1,25 @@ +// Original file: proto/starlark_debugging.proto + +import type { Frame as _starlark_debugging_Frame, Frame__Output as _starlark_debugging_Frame__Output } from '../starlark_debugging/Frame'; + +/** + * The response to a ListFramesRequest. + */ +export interface ListFramesResponse { + /** + * The list of stack frames. The first element in the list represents the + * topmost frame (that is, the current innermost function). + */ + 'frame'?: (_starlark_debugging_Frame)[]; +} + +/** + * The response to a ListFramesRequest. + */ +export interface ListFramesResponse__Output { + /** + * The list of stack frames. The first element in the list represents the + * topmost frame (that is, the current innermost function). + */ + 'frame': (_starlark_debugging_Frame__Output)[]; +} diff --git a/src/proto/starlark_debugging/Location.ts b/src/proto/starlark_debugging/Location.ts new file mode 100644 index 00000000..e61c9098 --- /dev/null +++ b/src/proto/starlark_debugging/Location.ts @@ -0,0 +1,40 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * A location in Starlark source code. + */ +export interface Location { + /** + * The path of the Starlark source file. + */ + 'path'?: (string); + /** + * A 1-indexed line number in the file denoted by path. + */ + 'lineNumber'?: (number); + /** + * A 1-indexed column number in the file denoted by path. 0 (/unset) indicates + * column number is unknown or irrelevant. + */ + 'columnNumber'?: (number); +} + +/** + * A location in Starlark source code. + */ +export interface Location__Output { + /** + * The path of the Starlark source file. + */ + 'path': (string); + /** + * A 1-indexed line number in the file denoted by path. + */ + 'lineNumber': (number); + /** + * A 1-indexed column number in the file denoted by path. 0 (/unset) indicates + * column number is unknown or irrelevant. + */ + 'columnNumber': (number); +} diff --git a/src/proto/starlark_debugging/PauseReason.ts b/src/proto/starlark_debugging/PauseReason.ts new file mode 100644 index 00000000..85d052d8 --- /dev/null +++ b/src/proto/starlark_debugging/PauseReason.ts @@ -0,0 +1,36 @@ +// Original file: proto/starlark_debugging.proto + +/** + * The reason why a thread was paused. + */ +export enum PauseReason { + /** + * The debug server hasn't set any reason. + */ + UNSET = 0, + /** + * The stepping condition in a ContinueExecutionRequest was hit. + */ + STEPPING = 1, + /** + * A PauseThreadRequest was sent with thread_id=0. + */ + ALL_THREADS_PAUSED = 2, + /** + * A PauseThreadRequest was sent with thread_id matching this thread. + */ + PAUSE_THREAD_REQUEST = 3, + /** + * A breakpoint was hit. + */ + HIT_BREAKPOINT = 4, + /** + * An error occurred while evaluating a breakpoint condition. + */ + CONDITIONAL_BREAKPOINT_ERROR = 5, + /** + * Debugging just started, and a StartDebuggingRequest has not yet been + * received and processed. + */ + INITIALIZING = 6, +} diff --git a/src/proto/starlark_debugging/PauseThreadRequest.ts b/src/proto/starlark_debugging/PauseThreadRequest.ts new file mode 100644 index 00000000..6d67d6cb --- /dev/null +++ b/src/proto/starlark_debugging/PauseThreadRequest.ts @@ -0,0 +1,31 @@ +// Original file: proto/starlark_debugging.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * A request to pause execution of a thread, or all threads. + */ +export interface PauseThreadRequest { + /** + * The identifier of the thread to be paused. + * + * If not set (i.e. zero), all current Starlark threads will be paused, and + * until a ContinueExecutionRequest is sent, any future Starlark threads will + * also start off paused. + */ + 'threadId'?: (number | string | Long); +} + +/** + * A request to pause execution of a thread, or all threads. + */ +export interface PauseThreadRequest__Output { + /** + * The identifier of the thread to be paused. + * + * If not set (i.e. zero), all current Starlark threads will be paused, and + * until a ContinueExecutionRequest is sent, any future Starlark threads will + * also start off paused. + */ + 'threadId': (Long); +} diff --git a/src/proto/starlark_debugging/PauseThreadResponse.ts b/src/proto/starlark_debugging/PauseThreadResponse.ts new file mode 100644 index 00000000..21516390 --- /dev/null +++ b/src/proto/starlark_debugging/PauseThreadResponse.ts @@ -0,0 +1,18 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * The response to a PauseThreadRequest. This is an acknowledgement that the + * request was received. Actual pausing of individual threads happens + * asynchronously, and will be communicated via ThreadPausedEvent(s). + */ +export interface PauseThreadResponse { +} + +/** + * The response to a PauseThreadRequest. This is an acknowledgement that the + * request was received. Actual pausing of individual threads happens + * asynchronously, and will be communicated via ThreadPausedEvent(s). + */ +export interface PauseThreadResponse__Output { +} diff --git a/src/proto/starlark_debugging/PausedThread.ts b/src/proto/starlark_debugging/PausedThread.ts new file mode 100644 index 00000000..c964633e --- /dev/null +++ b/src/proto/starlark_debugging/PausedThread.ts @@ -0,0 +1,58 @@ +// Original file: proto/starlark_debugging.proto + +import type { PauseReason as _starlark_debugging_PauseReason } from '../starlark_debugging/PauseReason'; +import type { Location as _starlark_debugging_Location, Location__Output as _starlark_debugging_Location__Output } from '../starlark_debugging/Location'; +import type { Error as _starlark_debugging_Error, Error__Output as _starlark_debugging_Error__Output } from '../starlark_debugging/Error'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Information about a paused Starlark thread. + */ +export interface PausedThread { + /** + * The identifier of the thread. + */ + 'id'?: (number | string | Long); + /** + * A descriptive name for the thread that can be displayed in the debugger's + * UI. + */ + 'name'?: (string); + 'pauseReason'?: (_starlark_debugging_PauseReason | keyof typeof _starlark_debugging_PauseReason); + /** + * The location in Starlark code of the next statement or expression that will + * be executed. + */ + 'location'?: (_starlark_debugging_Location); + /** + * An error that occurred while evaluating a breakpoint condition. Present if + * and only if pause_reason is CONDITIONAL_BREAKPOINT_ERROR. + */ + 'conditionalBreakpointError'?: (_starlark_debugging_Error); +} + +/** + * Information about a paused Starlark thread. + */ +export interface PausedThread__Output { + /** + * The identifier of the thread. + */ + 'id': (Long); + /** + * A descriptive name for the thread that can be displayed in the debugger's + * UI. + */ + 'name': (string); + 'pauseReason': (_starlark_debugging_PauseReason); + /** + * The location in Starlark code of the next statement or expression that will + * be executed. + */ + 'location'?: (_starlark_debugging_Location__Output); + /** + * An error that occurred while evaluating a breakpoint condition. Present if + * and only if pause_reason is CONDITIONAL_BREAKPOINT_ERROR. + */ + 'conditionalBreakpointError'?: (_starlark_debugging_Error__Output); +} diff --git a/src/proto/starlark_debugging/Scope.ts b/src/proto/starlark_debugging/Scope.ts new file mode 100644 index 00000000..f3eb5622 --- /dev/null +++ b/src/proto/starlark_debugging/Scope.ts @@ -0,0 +1,31 @@ +// Original file: proto/starlark_debugging.proto + +import type { Value as _starlark_debugging_Value, Value__Output as _starlark_debugging_Value__Output } from '../starlark_debugging/Value'; + +/** + * A scope that contains value bindings accessible in a frame. + */ +export interface Scope { + /** + * A human-readable name of the scope, such as "global" or "local". + */ + 'name'?: (string); + /** + * The variable bindings that are defined in this scope. + */ + 'binding'?: (_starlark_debugging_Value)[]; +} + +/** + * A scope that contains value bindings accessible in a frame. + */ +export interface Scope__Output { + /** + * A human-readable name of the scope, such as "global" or "local". + */ + 'name': (string); + /** + * The variable bindings that are defined in this scope. + */ + 'binding': (_starlark_debugging_Value__Output)[]; +} diff --git a/src/proto/starlark_debugging/SetBreakpointsRequest.ts b/src/proto/starlark_debugging/SetBreakpointsRequest.ts new file mode 100644 index 00000000..5dd0cf02 --- /dev/null +++ b/src/proto/starlark_debugging/SetBreakpointsRequest.ts @@ -0,0 +1,25 @@ +// Original file: proto/starlark_debugging.proto + +import type { Breakpoint as _starlark_debugging_Breakpoint, Breakpoint__Output as _starlark_debugging_Breakpoint__Output } from '../starlark_debugging/Breakpoint'; + +/** + * A request to update the breakpoints used by the debug server. + */ +export interface SetBreakpointsRequest { + /** + * The breakpoints that describe where the debug server should pause + * evaluation. + */ + 'breakpoint'?: (_starlark_debugging_Breakpoint)[]; +} + +/** + * A request to update the breakpoints used by the debug server. + */ +export interface SetBreakpointsRequest__Output { + /** + * The breakpoints that describe where the debug server should pause + * evaluation. + */ + 'breakpoint': (_starlark_debugging_Breakpoint__Output)[]; +} diff --git a/src/proto/starlark_debugging/SetBreakpointsResponse.ts b/src/proto/starlark_debugging/SetBreakpointsResponse.ts new file mode 100644 index 00000000..c7496400 --- /dev/null +++ b/src/proto/starlark_debugging/SetBreakpointsResponse.ts @@ -0,0 +1,14 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * The response to a SetBreakpointsRequest. + */ +export interface SetBreakpointsResponse { +} + +/** + * The response to a SetBreakpointsRequest. + */ +export interface SetBreakpointsResponse__Output { +} diff --git a/src/proto/starlark_debugging/StartDebuggingRequest.ts b/src/proto/starlark_debugging/StartDebuggingRequest.ts new file mode 100644 index 00000000..acb251cb --- /dev/null +++ b/src/proto/starlark_debugging/StartDebuggingRequest.ts @@ -0,0 +1,18 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * A request to begin the debugging session. Starlark execution will block until + * this request is made, to allow initial setup after the connection is + * established (e.g. setting breakpoints). + */ +export interface StartDebuggingRequest { +} + +/** + * A request to begin the debugging session. Starlark execution will block until + * this request is made, to allow initial setup after the connection is + * established (e.g. setting breakpoints). + */ +export interface StartDebuggingRequest__Output { +} diff --git a/src/proto/starlark_debugging/StartDebuggingResponse.ts b/src/proto/starlark_debugging/StartDebuggingResponse.ts new file mode 100644 index 00000000..5881c560 --- /dev/null +++ b/src/proto/starlark_debugging/StartDebuggingResponse.ts @@ -0,0 +1,14 @@ +// Original file: proto/starlark_debugging.proto + + +/** + * The response to a StartDebuggingRequest. + */ +export interface StartDebuggingResponse { +} + +/** + * The response to a StartDebuggingRequest. + */ +export interface StartDebuggingResponse__Output { +} diff --git a/src/proto/starlark_debugging/Stepping.ts b/src/proto/starlark_debugging/Stepping.ts new file mode 100644 index 00000000..22313b8f --- /dev/null +++ b/src/proto/starlark_debugging/Stepping.ts @@ -0,0 +1,27 @@ +// Original file: proto/starlark_debugging.proto + +/** + * Describes the stepping behavior that should occur when execution of a thread + * is continued. + */ +export enum Stepping { + /** + * Do not step; continue execution until it completes or is paused for some + * other reason (such as hitting another breakpoint). + */ + NONE = 0, + /** + * If the thread is paused on a statement that contains a function call, + * step into that function. Otherwise, this is the same as OVER. + */ + INTO = 1, + /** + * Step over the next statement and any functions that it may call. + */ + OVER = 2, + /** + * Continue execution until the current function has been exited and then + * pause. + */ + OUT = 3, +} diff --git a/src/proto/starlark_debugging/ThreadContinuedEvent.ts b/src/proto/starlark_debugging/ThreadContinuedEvent.ts new file mode 100644 index 00000000..580cbfaa --- /dev/null +++ b/src/proto/starlark_debugging/ThreadContinuedEvent.ts @@ -0,0 +1,23 @@ +// Original file: proto/starlark_debugging.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * An event indicating that a thread has continued execution after being paused. + */ +export interface ThreadContinuedEvent { + /** + * The identifier of the thread that continued executing. + */ + 'threadId'?: (number | string | Long); +} + +/** + * An event indicating that a thread has continued execution after being paused. + */ +export interface ThreadContinuedEvent__Output { + /** + * The identifier of the thread that continued executing. + */ + 'threadId': (Long); +} diff --git a/src/proto/starlark_debugging/ThreadPausedEvent.ts b/src/proto/starlark_debugging/ThreadPausedEvent.ts new file mode 100644 index 00000000..b24c1254 --- /dev/null +++ b/src/proto/starlark_debugging/ThreadPausedEvent.ts @@ -0,0 +1,23 @@ +// Original file: proto/starlark_debugging.proto + +import type { PausedThread as _starlark_debugging_PausedThread, PausedThread__Output as _starlark_debugging_PausedThread__Output } from '../starlark_debugging/PausedThread'; + +/** + * An event indicating that a thread was paused during execution. + */ +export interface ThreadPausedEvent { + /** + * The thread that was paused. + */ + 'thread'?: (_starlark_debugging_PausedThread); +} + +/** + * An event indicating that a thread was paused during execution. + */ +export interface ThreadPausedEvent__Output { + /** + * The thread that was paused. + */ + 'thread'?: (_starlark_debugging_PausedThread__Output); +} diff --git a/src/proto/starlark_debugging/Value.ts b/src/proto/starlark_debugging/Value.ts new file mode 100644 index 00000000..42346ae9 --- /dev/null +++ b/src/proto/starlark_debugging/Value.ts @@ -0,0 +1,83 @@ +// Original file: proto/starlark_debugging.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * The debugger representation of a Starlark value. + */ +export interface Value { + /** + * A label that describes this value's location or source in a value + * hierarchy. + * + * For example, in a stack frame, the label would be the name of the variable + * to which the value is bound. For a value that is an element of a list, its + * its label would be its subscript, such as "[4]". A value that is a field in + * a struct would use the field's name as its label, and so forth. + */ + 'label'?: (string); + /** + * A string description of the value. + */ + 'description'?: (string); + /** + * A string describing the type of the value. + * + * This field may be omitted if the value does not correspond to a "real" type + * as far as the debugging view is concerned; for example, dictionaries will + * be rendered as sequences of key/value pairs ("entries") but the entries + * themselves do not have a meaningful type with respect to our rendering. + */ + 'type'?: (string); + /** + * Will be false if the value is known to have no children. May sometimes be + * true if this isn't yet known, in which case GetChildrenResponse#children + * will be empty. + */ + 'hasChildren'?: (boolean); + /** + * An identifier for this value, used to request its children. The same value + * may be known by multiple ids. Not set for values without children. + */ + 'id'?: (number | string | Long); +} + +/** + * The debugger representation of a Starlark value. + */ +export interface Value__Output { + /** + * A label that describes this value's location or source in a value + * hierarchy. + * + * For example, in a stack frame, the label would be the name of the variable + * to which the value is bound. For a value that is an element of a list, its + * its label would be its subscript, such as "[4]". A value that is a field in + * a struct would use the field's name as its label, and so forth. + */ + 'label': (string); + /** + * A string description of the value. + */ + 'description': (string); + /** + * A string describing the type of the value. + * + * This field may be omitted if the value does not correspond to a "real" type + * as far as the debugging view is concerned; for example, dictionaries will + * be rendered as sequences of key/value pairs ("entries") but the entries + * themselves do not have a meaningful type with respect to our rendering. + */ + 'type': (string); + /** + * Will be false if the value is known to have no children. May sometimes be + * true if this isn't yet known, in which case GetChildrenResponse#children + * will be empty. + */ + 'hasChildren': (boolean); + /** + * An identifier for this value, used to request its children. The same value + * may be known by multiple ids. Not set for values without children. + */ + 'id': (Long); +} From 600ccd73dcd0d7ded09f09716793b99894742732 Mon Sep 17 00:00:00 2001 From: Paul Code Johnston Date: Sun, 8 Nov 2020 15:16:48 -0700 Subject: [PATCH 4/4] Add experimental starlark DAP client --- .vscode/launch.json | 18 +- package-lock.json | 19 + package.json | 6 +- src/bzl/feature.ts | 3 +- src/starlark/debug/adapter.ts | 723 +++++++++++++++++++++++++++++++ src/starlark/debug/client.ts | 658 ++++++++++++++++++++++++++++ src/starlark/debug/connection.ts | 216 +++++++++ src/starlark/debug/dapClient.ts | 99 +++++ src/starlark/debug/handles.ts | 46 ++ src/starlark/debug/main.ts | 10 + 10 files changed, 1794 insertions(+), 4 deletions(-) create mode 100644 src/starlark/debug/adapter.ts create mode 100644 src/starlark/debug/client.ts create mode 100644 src/starlark/debug/connection.ts create mode 100644 src/starlark/debug/dapClient.ts create mode 100644 src/starlark/debug/handles.ts create mode 100644 src/starlark/debug/main.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index b1fbaf55..c47091ee 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,6 +31,22 @@ "${workspaceFolder}/out/test/**/*.js" ], "preLaunchTask": "${defaultBuildTask}" - } + }, + { + "name": "Launch starlark server (dlv dap)", + "type": "node", + "protocol": "inspector", + "request": "launch", + "program": "${workspaceFolder}/out/src/starlark/debug/adapter.js", + "args": [ + "--server=4711" + ], + "sourceMaps": true, + "smartStep": true, + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + } ] } diff --git a/package-lock.json b/package-lock.json index 3f68deba..1c060bfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5975,6 +5975,11 @@ "punycode": "^2.1.1" } }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + }, "ts-loader": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.4.tgz", @@ -6253,6 +6258,20 @@ "resolved": "https://registry.npmjs.org/vscode-common/-/vscode-common-1.50.0.tgz", "integrity": "sha512-LLya2PcXD1PNipniNKKZapRdxJBu2lc9PUANwxS9th7y92d6JDq/yY4LtK2gi4s7yD4JuaatUX3JUVk2LcjgjA==" }, + "vscode-debugadapter": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.42.1.tgz", + "integrity": "sha512-bICDB8mxReU2kGL13ftjNqeInYtVN3zpRdZKjRZHCpXC/mofrdaj9KRlJsALwcUNivMA9S/LwTZ2n+ros3X0jg==", + "requires": { + "mkdirp": "^0.5.5", + "vscode-debugprotocol": "1.42.0" + } + }, + "vscode-debugprotocol": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.42.0.tgz", + "integrity": "sha512-nVsfVCat9FZlOso5SYB1LQQiFGifTyOALpkpJdudDlRXGTpI3mSFiDYXWaoFm7UcfqTOzn1SC7Hqw4d89btT0w==" + }, "vscode-extension-telemetry": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.6.tgz", diff --git a/package.json b/package.json index c9c95d1e..86d6a886 100644 --- a/package.json +++ b/package.json @@ -572,12 +572,12 @@ }, { "command": "workbench.view.extension.bazel-explorer", - "key": "shift+cmd+z", + "key": "shift+cmd+t", "title": "Reveal Bazel Explorer" }, { "command": "workbench.view.extension.stackb-explorer", - "key": "shift+cmd+t", + "key": "shift+ctrl+t", "title": "Reveal Stack.Build Explorer" } ], @@ -1158,9 +1158,11 @@ "strip-ansi": "^6.0.0", "tail": "2.0.4", "tmp": "0.2.1", + "tree-kill": "1.2.2", "uuid": "8.3.0", "vscode-common": "1.50.0", "vscode-extension-telemetry": "^0.1.6", + "vscode-debugadapter": "1.42.1", "vscode-languageclient": "6.1.3" }, "devDependencies": { diff --git a/src/bzl/feature.ts b/src/bzl/feature.ts index c014107d..68d0a333 100644 --- a/src/bzl/feature.ts +++ b/src/bzl/feature.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { API } from '../api'; import { IExtensionFeature } from '../common'; -import { BzlClient, Closeable } from './bzlclient'; +import { BzlClient } from './bzlclient'; import { BzlServerProcess } from './client'; import { CodeSearch } from './codesearch/codesearch'; import { BzlServerCommandRunner } from './commandrunner'; @@ -20,6 +20,7 @@ import { loadNucleateProtos } from './configuration'; import { ConfigSection, Server, ViewName } from './constants'; +import { Closeable } from './grpcclient'; import { EmptyView } from './view/emptyview'; import { BuildEventProtocolView } from './view/events'; import { BzlCommandHistoryView } from './view/history'; diff --git a/src/starlark/debug/adapter.ts b/src/starlark/debug/adapter.ts new file mode 100644 index 00000000..be232c0d --- /dev/null +++ b/src/starlark/debug/adapter.ts @@ -0,0 +1,723 @@ +/*--------------------------------------------------------- + * Copyright 2020 The Go Authors. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +// NOTE: This debug adapter is experimental, in-development code. If you +// actually need to debug Go code, please use the default adapter. + +import { ChildProcess, spawn } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { + logger, + Logger, + LoggingDebugSession, + + TerminatedEvent +} from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { DAPClient } from './dapClient'; +import net = require('net'); +import kill = require('tree-kill'); + + + +interface LoadConfig { + // FollowPointers requests pointers to be automatically dereferenced. + followPointers: boolean; + // MaxVariableRecurse is how far to recurse when evaluating nested types. + maxVariableRecurse: number; + // MaxStringLen is the maximum number of bytes read from a string + maxStringLen: number; + // MaxArrayValues is the maximum number of elements read from an array, a slice or a map. + maxArrayValues: number; + // MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. + maxStructFields: number; +} + +// This interface should always match the schema found in `package.json`. +interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + request: 'launch'; + [key: string]: any; + program: string; + stopOnEntry?: boolean; + args?: string[]; + showLog?: boolean; + logOutput?: string; + cwd?: string; + env?: { [key: string]: string }; + mode?: 'auto' | 'debug' | 'remote' | 'test' | 'exec'; + remotePath?: string; + port?: number; + host?: string; + buildFlags?: string; + init?: string; + trace?: 'verbose' | 'log' | 'error'; + /** Optional path to .env file. */ + envFile?: string | string[]; + backend?: string; + output?: string; + /** Delve LoadConfig parameters */ + dlvLoadConfig?: LoadConfig; + dlvToolPath: string; + /** Delve Version */ + apiVersion: number; + /** Delve maximum stack trace depth */ + stackTraceDepth: number; + + showGlobalVariables?: boolean; + packagePathToGoModPathMap: { [key: string]: string }; +} + +interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { + request: 'attach'; + processId?: number; + stopOnEntry?: boolean; + showLog?: boolean; + logOutput?: string; + cwd?: string; + mode?: 'local' | 'remote'; + remotePath?: string; + port?: number; + host?: string; + trace?: 'verbose' | 'log' | 'error'; + backend?: string; + /** Delve LoadConfig parameters */ + dlvLoadConfig?: LoadConfig; + dlvToolPath: string; + /** Delve Version */ + apiVersion: number; + /** Delve maximum stack trace depth */ + stackTraceDepth: number; + + showGlobalVariables?: boolean; +} + +process.on('uncaughtException', (err: any) => { + const errMessage = err && (err.stack || err.message); + logger.error(`Unhandled error in debug adapter: ${errMessage}`); + throw err; +}); + +function logArgsToString(args: any[]): string { + return args + .map((arg) => { + return typeof arg === 'string' ? arg : JSON.stringify(arg); + }) + .join(' '); +} + +function log(...args: any[]) { + logger.warn(logArgsToString(args)); +} + +function logError(...args: any[]) { + logger.error(logArgsToString(args)); +} + +// GoDlvDapDebugSession implements a DAP debug adapter to talk to the editor. +// +// This adapter serves as a DAP proxy between the editor and the DAP server +// inside Delve. It relies on functionality inherited from DebugSession to +// implement the server side interfacing the editor, and on DapClient to +// implement the client side interfacing Delve: +// +// Editor GoDlvDapDebugSession Delve +// +------------+ +--------------+-----------+ +------------+ +// | DAP Client | <====> | DebugSession | DAPClient | <====> | DAP Server | +// +------------+ +--------------+-----------+ +------------+ +export class GoDlvDapDebugSession extends LoggingDebugSession { + private readonly DEFAULT_DELVE_HOST = '127.0.0.1'; + private readonly DEFAULT_DELVE_PORT = 42042; + + private logLevel: Logger.LogLevel = Logger.LogLevel.Error; + + private dlvClient: StarlarkClient = undefined; + + // // Child process used to track debugee launched without debugging (noDebug + // // mode). Either debugProcess or dlvClient are undefined. + // private debugProcess: ChildProcess = undefined; + + public constructor() { + super(); + + // Invoke logger.init here because we want logging to work in 'inline' + // DA mode. It's typically called in the start() method of our parent + // class, but this method isn't called in 'inline' mode. + logger.init((e) => this.sendEvent(e)); + + // this debugger uses zero-based lines and columns + this.setDebuggerLinesStartAt1(false); + this.setDebuggerColumnsStartAt1(false); + } + + protected initializeRequest( + response: DebugProtocol.InitializeResponse, + args: DebugProtocol.InitializeRequestArguments, + request?: DebugProtocol.Request + ): void { + log('InitializeRequest'); + response!.body!.supportsConfigurationDoneRequest = true; + + // We respond to InitializeRequest here, because Delve hasn't been + // launched yet. Delve will start responding to DAP requests after + // LaunchRequest is received, which tell us how to start it. + + // TODO: we could send an InitializeRequest to Delve when + // it launches, wait for its response and sanity check the capabilities + // it reports. Once DAP support in Delve is complete, this can be part + // of making sure that the "dlv" binary we find is sufficiently + // up-to-date to talk DAP with us. + this.sendResponse(response); + log('InitializeResponse'); + } + + protected launchRequest( + response: DebugProtocol.LaunchResponse, + args: LaunchRequestArguments, + request: DebugProtocol.Request + ): void { + // Setup logger now that we have the 'trace' level passed in from + // LaunchRequestArguments. + this.logLevel = + args.trace === 'verbose' + ? Logger.LogLevel.Verbose + : args.trace === 'log' + ? Logger.LogLevel.Log + : Logger.LogLevel.Error; + const logPath = + this.logLevel !== Logger.LogLevel.Error ? path.join(os.tmpdir(), 'vscode-godlvdapdebug.txt') : undefined; + logger.setup(this.logLevel, logPath); + log('launchRequest'); + + // // In noDebug mode, we don't launch Delve. + // // TODO: this logic is currently organized for compatibility with the + // // existing DA. It's not clear what we should do in case noDebug is + // // set and mode isn't 'debug'. Sending an error response could be + // // a safe option. + // if (args.noDebug && args.mode === 'debug') { + // try { + // this.launchNoDebug(args); + // } catch (e) { + // logError(`launchNoDebug failed: "${e}"`); + // // TODO: define error constants + // // https://github.com/golang/vscode-go/issues/305 + // this.sendErrorResponse( + // response, + // 3000, + // `Failed to launch "${e}"`); + // } + // return; + // } + + if (!args.port) { + args.port = this.DEFAULT_DELVE_PORT; + } + if (!args.host) { + args.host = this.DEFAULT_DELVE_HOST; + } + + this.dlvClient = new StarlarkClient(args); + + this.dlvClient.on('stdout', (str) => { + log('dlv stdout:', str); + }); + + this.dlvClient.on('stderr', (str) => { + log('dlv stderr:', str); + }); + + this.dlvClient.on('connected', () => { + this.dlvClient.send(request); + }); + + this.dlvClient.on('close', (rc) => { + if (rc !== 0) { + // TODO: define error constants + // https://github.com/golang/vscode-go/issues/305 + this.sendErrorResponse( + response, + 3000, + 'Failed to continue: Check the debug console for details.'); + } + log('Sending TerminatedEvent as delve is closed'); + this.sendEvent(new TerminatedEvent()); + }); + + // Relay events and responses back to vscode. In the future we will + // add middleware here to intercept specific kinds of responses/events + // for special handling. + this.dlvClient.on('event', (event) => { + this.sendEvent(event); + }); + + this.dlvClient.on('response', (resp) => { + this.sendResponse(resp); + }); + } + + protected attachRequest( + response: DebugProtocol.AttachResponse, + args: AttachRequestArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected disconnectRequest( + response: DebugProtocol.DisconnectResponse, + args: DebugProtocol.DisconnectArguments, + request?: DebugProtocol.Request + ): void { + log('DisconnectRequest'); + + // // How we handle DisconnectRequest depends on whether Delve was launched + // // at all. + // // * In noDebug node, the Go program was spawned directly without + // // debugging: this.debugProcess will be non-null, and this.dlvClient + // // will be null. + // // * Otherwise, Delve was spawned: this.debugProcess will be null, and + // // this.dlvClient will be non-null. + // if (this.debugProcess) { + // log(`killing debugee (pid: ${this.debugProcess.pid})...`); + + // // Kill the debugee and notify the client when the killing is + // // completed, to ensure a clean shutdown sequence. + // killProcessTree(this.debugProcess).then(() => { + // super.disconnectRequest(response, args); + // log('DisconnectResponse'); + // }); + // } else + + + if (this.dlvClient) { + // Forward this DisconnectRequest to Delve. + this.dlvClient.send(request); + } else { + logError('both debug process and dlv client are undefined'); + // TODO: define all error codes as constants + // https://github.com/golang/vscode-go/issues/305 + this.sendErrorResponse( + response, + 3000, + 'Failed to disconnect: Check the debug console for details.'); + } + } + + protected terminateRequest( + response: DebugProtocol.TerminateResponse, + args: DebugProtocol.TerminateArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected restartRequest( + response: DebugProtocol.RestartResponse, + args: DebugProtocol.RestartArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setBreakPointsRequest( + response: DebugProtocol.SetBreakpointsResponse, + args: DebugProtocol.SetBreakpointsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setFunctionBreakPointsRequest( + response: DebugProtocol.SetFunctionBreakpointsResponse, + args: DebugProtocol.SetFunctionBreakpointsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setExceptionBreakPointsRequest( + response: DebugProtocol.SetExceptionBreakpointsResponse, + args: DebugProtocol.SetExceptionBreakpointsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected configurationDoneRequest( + response: DebugProtocol.ConfigurationDoneResponse, + args: DebugProtocol.ConfigurationDoneArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected continueRequest( + response: DebugProtocol.ContinueResponse, + args: DebugProtocol.ContinueArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected nextRequest( + response: DebugProtocol.NextResponse, + args: DebugProtocol.NextArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected stepInRequest( + response: DebugProtocol.StepInResponse, + args: DebugProtocol.StepInArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected stepOutRequest( + response: DebugProtocol.StepOutResponse, + args: DebugProtocol.StepOutArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected stepBackRequest( + response: DebugProtocol.StepBackResponse, + args: DebugProtocol.StepBackArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected reverseContinueRequest( + response: DebugProtocol.ReverseContinueResponse, + args: DebugProtocol.ReverseContinueArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected restartFrameRequest( + response: DebugProtocol.RestartFrameResponse, + args: DebugProtocol.RestartFrameArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected gotoRequest( + response: DebugProtocol.GotoResponse, + args: DebugProtocol.GotoArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected pauseRequest( + response: DebugProtocol.PauseResponse, + args: DebugProtocol.PauseArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected sourceRequest( + response: DebugProtocol.SourceResponse, + args: DebugProtocol.SourceArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected threadsRequest( + response: DebugProtocol.ThreadsResponse, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected terminateThreadsRequest( + response: DebugProtocol.TerminateThreadsResponse, + args: DebugProtocol.TerminateThreadsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected stackTraceRequest( + response: DebugProtocol.StackTraceResponse, + args: DebugProtocol.StackTraceArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected scopesRequest( + response: DebugProtocol.ScopesResponse, + args: DebugProtocol.ScopesArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected variablesRequest( + response: DebugProtocol.VariablesResponse, + args: DebugProtocol.VariablesArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setVariableRequest( + response: DebugProtocol.SetVariableResponse, + args: DebugProtocol.SetVariableArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setExpressionRequest( + response: DebugProtocol.SetExpressionResponse, + args: DebugProtocol.SetExpressionArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected evaluateRequest( + response: DebugProtocol.EvaluateResponse, + args: DebugProtocol.EvaluateArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected stepInTargetsRequest( + response: DebugProtocol.StepInTargetsResponse, + args: DebugProtocol.StepInTargetsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected gotoTargetsRequest( + response: DebugProtocol.GotoTargetsResponse, + args: DebugProtocol.GotoTargetsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected completionsRequest( + response: DebugProtocol.CompletionsResponse, + args: DebugProtocol.CompletionsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected exceptionInfoRequest( + response: DebugProtocol.ExceptionInfoResponse, + args: DebugProtocol.ExceptionInfoArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected loadedSourcesRequest( + response: DebugProtocol.LoadedSourcesResponse, + args: DebugProtocol.LoadedSourcesArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected dataBreakpointInfoRequest( + response: DebugProtocol.DataBreakpointInfoResponse, + args: DebugProtocol.DataBreakpointInfoArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setDataBreakpointsRequest( + response: DebugProtocol.SetDataBreakpointsResponse, + args: DebugProtocol.SetDataBreakpointsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected readMemoryRequest( + response: DebugProtocol.ReadMemoryResponse, + args: DebugProtocol.ReadMemoryArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected disassembleRequest( + response: DebugProtocol.DisassembleResponse, + args: DebugProtocol.DisassembleArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected cancelRequest( + response: DebugProtocol.CancelResponse, + args: DebugProtocol.CancelArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected breakpointLocationsRequest( + response: DebugProtocol.BreakpointLocationsResponse, + args: DebugProtocol.BreakpointLocationsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + + protected setInstructionBreakpointsRequest( + response: DebugProtocol.SetInstructionBreakpointsResponse, + args: DebugProtocol.SetInstructionBreakpointsArguments, + request?: DebugProtocol.Request + ): void { + this.dlvClient.send(request); + } + +} + +// StarlarkClient provides a DAP client to talk to a DAP server in Delve. +// +// After creation, it emits the following events: +// +// 'connected': delve is connected to delve +// 'request (request)': delve sent request +// 'response (response)': delve sent response +// 'event (event)': delve sent event +// 'stdout' (str): delve emitted str to stdout +// 'stderr' (str): delve emitted str to stderr +// 'close' (rc): delve exited with return code rc +class StarlarkClient extends DAPClient { + private debugProcess: ChildProcess; + private serverStarted: boolean = false; + + constructor(launchArgs: LaunchRequestArguments) { + super(); + + const launchArgsEnv = launchArgs.env || {}; + const env = Object.assign({}, process.env, launchArgsEnv); + + // Let users override direct path to delve by setting it in the env + // map in launch.json; if unspecified, fall back to dlvToolPath. + let dlvPath = launchArgsEnv['dlvPath']; + if (!dlvPath) { + dlvPath = launchArgs.dlvToolPath; + } + + if (!fs.existsSync(dlvPath)) { + log( + `Couldn't find dlv at the Go tools path, ${process.env['GOPATH']}${ + env['GOPATH'] ? ', ' + env['GOPATH'] : '' + } or ${envPath}` + ); + throw new Error( + 'Cannot find Delve debugger. Install from https://github.com/go-delve/delve/ & ensure it is in your Go tools path, "GOPATH/bin" or "PATH".' + ); + } + + const dlvArgs = new Array(); + dlvArgs.push('dap'); + dlvArgs.push(`--listen=${launchArgs.host}:${launchArgs.port}`); + if (launchArgs.showLog) { + dlvArgs.push('--log=' + launchArgs.showLog.toString()); + } + if (launchArgs.logOutput) { + dlvArgs.push('--log-output=' + launchArgs.logOutput); + } + + log(`Running: ${dlvPath} ${dlvArgs.join(' ')}`); + + const child = this.debugProcess = spawn(dlvPath, dlvArgs, { + cwd: path.dirname(launchArgs.program), + env + }); + + child.stderr.on('data', (chunk) => { + const str = chunk.toString(); + this.emit('stderr', str); + }); + + child.stdout.on('data', (chunk) => { + const str = chunk.toString(); + this.emit('stdout', str); + + if (!this.serverStarted) { + this.serverStarted = true; + this.connectSocketToServer(launchArgs.port!, launchArgs.host!); + } + }); + + child.on('close', (rc) => { + if (rc) { + logError(`Process exiting with code: ${rc} signal: ${child.killed}`); + } else { + log(`Process exiting normally ${child.killed}`); + } + this.emit('close', rc); + }); + + child.on('error', (err) => { + throw err; + }); + } + + // Connect this client to the server. The server is expected to be listening + // on host:port. + private connectSocketToServer(port: number, host: string) { + // Add a slight delay to ensure that Delve started up the server. + setTimeout(() => { + const socket = net.createConnection( + port, + host, + () => { + this.connect(socket, socket); + this.emit('connected'); + }); + + socket.on('error', (err) => { + throw err; + }); + }, 200); + } +} + +// TODO: refactor this function into util.ts so it could be reused with +// the existing DA. Problem: it currently uses log() and logError() which makes +// this more difficult. +// We'll want a separate util.ts for the DA, because the current utils.ts pulls +// in vscode as a dependency, which shouldn't be done in a DA. +function killProcessTree(p: ChildProcess): Promise { + if (!p || !p.pid) { + log('no process to kill'); + return Promise.resolve(); + } + return new Promise((resolve) => { + kill(p.pid, (err) => { + if (err) { + logError(`Error killing process ${p.pid}: ${err}`); + } else { + log(`killed process ${p.pid}`); + } + resolve(); + }); + }); +} \ No newline at end of file diff --git a/src/starlark/debug/client.ts b/src/starlark/debug/client.ts new file mode 100644 index 00000000..55b2ea6a --- /dev/null +++ b/src/starlark/debug/client.ts @@ -0,0 +1,658 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + ContinuedEvent, + DebugSession, + InitializedEvent, + OutputEvent, + Scope, + Source, + StackFrame, + StoppedEvent, + TerminatedEvent, + Thread, + Variable +} from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { Breakpoint } from '../../proto/starlark_debugging/Breakpoint'; +import { DebugEvent } from '../../proto/starlark_debugging/DebugEvent'; +import { Frame } from '../../proto/starlark_debugging/Frame'; +import { PausedThread } from '../../proto/starlark_debugging/PausedThread'; +import { Scope as StarlarkScope } from '../../proto/starlark_debugging/Scope'; +import { Stepping } from '../../proto/starlark_debugging/Stepping'; +import { ThreadContinuedEvent } from '../../proto/starlark_debugging/ThreadContinuedEvent'; +import { ThreadPausedEvent } from '../../proto/starlark_debugging/ThreadPausedEvent'; +import { Value } from '../../proto/starlark_debugging/Value'; +import { BazelDebugConnection } from './connection'; +import { Handles } from './handles'; +import Long = require('long'); + +/** + * Returns a {@code number} equivalent to the given {@code number} or + * {@code Long}. + * + * @param value If a {@code number}, the value itself is returned; if it is a + * {@code Long}, its equivalent is returned. + * @returns A {@code number} equivalent to the given {@code number} or + * {@code Long}. + */ +function number64(value: number | Long): number { + if (value instanceof Number) { + return value as number; + } + return (value as Long).toNumber(); +} + +/** Arguments that the Bazel debug adapter supports for "attach" requests. */ +interface ILaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + /** + * Target labels and other command line options passed to the 'bazel build' + * command. + */ + args: string[]; + + /** The Bazel command to execute (build, test, etc.). */ + bazelCommand: string; + + /** + * The Bazel executable that should be invoked to execute the command. + * + * This can be either an absolute path or a command name that will be found on + * the system path. If it is not specified, then the debugger will search for + * "bazel" on the system path. + */ + bazelExecutablePath?: string; + + /** + * Any Bazel startup options to be passed before the action ('build') in the + * final command line. + */ + bazelStartupOptions?: [string]; + + /** The working directory in which Bazel will be invoked. */ + cwd: string; + + /** The port number on which the Bazel debug server is running. */ + port?: number; + + /** Indicates whether verbose logging is enabled for the debugger. */ + verbose?: boolean; +} + +/** Manages the state of the debugging client's session. */ +class BazelDebugSession extends DebugSession { + /** Manages communication with the Bazel debugging server. */ + private bazelConnection: BazelDebugConnection | undefined; + + /** The spawned Bazel process. */ + private bazelProcess: child_process.ChildProcess | undefined; + + /** Keeps track of whether a Bazel child process is currently running. */ + private isBazelRunning: boolean = false; + + /** Caches the result of invoking {@code bazel info} when debugging begins. */ + private bazelInfo = new Map(); + + /** Currently set breakpoints, keyed by source path. */ + private sourceBreakpoints = new Map< + string, + DebugProtocol.SourceBreakpoint[] + >(); + + /** Information about paused threads, keyed by thread number. */ + private pausedThreads = new Map(); + + /** An auto-indexed mapping of stack frames. */ + private frameHandles = new Handles(); + + /** + * An auto-indexed mapping of variables references, which may be either scopes + * (whose values are directly members of the scope) or values with child + * values (which need to be requested by contacting the debug server). + */ + private variableHandles = new Handles< + StarlarkScope | Value + >(); + + /** A mapping from frame reference numbers to thread IDs. */ + private frameThreadIds = new Map(); + + /** A mapping from scope reference numbers to thread IDs. */ + private scopeThreadIds = new Map(); + + /** A mapping from value reference numbers to thread IDs. */ + private valueThreadIds = new Map(); + + /** Initializes a new Bazel debug session. */ + public constructor() { + super(); + + // Starlark uses 1-based line and column numbers. + this.setDebuggerLinesStartAt1(true); + this.setDebuggerColumnsStartAt1(true); + } + + // Life-cycle requests + + protected initializeRequest( + response: DebugProtocol.InitializeResponse, + args: DebugProtocol.InitializeRequestArguments, + ) { + response.body = response.body || {}; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsConditionalBreakpoints = true; + response.body.supportsEvaluateForHovers = true; + this.sendResponse(response); + } + + protected async configurationDoneRequest( + response: DebugProtocol.ConfigurationDoneResponse, + args: DebugProtocol.ConfigurationDoneArguments, + ) { + await this.bazelConnection!.sendRequest({ + startDebugging: {}, + }); + + this.sendResponse(response); + } + + protected async launchRequest( + response: DebugProtocol.LaunchResponse, + args: ILaunchRequestArguments, + ) { + const port = args.port || 7300; + const verbose = args.verbose || false; + + const bazelExecutable = this.bazelExecutable(args); + this.bazelInfo = await this.getBazelInfo(bazelExecutable, args.cwd); + + const fullArgs = args.bazelStartupOptions! + .concat([ + args.bazelCommand, + '--color=yes', + '--experimental_skylark_debug', + `--experimental_skylark_debug_server_port=${port}`, + `--experimental_skylark_debug_verbose_logging=${verbose}`, + ]) + .concat(args.args); + + this.launchBazel(bazelExecutable, args.cwd, fullArgs); + + this.bazelConnection = new BazelDebugConnection( + 'localhost', + port, + this.debugLog, + ); + this.bazelConnection.on('connect', () => { + this.sendResponse(response); + this.sendEvent(new InitializedEvent()); + }); + + this.bazelConnection.on('event', (event) => { + this.handleBazelEvent(event); + }); + } + + protected disconnectRequest( + response: DebugProtocol.DisconnectResponse, + args: DebugProtocol.DisconnectArguments, + ) { + // Kill the spawned Bazel process on disconnect. The Bazel server will stay + // up, but this should terminate processing of the invoked command. + if (this.bazelProcess) { + this.bazelProcess.kill('SIGKILL'); + this.bazelProcess = undefined; + } + this.isBazelRunning = false; + this.sendResponse(response); + } + + // Breakpoint requests + + protected setBreakPointsRequest( + response: DebugProtocol.SetBreakpointsResponse, + args: DebugProtocol.SetBreakpointsArguments, + ) { + // The path we need to pass to Bazel here depends on how the .bzl file has + // been loaded. Unfortunately this means we have to create two breakpoints, + // one for each possible path, because the way the .bzl file is loaded is + // chosen by the user: + // + // 1. If the file is loaded using an explicit repository reference (i.e., + // `@foo//:bar.bzl`), then it will appear in the `external` subdirectory + // of Bazel's output_base. + // 2. If the file is loaded as a same-repository path (i.e., `//:bar.bzl`), + // then Bazel will treat it as if it were under `execroot`, which is a + // symlink to the actual filesystem location of the file. + // + // TODO(allevato): We may be able to simplify this once + // https://github.com/bazelbuild/bazel/issues/6848 is in a release. + const workspaceName = path.basename(this.bazelInfo.get('execution_root')!); + const relativeSourcePath = path.relative( + this.bazelInfo.get('workspace')!, + args.source.path!, + ); + const sourcePathInExternal = path.join( + this.bazelInfo.get('output_base')!, + 'external', + workspaceName, + relativeSourcePath, + ); + this.sourceBreakpoints.set(args.source.path!, args.breakpoints || []); + this.sourceBreakpoints.set(sourcePathInExternal, args.breakpoints || []); + + // Convert to Bazel breakpoints. + const bazelBreakpoints = new Array(); + for (const [sourcePath, breakpoints] of this.sourceBreakpoints) { + for (const breakpoint of breakpoints) { + bazelBreakpoints.push({ + expression: breakpoint.condition, + location: { + lineNumber: breakpoint.line, + path: sourcePath, + }, + }, + ); + } + } + + this.bazelConnection!.sendRequest({ + setBreakpoints: { + breakpoint: bazelBreakpoints, + }, + }); + this.sendResponse(response); + } + + // Thread, stack frame, and variable requests + + protected async threadsRequest(response: DebugProtocol.ThreadsResponse) { + response.body = { + threads: Array.from(this.pausedThreads.values()).map((bazelThread) => { + return new Thread(Long.fromValue(bazelThread.id!).toNumber(), bazelThread.name!); + }), + }; + this.sendResponse(response); + } + + protected async stackTraceRequest( + response: DebugProtocol.StackTraceResponse, + args: DebugProtocol.StackTraceArguments, + ) { + const event = await this.bazelConnection!.sendRequest({ + listFrames: { + threadId: args.threadId, + }, + }); + + if (event.listFrames) { + const bazelFrames = event.listFrames.frame; + const vsFrames = new Array(); + for (const bazelFrame of bazelFrames || []) { + const frameHandle = this.frameHandles.create(bazelFrame); + this.frameThreadIds.set(frameHandle, args.threadId); + + const location = bazelFrame.location; + const vsFrame = new StackFrame( + frameHandle, + bazelFrame.functionName || '', + ); + if (location && location.path) { + // Resolve the real path to the file, which will make sure that when + // the user interacts with the stack frame, VS Code loads the file + // from it's actual path instead of from a location inside Bazel's + // output base. + const sourcePath = fs.realpathSync(location.path); + vsFrame.source = new Source(path.basename(sourcePath), sourcePath); + vsFrame.line = location.lineNumber || 1; + } + vsFrames.push(vsFrame); + } + response.body = { stackFrames: vsFrames, totalFrames: vsFrames.length }; + } + + this.sendResponse(response); + } + + protected scopesRequest( + response: DebugProtocol.ScopesResponse, + args: DebugProtocol.ScopesArguments, + ) { + const frameThreadId = this.frameThreadIds.get(args.frameId)!; + const bazelFrame = this.frameHandles.get(args.frameId)!; + + const vsScopes = new Array(); + for (const bazelScope of bazelFrame.scope || []) { + const scopeHandle = this.variableHandles.create(bazelScope); + const vsScope = new Scope(bazelScope.name!, scopeHandle); + vsScopes.push(vsScope); + + // Associate the thread ID from the frame with the scope so that it can be + // passed through to child values as well. + this.scopeThreadIds.set(scopeHandle, frameThreadId); + } + + response.body = { scopes: vsScopes }; + this.sendResponse(response); + } + + protected async variablesRequest( + response: DebugProtocol.VariablesResponse, + args: DebugProtocol.VariablesArguments, + ) { + let bazelValues: Value[]; + let threadId: number; + + const reference = args.variablesReference; + const scopeOrParentValue = this.variableHandles.get(reference); + if (scopeOrParentValue instanceof StarlarkScope) { + // If the reference is to a scope, then we ask for the thread ID + // associated with the scope so that we can associate it later with the + // top-level values in the scope. + threadId = this.scopeThreadIds.get(reference); + bazelValues = (scopeOrParentValue as StarlarkScope).binding; + } else if (scopeOrParentValue instanceof Value) { + // If the reference is to a value, we need to send a request to Bazel to + // get its child values. + threadId = this.valueThreadIds.get(reference); + bazelValues = (await this.bazelConnection.sendRequest({ + getChildren: skylark_debugging.GetChildrenRequest.create({ + threadId, + valueId: (scopeOrParentValue as Value).id, + }), + })).getChildren.children; + } else { + bazelValues = []; + threadId = 0; + } + + const variables = new Array(); + for (const value of bazelValues) { + let valueHandle: number; + if (value.hasChildren && value.id) { + // Record the value in a handle so that its children can be queried when + // the user expands it in the UI. We also record the thread ID for the + // value since we need it when we make that request later. + valueHandle = this.variableHandles.create(value); + this.valueThreadIds.set(valueHandle, threadId); + } else { + valueHandle = 0; + } + const variable = new Variable( + value.label, + value.description, + valueHandle, + ); + variables.push(variable); + } + + response.body = { variables }; + this.sendResponse(response); + } + + protected async evaluateRequest( + response: DebugProtocol.EvaluateResponse, + args: DebugProtocol.EvaluateArguments, + ) { + const threadId = this.frameThreadIds.get(args.frameId); + + const value = (await this.bazelConnection.sendRequest({ + evaluate: EvaluateRequest.create({ + statement: args.expression, + threadId, + }), + })).evaluate.result; + + let valueHandle: number; + if (value.hasChildren && value.id) { + // Record the value in a handle so that its children can be queried when + // the user expands it in the UI. We also record the thread ID for the + // value since we need it when we make that request later. + valueHandle = this.variableHandles.create(value); + this.valueThreadIds.set(valueHandle, threadId); + } else { + valueHandle = 0; + } + + response.body = { + result: value.description, + variablesReference: valueHandle, + }; + this.sendResponse(response); + } + + // Execution/control flow requests + + protected continueRequest( + response: DebugProtocol.ContinueResponse, + args: DebugProtocol.ContinueArguments, + ) { + response.body = { allThreadsContinued: false }; + this.sendControlFlowRequest(args.threadId, Stepping.NONE); + this.sendResponse(response); + } + + protected nextRequest( + response: DebugProtocol.NextResponse, + args: DebugProtocol.NextArguments, + ) { + this.sendControlFlowRequest(args.threadId, Stepping.OVER); + this.sendResponse(response); + } + + protected stepInRequest( + response: DebugProtocol.StepInResponse, + args: DebugProtocol.StepInArguments, + ) { + this.sendControlFlowRequest(args.threadId, Stepping.INTO); + this.sendResponse(response); + } + + protected stepOutRequest( + response: DebugProtocol.StepOutResponse, + args: DebugProtocol.StepOutArguments, + ) { + this.sendControlFlowRequest(args.threadId, Stepping.OUT); + this.sendResponse(response); + } + + /** + * Sends a request to Bazel to continue the execution of the given thread, + * with stepping behavior. + * + * @param threadId The identifier of the thread to continue. + * @param stepping The stepping behavior of the request (OVER, INTO, OUT, or + * NONE). + */ + private sendControlFlowRequest( + threadId: number, + stepping: Stepping, + ) { + // Clear out all the cached state when the user resumes a thread. + this.frameHandles.clear(); + this.variableHandles.clear(); + this.frameThreadIds.clear(); + this.scopeThreadIds.clear(); + this.valueThreadIds.clear(); + + this.bazelConnection.sendRequest({ + continueExecution: ContinueExecutionRequest.create({ + stepping, + threadId, + }), + }); + } + + /** + * Dispatches an asynchronous Bazel debug event received from the server. + * + * @param event The event that was received from the server. + */ + private handleBazelEvent(event: DebugEvent) { + switch (event.payload) { + case 'threadPaused': + this.handleThreadPaused(event.threadPaused); + break; + case 'threadContinued': + this.handleThreadContinued(event.threadContinued); + break; + default: + break; + } + } + + private handleThreadPaused(event: ThreadPausedEvent) { + this.pausedThreads.set(number64(event.thread.id), event.thread); + this.sendEvent(new StoppedEvent('a breakpoint', number64(event.thread.id))); + } + + private handleThreadContinued( + event: ThreadContinuedEvent, + ) { + this.sendEvent(new ContinuedEvent(number64(event.threadId))); + this.pausedThreads.delete(number64(event.threadId)); + } + + /** + * Returns the path to the Bazel executable from launch arguments, or a + * reasonable default. + */ + private bazelExecutable(launchArgs: ILaunchRequestArguments): string { + const bazelExecutable = launchArgs.bazelExecutablePath; + if (!bazelExecutable || bazelExecutable.length === 0) { + return 'bazel'; + } + return bazelExecutable; + } + + /** + * Invokes {@code bazel info} and returns the information in a map. + * + * @param bazelExecutable The name/path of the Bazel executable. + * @param cwd The working directory in which Bazel should be launched. + */ + private getBazelInfo( + bazelExecutable: string, + cwd: string, + ): Promise> { + return new Promise((resolve, reject) => { + const execOptions = { + cwd, + // The maximum amount of data allowed on stdout. 500KB should be plenty + // of `bazel info`, but if this becomes problematic we can switch to the + // event-based `child_process` APIs instead. + maxBuffer: 500 * 1024, + }; + child_process.execFile( + bazelExecutable, + ['info'], + execOptions, + (error: Error, stdout: string, stderr: string) => { + if (error) { + reject(error); + } else { + const keyValues = new Map(); + const lines = stdout.trim().split('\n'); + for (const line of lines) { + // Windows paths can have >1 ':', so can't use line.split(":", 2) + const splitterIndex = line.indexOf(':'); + const key = line.substring(0, splitterIndex); + const value = line.substring(splitterIndex + 1); + keyValues.set(key.trim(), value.trim()); + } + resolve(keyValues); + } + }, + ); + }); + } + + /** + * Launches the Bazel process to be debugged. + * + * @param bazelExecutable The name/path of the Bazel executable. + * @param cwd The working directory in which Bazel should be launched. + * @param args The command line arguments to pass to Bazel. + */ + private launchBazel(bazelExecutable: string, cwd: string, args: string[]) { + const options = { cwd }; + + this.bazelProcess = child_process + .spawn(bazelExecutable, args, options) + .on('error', (error) => { + this.onBazelTerminated(error); + }) + .on('exit', (code, signal) => { + this.onBazelTerminated({ code, signal }); + }); + this.isBazelRunning = true; + + // We intentionally render stderr from Bazel as stdout in VS Code so that + // normal build log text shows up as white instead of red. ANSI color codes + // are applied as expected in either case. + this.bazelProcess.stdout.on('data', (data: string) => { + this.onBazelOutput(data); + }); + this.bazelProcess.stderr.on('data', (data: string) => { + this.onBazelOutput(data); + }); + } + + /** + * Called when the Bazel child process as terminated. + * + * @param result The outcome of the process; either an object containing the + * exit code and signal by which it terminated, or an {@code Error} + * describing an exceptional situation that occurred. + */ + private onBazelTerminated(result: { code: number; signal: string } | Error) { + // TODO(allevato): Handle abnormal termination. + if (this.isBazelRunning) { + this.isBazelRunning = false; + this.sendEvent(new TerminatedEvent()); + } + } + + /** + * Called when the Bazel child process has produced output on stdout or + * stderr. + * + * @param data The string that was output. + */ + private onBazelOutput(data: string) { + this.sendEvent(new OutputEvent(data.toString(), 'stdout')); + } + + /** + * Sends output events to the client to log messages and optional + * pretty-printed objects. + */ + private debugLog(message: string, ...objects: object[]) { + this.sendEvent(new OutputEvent(message, 'console')); + for (const object of objects) { + const s = JSON.stringify(object, undefined, 2); + if (s) { + this.sendEvent(new OutputEvent(`\n${s}`, 'console')); + } + } + this.sendEvent(new OutputEvent('\n', 'console')); + } +} + +// Start the debugging session. +DebugSession.run(BazelDebugSession); \ No newline at end of file diff --git a/src/starlark/debug/connection.ts b/src/starlark/debug/connection.ts new file mode 100644 index 00000000..cc0a4fe1 --- /dev/null +++ b/src/starlark/debug/connection.ts @@ -0,0 +1,216 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { EventEmitter } from 'events'; +import * as net from 'net'; +import * as protobuf from 'protobufjs'; +import { DebugEvent } from '../../proto/starlark_debugging/DebugEvent'; +import { DebugRequest } from '../../proto/starlark_debugging/DebugRequest'; +import Long = require('long'); + +/** + * Manages the connection between the debug adapter and the debugging server + * running in Bazel. + * + * This class acts as a Node event emitter for asynchronous events from the + * server, and provides a Promise-based API for handling responses from + * requests. + */ +export class BazelDebugConnection extends EventEmitter { + + /** The socket used to connect to the debugging server in Bazel. */ + private socket: net.Socket | undefined; + + /** + * A buffer that stores data read from the socket until a complete message is + * available. + */ + private buffer: Buffer | undefined; + + /** Reads protobuf messages from the buffer. */ + private reader: protobuf.Reader | undefined; + + /** + * A monotonically increasing sequence number used to uniquely identify + * requests. + */ + private sequenceNumber = 1; + + /** + * Keeps track of promises for responses that have not yet been received from + * the server. + * + * When the debug adapter sends a request to Bazel, a promise for the response + * is created and the resolve function for that promise is stored in this map, + * keyed by the sequence number of the request. Then, when the response with + * the matching sequence number is received from the server, we can look up + * the resolver, call it, and continue execution of the client code waiting on + * the promise. + */ + private pendingResolvers = new Map< + string, + (event: DebugEvent) => void + >(); + + /** + * Initializes a new debug connection and connects to the server. + * + * @param debugRequestType The protobuf type for the + * starlark_debugging.DebugRequest message. + * @param debugEventType The protobuf type for the + * starlark_debugging.DebugEvent message. + * @param host The host name to connect to. + * @param port The port number to connect to. + */ + public constructor( + private debugRequestType: protobuf.Type, + private debugEventType: protobuf.Type, + host: string, + port: number, + private logger: (message: string, ...objects: any[]) => void, + ) { + super(); + + this.tryToConnect(host, port); + } + + /** + * Sends a request to the Bazel debug server and returns a promise for its + * response. + * + * @param options The options for the request. The sequence number will be + * populated by this method. + * @returns A {@code Promise} for the response to the request. + */ + public sendRequest( + request: DebugRequest, + ): Promise { + request.sequenceNumber = this.sequenceNumber++; + + const promise = new Promise((resolve) => { + this.pendingResolvers.set(Long.fromValue(request.sequenceNumber!).toString(), resolve); + }); + + const writer = this.debugRequestType.encodeDelimited(request); + const bytes = writer.finish(); + this.socket!.write(bytes); + + return promise; + } + + /** + * Makes an attempt to connect to the Bazel debug server. + * + * If the connection is not successful (for example, if Bazel is still + * starting up and has not opened the socket yet), this function will wait one + * second and make another attempt, up to a total of five attempts. If the + * fifth attempt is unsuccessful, an error will be thrown. + * + * @param host The host name to connect to. + * @param port The port number to connect to. + * @param attempt The number of the attempt being made. Defaults to 1. + */ + private tryToConnect(host: string, port: number, attempt: number = 1) { + const socket = new net.Socket() + .on('connect', () => { + this.socket = socket; + socket.on('data', (chunk) => { + this.consumeChunk(chunk); + }); + this.emit('connect'); + }) + .on('error', (error) => { + if (attempt <= 5) { + setTimeout(() => { + this.tryToConnect(host, port, attempt + 1); + }, 1000); + } else { + this.logger( + 'Could not connect to Bazel debug server after 5 seconds', + ); + // TODO(allevato): Improve the error case. + throw error; + } + }); + socket.connect( + port, + host, + ); + } + + /** + * Consumes a chunk of data from the socket and decodes an event/response out + * of the data received so far, if possible. + * + * If there is not enough data in the buffer for a full event, this method + * tracks the chunk and then the connection waits for more data to try to + * decode again. + * + * @param chunk A chunk of bytes from the socket. + */ + private consumeChunk(chunk: Buffer) { + let event: DebugEvent | undefined = undefined; + this.append(chunk); + + while (true) { + try { + event = this.debugEventType.decodeDelimited(this.reader!) as DebugEvent; + } catch (err) { + // This occurs if there is a partial message in the buffer; stop reading + // and wait for more data. + return; + } + + this.collapse(); + + const sequenceNumber = Long.fromValue(event!.sequenceNumber!); + // Do the right thing whether the sequence number comes in as either a + // number or a Long (which is an object with separate low/high ints.) + if (sequenceNumber.toString() !== '0') { + const handler = this.pendingResolvers.get( + sequenceNumber.toString(), + ); + if (handler) { + this.pendingResolvers.delete(sequenceNumber.toString()); + handler(event!); + } + } else { + this.emit('event', event); + } + } + } + + /** Appends a chunk of data to the buffer, resizing it as needed. */ + private append(chunk: Buffer) { + if (!this.buffer) { + this.buffer = chunk; + } else { + // The reader's position indicates where it last stopped trying to read + // data from the buffer. In the event of an unsuccessful read, this tells + // us how much data is in the buffer. + const pos = this.reader!.pos; + const newBuffer = Buffer.alloc(pos + chunk.byteLength); + this.buffer.copy(newBuffer, 0); + chunk.copy(newBuffer, pos); + this.buffer = newBuffer; + } + this.reader = protobuf.Reader.create(this.buffer); + } + + /** Collapses the buffer so that any data already read is removed. */ + private collapse() { + this.buffer = this.buffer!.slice(this.reader!.pos); + this.reader = protobuf.Reader.create(this.buffer); + } +} \ No newline at end of file diff --git a/src/starlark/debug/dapClient.ts b/src/starlark/debug/dapClient.ts new file mode 100644 index 00000000..97a6ae34 --- /dev/null +++ b/src/starlark/debug/dapClient.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------- + * Copyright 2020 The Go Authors. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ +import { EventEmitter } from 'events'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import stream = require('stream'); + + +// DapClient implements a simple client for the DAP protocol. It's initialized +// with a pair of streams that the caller creates and enables sending and +// receiving DAP messages over these streams. After calling connect(): +// +// - For sending messages call send(). +// - For receiving messages, subscibe to events this class emits. +// - 'event', 'respones', 'request' - each carrying an appropriate +// DebugProtocol type as an argument. +export class DAPClient extends EventEmitter { + private static readonly TWO_CRLF = '\r\n\r\n'; + + private outputStream: stream.Writable | undefined; + + private rawData = Buffer.alloc(0); + private contentLength: number = -1; + + constructor() { + super(); + } + + public send(req: any): void { + const json = JSON.stringify(req); + this.outputStream!.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`, 'utf8'); + } + + // Connect this client to a server, which is represented by read and write + // streams. Before this method is called, send() won't work and no messages + // from the server will be delivered. + protected connect(readable: stream.Readable, writable: stream.Writable): void { + this.outputStream = writable; + + readable.on('data', (data: Buffer) => { + this.handleData(data); + }); + } + + // Implements parsing of the DAP protocol. We cannot use ProtocolClient from + // the vscode-debugadapter package, because it's not exported and is not + // meant for external usage. See + // https://github.com/microsoft/vscode-debugadapter-node/issues/232 + private handleData(data: Buffer): void { + this.rawData = Buffer.concat([this.rawData, data]); + + while (true) { + if (this.contentLength >= 0) { + if (this.rawData.length >= this.contentLength) { + const message = this.rawData.toString('utf8', 0, this.contentLength); + this.rawData = this.rawData.slice(this.contentLength); + this.contentLength = -1; + if (message.length > 0) { + this.dispatch(message); + } + continue; // there may be more complete messages to process + } + } else { + const idx = this.rawData.indexOf(DAPClient.TWO_CRLF); + if (idx !== -1) { + const header = this.rawData.toString('utf8', 0, idx); + const lines = header.split('\r\n'); + for (const line of lines) { + const pair = line.split(/: +/); + if (pair[0] === 'Content-Length') { + this.contentLength = +pair[1]; + } + } + this.rawData = this.rawData.slice(idx + DAPClient.TWO_CRLF.length); + continue; + } + } + break; + } + } + + private dispatch(body: string): void { + const rawData = JSON.parse(body); + + if (rawData.type === 'event') { + const event = rawData; + this.emit('event', event); + } else if (rawData.type === 'response') { + const response = rawData; + this.emit('response', response); + } else if (rawData.type === 'request') { + const request = rawData; + this.emit('request', request); + } else { + throw new Error(`unknown message ${JSON.stringify(rawData)}`); + } + } +} \ No newline at end of file diff --git a/src/starlark/debug/handles.ts b/src/starlark/debug/handles.ts new file mode 100644 index 00000000..8f277154 --- /dev/null +++ b/src/starlark/debug/handles.ts @@ -0,0 +1,46 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * A map-like class that stores data in an integer-indexed structure where the + * indexes are autogenerated. + */ +export class Handles { + /** The auto-incremented index of the next handle to be created. */ + private nextHandle = 1; + + /** + * The values for which handles have been created, stored with the handle + * number as the key. + */ + private values = new Map(); + + /** Creates and returns a new handle for the given value. */ + public create(value: T): number { + const handle = this.nextHandle++; + this.values.set(handle, value); + return handle; + } + + /** Retrieves the value with the given handle. */ + public get(handle: number): T | undefined { + return this.values.get(handle); + } + + /** Clears all the handles and stored values. */ + public clear() { + this.nextHandle = 1; + this.values = new Map(); + } +} \ No newline at end of file diff --git a/src/starlark/debug/main.ts b/src/starlark/debug/main.ts new file mode 100644 index 00000000..309926c9 --- /dev/null +++ b/src/starlark/debug/main.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------- + * Copyright 2020 The Go Authors. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +// This file is for running the godlvdap debug adapter as a standalone program +// in a separate process (e.g. when working in --server mode). +import { GoDlvDapDebugSession as StarlarkDapDebugSession } from './adapter'; + +StarlarkDapDebugSession.run(StarlarkDapDebugSession);