Skip to content

Commit

Permalink
feat(workspace-tree): Workspace tree can show external local repository
Browse files Browse the repository at this point in the history
Besides the existing internal packages, handles handles external local
repository: the packages in pattern `@repo//package/path:target`.

We made the WorkspaceTreeProvider able to process only the following two patterns:
* `//internal/package/path`
* `@external//package/path`
And normalize the patterns queried in IBazelQuerier.

Simple test cases also added for package path handling.
  • Loading branch information
cwahbong committed Oct 22, 2024
1 parent 3ca663e commit 91a5211
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 113 deletions.
4 changes: 2 additions & 2 deletions src/bazel/bazel_workspace_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export class BazelWorkspaceInfo {
* belong to a workspace folder (for example, a standalone file loaded
* into the editor).
*/
private constructor(
constructor(
public readonly bazelWorkspacePath: string,
public readonly workspaceFolder: vscode.WorkspaceFolder | undefined,
public readonly workspaceFolder?: vscode.WorkspaceFolder,
) {}
}
16 changes: 10 additions & 6 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { activateWrapperCommands } from "./bazel_wrapper_commands";
* @param context The extension context.
*/
export async function activate(context: vscode.ExtensionContext) {
const workspaceTreeProvider = new BazelWorkspaceTreeProvider();
const workspaceTreeProvider = await BazelWorkspaceTreeProvider.forExtension();
context.subscriptions.push(workspaceTreeProvider);

const codeLensProvider = new BazelBuildCodeLensProvider(context);
Expand Down Expand Up @@ -95,11 +95,15 @@ export async function activate(context: vscode.ExtensionContext) {
),
// Commands
...activateWrapperCommands(),
vscode.commands.registerCommand("bazel.refreshBazelBuildTargets", () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
completionItemProvider.refresh();
workspaceTreeProvider.refresh();
}),
vscode.commands.registerCommand(
"bazel.refreshBazelBuildTargets",
async () => {
await Promise.allSettled([
completionItemProvider.refresh(),
workspaceTreeProvider.refresh(vscode.workspace.workspaceFolders),
]);
},
),
vscode.commands.registerCommand(
"bazel.copyTargetToClipboard",
bazelCopyTargetToClipboard,
Expand Down
34 changes: 22 additions & 12 deletions src/workspace-tree/bazel_package_tree_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class BazelPackageTreeItem
/**
* Initializes a new tree item with the given workspace path and package path.
*
* @param querier Querier for getting information inside a Bazel workspace.
* @param workspacePath The path to the VS Code workspace folder.
* @param packagePath The path to the build package that this item represents.
* @param parentPackagePath The path to the build package of the tree item
Expand All @@ -43,7 +44,7 @@ export class BazelPackageTreeItem
private readonly querier: IBazelQuerier,
private readonly workspaceInfo: BazelWorkspaceInfo,
private readonly packagePath: string,
private readonly parentPackagePath: string,
private readonly parentPackagePath?: string,
) {}

public mightHaveChildren(): boolean {
Expand All @@ -62,26 +63,35 @@ export class BazelPackageTreeItem
}

public getLabel(): string {
// If this is a top-level package, include the leading double-slash on the
// label.
if (this.parentPackagePath.length === 0) {
return `//${this.packagePath}`;
if (this.parentPackagePath === undefined) {
return this.packagePath;
}
// Otherwise, strip off the part of the package path that came from the
// parent item (along with the slash).
return this.packagePath.substring(this.parentPackagePath.length + 1);
// Strip off the part of the package path that came from the
// parent item.
const parentLength = this.parentPackagePath.length;
// (null)
// //a
//
// @repo//foo
// @repo//foo/bar
//
// @repo//
// @repo//foo
const diffIsLeadingSlash = this.packagePath[parentLength] === "/";
const prefixLength = diffIsLeadingSlash ? parentLength + 1 : parentLength;
return this.packagePath.substring(prefixLength);
}

public getIcon(): vscode.ThemeIcon {
return vscode.ThemeIcon.Folder;
}

public getTooltip(): string {
return `//${this.packagePath}`;
return this.packagePath;
}

public getCommand(): vscode.Command | undefined {
return undefined;
public getCommand(): Thenable<vscode.Command | undefined> {
return Promise.resolve(undefined);
}

public getContextValue(): string {
Expand All @@ -91,7 +101,7 @@ export class BazelPackageTreeItem
public getBazelCommandOptions(): IBazelCommandOptions {
return {
options: [],
targets: [`//${this.packagePath}`],
targets: [this.packagePath],
workspaceInfo: this.workspaceInfo,
};
}
Expand Down
38 changes: 31 additions & 7 deletions src/workspace-tree/bazel_target_tree_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
// limitations under the License.

import * as vscode from "vscode";
import * as fs from "fs/promises";
import { BazelWorkspaceInfo, QueryLocation } from "../bazel";
import { IBazelCommandAdapter, IBazelCommandOptions } from "../bazel";
import { blaze_query } from "../protos";
import { IBazelTreeItem } from "./bazel_tree_item";
import { getBazelRuleIcon } from "./icons";
import { BazelInfo } from "../bazel/bazel_info";
import { getDefaultBazelExecutablePath } from "../extension/configuration";

/** A tree item representing a build target. */
export class BazelTargetTreeItem
Expand All @@ -27,6 +30,7 @@ export class BazelTargetTreeItem
* Initializes a new tree item with the given query result representing a
* build target.
*
* @param querier Querier for getting information inside a Bazel workspace.
* @param target An object representing a build target that was produced by a
* query.
*/
Expand Down Expand Up @@ -55,16 +59,36 @@ export class BazelTargetTreeItem
}

public getTooltip(): string {
return `${this.target.rule.name}`;
return this.target.rule.name;
}

public getCommand(): vscode.Command | undefined {
public async getCommand(): Promise<vscode.Command | undefined> {
// Resolve the prefix if prefix is
// $(./prebuilts/bazel info output_base)/external/
const location = new QueryLocation(this.target.rule.location);
// Maybe we should cache this to prevent the repeating invocations.
const outputBase = await new BazelInfo(
getDefaultBazelExecutablePath(),
this.workspaceInfo.workspaceFolder.uri.fsPath,
).getOne("output_base");
let locationPath = location.path;
// If location is in pattern `${execRoot}/external/<repo>/...`, then it
// should be a file in local_repository(). Trying to remapping it back to
// the origin source folder by resolve the symlink
// ${execRoot}/external/<repo>.
const outputBaseExternalPath = `${outputBase}/external/`;
if (location.path.startsWith(outputBaseExternalPath)) {
const repoPath = location.path.substring(outputBaseExternalPath.length);
const repoPathMatch = repoPath.match(/^([^/]+)\/(.*)$/);
if (repoPathMatch.length === 3) {
const repo = repoPathMatch[1];
const rest = repoPathMatch[2];
const realRepo = await fs.realpath(`${outputBaseExternalPath}${repo}`);
locationPath = `${realRepo}/${rest}`;
}
}
return {
arguments: [
vscode.Uri.file(location.path),
{ selection: location.range },
],
arguments: [vscode.Uri.file(locationPath), { selection: location.range }],
command: "vscode.open",
title: "Jump to Build Target",
};
Expand All @@ -81,7 +105,7 @@ export class BazelTargetTreeItem
public getBazelCommandOptions(): IBazelCommandOptions {
return {
options: [],
targets: [`${this.target.rule.name}`],
targets: [this.target.rule.name],
workspaceInfo: this.workspaceInfo,
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/workspace-tree/bazel_tree_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface IBazelTreeItem {
getTooltip(): string | undefined;

/** Returns the command that should be executed when the item is selected. */
getCommand(): vscode.Command | undefined;
getCommand(): Thenable<vscode.Command | undefined>;

/**
* Returns an identifying string that is used to filter which commands are
Expand Down
23 changes: 10 additions & 13 deletions src/workspace-tree/bazel_workspace_folder_tree_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
/**
* Initializes a new tree item with the given workspace folder.
*
* @param querier Querier for getting information inside a Bazel workspace.
* @param workspaceFolder The workspace folder that the tree item represents.
*/
constructor(
Expand Down Expand Up @@ -52,8 +53,8 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
return this.workspaceInfo.workspaceFolder.uri.fsPath;
}

public getCommand(): vscode.Command | undefined {
return undefined;
public getCommand(): Thenable<vscode.Command | undefined> {
return Promise.resolve(undefined);
}

public getContextValue(): string {
Expand Down Expand Up @@ -81,7 +82,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
startIndex: number,
endIndex: number,
treeItems: IBazelTreeItem[],
parentPackagePath: string,
parentPackagePath?: string,
) {
// We can assume that the caller has sorted the packages, so we scan them to
// find groupings into which we should traverse more deeply. For example, if
Expand Down Expand Up @@ -115,7 +116,9 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
// erroneously collapse something like "foo" and "foobar".
while (
groupEnd < endIndex &&
packagePaths[groupEnd].startsWith(packagePath + "/")
(packagePaths[groupEnd].startsWith(packagePath + "/") ||
(packagePaths[groupEnd].startsWith(packagePath) &&
packagePath.endsWith("//")))
) {
groupEnd++;
}
Expand Down Expand Up @@ -160,14 +163,8 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
return Promise.resolve([] as IBazelTreeItem[]);
}
const packagePaths = await this.querier.queryPackages(this.workspaceInfo);
const topLevelItems: BazelPackageTreeItem[] = [];
this.buildPackageTree(
packagePaths,
0,
packagePaths.length,
topLevelItems,
"",
);
const topLevelItems: IBazelTreeItem[] = [];
this.buildPackageTree(packagePaths, 0, packagePaths.length, topLevelItems);

// Now collect any targets in the directory also (this can fail since
// there might not be a BUILD files at this level (but down levels)).
Expand All @@ -179,6 +176,6 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
return new BazelTargetTreeItem(this.workspaceInfo, target);
});

return Promise.resolve((topLevelItems as IBazelTreeItem[]).concat(targets));
return Promise.resolve(topLevelItems.concat(targets));
}
}
Loading

0 comments on commit 91a5211

Please sign in to comment.