Skip to content

Commit e457942

Browse files
authored
[starlark debugger] Add launch support (#78)
* Add debug launch support
1 parent 8677f0e commit e457942

File tree

8 files changed

+134
-57
lines changed

8 files changed

+134
-57
lines changed

package.json

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"onUri",
2929
"onCommand:bsv.openExtensionSetting",
3030
"onCommand:workbench.view.extension.bazel-explorer",
31+
"onCommand:bsv.bzl.askForDebugTargetLabel",
3132
"onView:bsv.workspace",
3233
"onView:bazel-explorer",
3334
"onLanguage:bazel",
@@ -48,26 +49,65 @@
4849
"type": "starlark",
4950
"label": "Starlark Debug",
5051
"configurationAttributes": {
51-
"attach": {}
52+
"attach": {},
53+
"launch": {
54+
"required": [
55+
"targetLabel"
56+
],
57+
"properties": {
58+
"targetLabel": {
59+
"type": "string",
60+
"description": "bazel label of target to build+debug",
61+
"default": "${workspaceFolder}/${command:bsv.bzl.askForDebugTargetLabel}"
62+
},
63+
"extraBazelFlags": {
64+
"type": "array",
65+
"description": "Additional bazel flags to pass to the bazel build command during debugging",
66+
"items": {
67+
"type": "string"
68+
},
69+
"default": []
70+
}
71+
}
72+
}
5273
},
5374
"initialConfigurations": [
5475
{
5576
"type": "starlark",
5677
"request": "attach",
5778
"name": "Attach to a running Starlark Debug Server"
79+
},
80+
{
81+
"type": "starlark",
82+
"request": "launch",
83+
"name": "Launch a bazel starlark debug session",
84+
"targetLabel": "${workspaceFolder}/${command:AskForDebugTargetLabel}"
5885
}
5986
],
6087
"configurationSnippets": [
6188
{
6289
"label": "Starlark Debug: Attach",
63-
"description": "Configuration for attaching to a starlark debug adapter",
90+
"description": "A new configuration for attaching to a starlark debug adapter",
6491
"body": {
6592
"type": "starlark",
6693
"request": "attach",
6794
"name": "Attach to a running Starlark Debug Adapter"
6895
}
96+
},
97+
{
98+
"label": "Starlark Debug: Launch",
99+
"description": "A new configuration for launching a 'bazel build {target}'",
100+
"body": {
101+
"type": "starlark",
102+
"request": "launch",
103+
"name": "Starlark debug ${1://:target_label_to_debug}",
104+
"targetLabel": "${1://:target_label_to_debug}"
105+
}
69106
}
70-
]
107+
],
108+
"variables": {
109+
"AskForDebugTargetLabel": "extension.bazel-stack-vscode.bsv.bzl.askForDebugTargetLabel"
110+
}
71111
}
72112
],
73113
"configuration": {
@@ -137,6 +177,11 @@
137177
"description": "If false, disable the Starlark Debugger component",
138178
"default": true
139179
},
180+
"bsv.bzl.starlarkDebugger.autoLaunch": {
181+
"type": "boolean",
182+
"description": "If true, attempt to launch the debug adapter if not running",
183+
"default": false
184+
},
140185
"bsv.bzl.starlarkDebugger.debugAdapterExecutable": {
141186
"type": "string",
142187
"description": "Executable for the starlark debug adapter. If unset, defaults to the bzl executable."

src/bezel/codesearch.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ export interface OutputChannel {
4040
*/
4141
export class CodeSearch
4242
extends RunnableComponent<CodeSearchConfiguration>
43-
implements vscode.Disposable
44-
{
43+
implements vscode.Disposable {
4544
private readonly output: vscode.OutputChannel;
4645
private readonly renderer: CodesearchRenderer;
4746
private panel: CodesearchPanel | undefined;
@@ -79,7 +78,7 @@ export class CodeSearch
7978
}
8079
}
8180

82-
async stopInternal() {}
81+
async stopInternal() { }
8382

8483
async handleCommandCodesearch(label: string): Promise<void> {
8584
const ws = await this.bzl.getWorkspace();
@@ -190,7 +189,9 @@ export class CodeSearch
190189
try {
191190
return this.handleCodeSearch(opts);
192191
} catch (e) {
193-
vscode.window.showErrorMessage(`could not handle codesearch command: ${e.message}`);
192+
if (e instanceof Error) {
193+
vscode.window.showErrorMessage(`could not handle codesearch command: ${e.message}`);
194+
}
194195
}
195196
}
196197

@@ -226,8 +227,8 @@ export class CodeSearch
226227
name: scopeName,
227228
});
228229
} catch (err) {
229-
if (err.code !== grpc.status.NOT_FOUND) {
230-
const e: grpc.ServiceError = err as grpc.ServiceError;
230+
const e: grpc.ServiceError = err as grpc.ServiceError;
231+
if (e.code && e.code !== grpc.status.NOT_FOUND) {
231232
vscode.window.showErrorMessage(`getScope: ${e.message} (${e.code})`);
232233
}
233234
}

src/bezel/configuration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export interface LanguageServerConfiguration extends ComponentConfiguration {
133133
}
134134

135135
export interface StarlarkDebuggerConfiguration extends ComponentConfiguration {
136+
autoLaunch: boolean;
136137
debugAdapterExecutable: string,
137138
debugAdapterCommand: string[],
138139
debugAdapterHost: string,
@@ -214,6 +215,7 @@ export class StarlarkDebuggerSettings extends Settings<StarlarkDebuggerConfigura
214215
): Promise<StarlarkDebuggerConfiguration> {
215216
const cfg: StarlarkDebuggerConfiguration = {
216217
enabled: config.get<boolean>('enabled', true),
218+
autoLaunch: config.get<boolean>('autoLaunch', false),
217219
debugAdapterExecutable: config.get<string>('debugAdapterExecutable', ''),
218220
debugAdapterCommand: config.get<string[]>('debugAdapterCommand', ['debug', 'adapter']),
219221
debugAdapterHost: config.get<string>('debugAdapterHost', 'localhost'),

src/bezel/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export enum CommandName {
4949
CopyLabel = 'bsv.bzl.copyLabel',
5050
CopyToClipboard = 'bsv.bzl.copyToClipboard',
5151
DebugBuild = 'bsv.bzl.debugBuild',
52-
DebugTest = 'bsv.bzl.debugTest',
52+
AskForDebugTargetLabel = 'bsv.bzl.askForDebugTargetLabel',
5353
LaunchDebugAdapter = 'bsv.bzl.starlarkDebugger.launch',
5454
LaunchRemoteCache = 'bsv.bzl.remoteCache.launch',
5555
LaunchBzlServer = 'bsv.bzl.server.launch',

src/bezel/debugger.ts

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as vscode from 'vscode';
2+
23
import {
34
BazelConfiguration,
45
BzlConfiguration,
@@ -7,8 +8,9 @@ import {
78
} from './configuration';
89
import { CommandName } from './constants';
910
import { Settings } from './settings';
10-
import { LaunchableComponent, LaunchArgs, Status } from './status';
11+
import { LaunchableComponent, LaunchArgs, Status, StatusError } from './status';
1112
import { SocketDebugClient, LogLevel } from "node-debugprotocol-client";
13+
import { isDefined } from 'vscode-common/out/types';
1214

1315

1416
export class StarlarkDebugger
@@ -25,39 +27,34 @@ export class StarlarkDebugger
2527

2628
this.disposables.push(vscode.debug.registerDebugAdapterDescriptorFactory('starlark', this));
2729
this.disposables.push(vscode.debug.registerDebugConfigurationProvider('starlark', this));
30+
31+
vscode.commands.registerCommand(CommandName.AskForDebugTargetLabel, this.handleCommandAskForDebugTargetLabel, this.disposables);
32+
}
33+
34+
async handleCommandAskForDebugTargetLabel(): Promise<string | undefined> {
35+
return vscode.window.showInputBox({
36+
placeHolder: 'Please enter the label of bazel build target for the debug session',
37+
value: '//:your_build_target_here'
38+
});
2839
}
2940

3041
/**
31-
* Invoke is typically triggered from a 'debug' code action click. It tries to check
32-
* that the debug adapter is running and then starts a debug session.
42+
* Invoke is typically triggered from a 'debug' code action click. It tries
43+
* to check that the debug adapter is running and then starts a debug session.
3344
* @param command
3445
* @param label
3546
* @returns
3647
*/
3748
async invoke(command: string, label: string): Promise<boolean> {
38-
const bazelSettings = await this.bazelSettings.get();
3949
const debugSettings = await this.settings.get();
4050

41-
const action = await vscode.window.showInformationMessage(debugInfoMessage(), 'OK', 'Cancel');
42-
if (action !== 'OK') {
43-
return false;
44-
}
45-
46-
const args = [command, label];
47-
args.push(...bazelSettings.buildFlags, ...bazelSettings.starlarkDebugFlags);
48-
49-
if (this.status !== Status.READY && this.status !== Status.DISABLED) {
50-
await this.handleCommandLaunch();
51-
}
52-
53-
await vscode.commands.executeCommand(CommandName.Invoke, args);
54-
5551
return vscode.debug.startDebugging(
5652
vscode.workspace.getWorkspaceFolder(this.workspaceFolder),
5753
{
5854
type: 'starlark',
59-
name: 'Attach to a Starlark Debug Session',
60-
request: 'attach',
55+
name: 'Launch to a Starlark Debug Session for ' + label,
56+
request: 'launch',
57+
targetLabel: label,
6158
debugServerHost: debugSettings.debugAdapterHost,
6259
debugServerPort: debugSettings.debugAdapterPort
6360
},
@@ -69,7 +66,8 @@ export class StarlarkDebugger
6966
* @override
7067
*/
7168
async shouldLaunch(e: Error): Promise<boolean> {
72-
return false;
69+
const cfg = await this.settings.get();
70+
return cfg.autoLaunch;
7371
}
7472

7573
/**
@@ -89,7 +87,7 @@ export class StarlarkDebugger
8987
await client.connectAdapter();
9088
} catch (e) {
9189
if (isTCPConnectionError(e) && e.code === 'ECONNREFUSED') {
92-
throw new Error(`Debug Adapter is not running`);
90+
throw new StatusError(`Debug Adapter is not running`, Status.STOPPED);
9391
} else {
9492
throw e;
9593
}
@@ -118,7 +116,7 @@ export class StarlarkDebugger
118116

119117
return {
120118
command: args.map(a => a.replace('${workspaceFolder}', this.workspaceFolder.fsPath)),
121-
showSuccessfulLaunchTerminal: true,
119+
showSuccessfulLaunchTerminal: false,
122120
showFailedLaunchTerminal: true,
123121
};
124122
}
@@ -140,7 +138,7 @@ export class StarlarkDebugger
140138
* Massage a debug configuration just before a debug session is being
141139
* launched, e.g. add all missing attributes to the debug configuration.
142140
*/
143-
resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
141+
async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration | null | undefined> {
144142
return config;
145143
}
146144

@@ -163,18 +161,36 @@ export class StarlarkDebugger
163161
* @param token A cancellation token.
164162
* @return The resolved debug configuration or undefined or null.
165163
*/
166-
resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
167-
return debugConfiguration;
168-
}
164+
async resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration | undefined> {
165+
let targetLabel = config.targetLabel;
166+
if (!targetLabel) {
167+
targetLabel = await this.handleCommandAskForDebugTargetLabel();
168+
}
169+
if (!targetLabel) {
170+
vscode.window.showInformationMessage(`A label for the "bazel build" command is required. Please add it to your launch configuration.`);
171+
return;
172+
}
169173

170-
}
174+
// launch the bazel debugger if this is a launch config
175+
if (config.request === 'launch') {
176+
const bazelSettings = await this.bazelSettings.get();
177+
const flags = bazelSettings.starlarkDebugFlags || [];
178+
const extraFlags = config.extraBazelFlags || [];
179+
180+
await vscode.commands.executeCommand(CommandName.Invoke,
181+
['build', targetLabel, ...flags, ...extraFlags].filter(arg => isDefined(arg)));
182+
}
183+
184+
// launch the debug adapter if it not already running
185+
if (this.status !== Status.READY && this.status !== Status.DISABLED) {
186+
// this needs to wait until the thing is actually running!
187+
await this.handleCommandLaunch();
188+
this.restart();
189+
}
190+
191+
return config;
192+
}
171193

172-
function debugInfoMessage(): string {
173-
return (
174-
'Running Bazel in debug mode blocks until the debug adapter client attaches. ' +
175-
"It is recommended to make changes to BUILD/bzl files in the area of interest to defeat Bazel's aggressive caching mechanism. " +
176-
'Are you sure you want to continue?'
177-
);
178194
}
179195

180196
interface TCPConnectionError {

src/bezel/feature.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export class BzlFeature implements vscode.Disposable {
6565
this.addRedoableCommand(CommandName.Build, this.handleCommandBuild);
6666
this.addRedoableCommand(CommandName.DebugBuild, this.handleCommandBuildDebug);
6767
this.addRedoableCommand(CommandName.Test, this.handleCommandTest);
68-
this.addRedoableCommand(CommandName.DebugTest, this.handleCommandTestDebug);
6968

7069
// ======= Settings =========
7170

@@ -235,10 +234,6 @@ export class BzlFeature implements vscode.Disposable {
235234
return this.starlarkDebugger.invoke('build', label);
236235
}
237236

238-
async handleCommandTestDebug(label: string): Promise<boolean> {
239-
return this.starlarkDebugger.invoke('test', label);
240-
}
241-
242237
async handleCommandInvoke(args: string[]): Promise<void> {
243238
if (this.invocations.status !== Status.READY) {
244239
return this.bazelServer.runInBazelTerminal(args);

src/bezel/status.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export abstract class RunnableComponent<T extends ComponentConfiguration>
9393

9494
if (err instanceof DisabledError) {
9595
this.setDisabled(true);
96+
} else if (err instanceof StatusError) {
97+
this.setStatus(err.status);
9698
} else {
9799
this.setStatus(Status.ERROR);
98100
}
@@ -304,6 +306,10 @@ export abstract class LaunchableComponent<
304306
try {
305307
await this.launchInternal();
306308
} catch (e) {
309+
if (e instanceof StatusError) {
310+
// handle this in the caller
311+
throw e;
312+
}
307313
if (e instanceof Error) {
308314
if (await this.shouldLaunch(e)) {
309315
this.handleCommandLaunch();
@@ -398,4 +404,12 @@ export class DisabledError extends Error {
398404
constructor(msg: string) {
399405
super(msg);
400406
}
401-
}
407+
}
408+
409+
// StatusError can thrown by subclasses in the .launchInternal method to set the
410+
// status in the catch.
411+
export class StatusError extends Error {
412+
constructor(msg: string, public readonly status: Status) {
413+
super(msg);
414+
}
415+
}

0 commit comments

Comments
 (0)