diff --git a/src/extension/command_variables.ts b/src/extension/command_variables.ts new file mode 100644 index 00000000..bc729d2c --- /dev/null +++ b/src/extension/command_variables.ts @@ -0,0 +1,143 @@ +// Copyright 2024 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 path from "path"; +import * as vscode from "vscode"; +import { getDefaultBazelExecutablePath } from "./configuration"; +import { BazelWorkspaceInfo } from "../bazel"; +import { BazelCQuery } from "../bazel/bazel_cquery"; +import { BazelInfo } from "../bazel/bazel_info"; + +/** + * Get the output of the given target. + * + * If there are multiple outputs, a quick-pick window will be opened asking the + * user to choose one. + * + * The `bazel.getTargetOutput` command can be used in launch configurations to + * obtain the path to an executable built by Bazel. For example, you can set the + * "program" attribute of a launch configuration to an input variable: + * + * ``` + * "program": "${input:binaryOutputLocation}" + * ``` + * + * Then define a command input variable: + * + * ``` + * "inputs": [ + * { + * "id": "binaryOutputLocation", + * "type": "command", + * "command": "bazel.getTargetOutput", + * "args": ["//my/binary:target"], + * } + * ] + * ``` + * + * Additional Bazel flags can be provided: + * + * ``` + * "inputs": [ + * { + * "id": "debugOutputLocation", + * "type": "command", + * "command": "bazel.getTargetOutput", + * "args": ["//my/binary:target", ["--compilation_mode", "dbg"]], + * } + * ] + * ``` + */ +async function bazelGetTargetOutput( + target: string, + options: string[] = [], +): Promise { + // Workaround for https://github.com/microsoft/vscode/issues/167970 + if (Array.isArray(target)) { + options = (target[1] || []) as string[]; + target = target[0] as string; + } + const workspaceInfo = await BazelWorkspaceInfo.fromWorkspaceFolders(); + if (!workspaceInfo) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + vscode.window.showInformationMessage( + "Please open a Bazel workspace folder to use this command.", + ); + + return; + } + const outputPath = await new BazelInfo( + getDefaultBazelExecutablePath(), + workspaceInfo.bazelWorkspacePath, + ).run("output_path"); + const outputs = await new BazelCQuery( + getDefaultBazelExecutablePath(), + workspaceInfo.bazelWorkspacePath, + ).queryOutputs(target, options); + switch (outputs.length) { + case 0: + throw new Error(`Target ${target} has no outputs.`); + case 1: + return path.join(outputPath, "..", outputs[0]); + default: + return await vscode.window.showQuickPick(outputs, { + placeHolder: `Pick an output of ${target}`, + }); + } +} + +/** + * Get the output of `bazel info` for the given key. + * + * If there are multiple outputs, a quick-pick window will be opened asking the + * user to choose one. + */ +async function bazelInfo(key: string): Promise { + const workspaceInfo = await BazelWorkspaceInfo.fromWorkspaceFolders(); + if (!workspaceInfo) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + vscode.window.showInformationMessage( + "Please open a Bazel workspace folder to use this command.", + ); + return; + } + return new BazelInfo( + getDefaultBazelExecutablePath(), + workspaceInfo.bazelWorkspacePath, + ).run(key); +} + +/** + * Activate all "command variables" + */ +export function activateCommandVariables(): vscode.Disposable[] { + return [ + vscode.commands.registerCommand( + "bazel.getTargetOutput", + bazelGetTargetOutput, + ), + ...[ + "bazel-bin", + "bazel-genfiles", + "bazel-testlogs", + "execution_root", + "output_base", + "output_path", + ].map((key) => + vscode.commands.registerCommand(`bazel.info.${key}`, () => + bazelInfo(key), + ), + ), + ]; +} diff --git a/src/extension/extension.ts b/src/extension/extension.ts index 2e4e0c85..56a00fba 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as path from "path"; import * as vscode from "vscode"; import { LanguageClient, @@ -32,8 +31,6 @@ import { IBazelCommandAdapter, parseExitCode, } from "../bazel"; -import { BazelCQuery } from "../bazel/bazel_cquery"; -import { BazelInfo } from "../bazel/bazel_info"; import { BuildifierDiagnosticsManager, BuildifierFormatProvider, @@ -45,6 +42,7 @@ import { BazelGotoDefinitionProvider } from "../definition/bazel_goto_definition import { BazelTargetSymbolProvider } from "../symbols"; import { BazelWorkspaceTreeProvider } from "../workspace-tree"; import { getDefaultBazelExecutablePath } from "./configuration"; +import { activateCommandVariables } from "./command_variables"; /** * Called when the extension is activated; that is, when its first command is @@ -131,22 +129,6 @@ export async function activate(context: vscode.ExtensionContext) { "bazel.copyTargetToClipboard", bazelCopyTargetToClipboard, ), - vscode.commands.registerCommand( - "bazel.getTargetOutput", - bazelGetTargetOutput, - ), - ...[ - "bazel-bin", - "bazel-genfiles", - "bazel-testlogs", - "execution_root", - "output_base", - "output_path", - ].map((key) => - vscode.commands.registerCommand(`bazel.info.${key}`, () => - bazelInfo(key), - ), - ), // CodeLens provider for BUILD files vscode.languages.registerCodeLensProvider( [{ pattern: "**/BUILD" }, { pattern: "**/BUILD.bazel" }], @@ -170,6 +152,8 @@ export async function activate(context: vscode.ExtensionContext) { vscode.tasks.onDidStartTask(onTaskStart), vscode.tasks.onDidStartTaskProcess(onTaskProcessStart), vscode.tasks.onDidEndTaskProcess(onTaskProcessEnd), + // Command variables + ...activateCommandVariables(), ); // Notify the user if buildifier is not available on their path (or where @@ -490,105 +474,6 @@ function bazelCopyTargetToClipboard(adapter: IBazelCommandAdapter | undefined) { vscode.env.clipboard.writeText(target); } -/** - * Get the output of the given target. - * - * If there are multiple outputs, a quick-pick window will be opened asking the - * user to choose one. - * - * The `bazel.getTargetOutput` command can be used in launch configurations to - * obtain the path to an executable built by Bazel. For example, you can set the - * "program" attribute of a launch configuration to an input variable: - * - * ``` - * "program": "${input:binaryOutputLocation}" - * ``` - * - * Then define a command input variable: - * - * ``` - * "inputs": [ - * { - * "id": "binaryOutputLocation", - * "type": "command", - * "command": "bazel.getTargetOutput", - * "args": ["//my/binary:target"], - * } - * ] - * ``` - * - * Additional Bazel flags can be provided: - * - * ``` - * "inputs": [ - * { - * "id": "debugOutputLocation", - * "type": "command", - * "command": "bazel.getTargetOutput", - * "args": ["//my/binary:target", ["--compilation_mode", "dbg"]], - * } - * ] - * ``` - */ -async function bazelGetTargetOutput( - target: string, - options: string[] = [], -): Promise { - // Workaround for https://github.com/microsoft/vscode/issues/167970 - if (Array.isArray(target)) { - options = (target[1] || []) as string[]; - target = target[0] as string; - } - const workspaceInfo = await BazelWorkspaceInfo.fromWorkspaceFolders(); - if (!workspaceInfo) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showInformationMessage( - "Please open a Bazel workspace folder to use this command.", - ); - - return; - } - const outputPath = await new BazelInfo( - getDefaultBazelExecutablePath(), - workspaceInfo.bazelWorkspacePath, - ).run("output_path"); - const outputs = await new BazelCQuery( - getDefaultBazelExecutablePath(), - workspaceInfo.bazelWorkspacePath, - ).queryOutputs(target, options); - switch (outputs.length) { - case 0: - throw new Error(`Target ${target} has no outputs.`); - case 1: - return path.join(outputPath, "..", outputs[0]); - default: - return await vscode.window.showQuickPick(outputs, { - placeHolder: `Pick an output of ${target}`, - }); - } -} - -/** - * Get the output of `bazel info` for the given key. - * - * If there are multiple outputs, a quick-pick window will be opened asking the - * user to choose one. - */ -async function bazelInfo(key: string): Promise { - const workspaceInfo = await BazelWorkspaceInfo.fromWorkspaceFolders(); - if (!workspaceInfo) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showInformationMessage( - "Please open a Bazel workspace folder to use this command.", - ); - return; - } - return new BazelInfo( - getDefaultBazelExecutablePath(), - workspaceInfo.bazelWorkspacePath, - ).run(key); -} - function onTaskStart(event: vscode.TaskStartEvent) { const bazelTaskInfo = getBazelTaskInfo(event.execution.task); if (bazelTaskInfo) {