Skip to content

Commit

Permalink
refactor(workspace-tree): Add BazelQuerier for workspace tree
Browse files Browse the repository at this point in the history
Separate and abstract the tree view specific bazel queries in prepare
for adding test cases.

Also pull out the configuration getter since I found it simpler to
use (auto complete and check by tsc, and maybe customizing the defaults
based on more complicated logic).
  • Loading branch information
cwahbong committed Oct 22, 2024
1 parent b01fdbc commit 3ca663e
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 38 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
},
"bazel.commandLine.queryExpression": {
"type": "string",
"default": "...:*",
"default": "",
"description": "A [query language expression](https://bazel.build/query/language) which determines the packages displayed in the workspace tree and quick picker. The default inspects the entire workspace, but you could narrow it. For example: `//part/you/want/...:*`"
},
"bazel.lsp.command": {
Expand Down
16 changes: 16 additions & 0 deletions src/bazel/bazel_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ import * as vscode from "vscode";
import { blaze_query } from "../protos";
import { BazelQuery } from "./bazel_query";

/**
* Get the absolute path for a queried label.
*
* The queried package path are without leading double slash, while we want to
* provide with leading slash.
*
* @param label The label.
* @returns The label in absolute path.
*/
export function labelFromQueriedToAbsolute(label: string): string {
// External packages are in form `@repo//foo/bar`.
// Main repo relative label are in form `foo/bar`.
// Main repo absolute label are in form `//foo/bar`.
return label.includes("//") ? label : `//${label}`;
}

/**
* Get the package label for a build file.
*
Expand Down
8 changes: 8 additions & 0 deletions src/extension/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,11 @@ export function getDefaultBazelExecutablePath(): string {
}
return bazelExecutable;
}

export function getDefaultQueryExpression(): string {
return (
vscode.workspace
.getConfiguration("bazel.commandLine")
.get<string>("queryExpression") ?? "...:*"
);
}
24 changes: 9 additions & 15 deletions src/workspace-tree/bazel_package_tree_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,11 @@

import * as vscode from "vscode";
import { BazelWorkspaceInfo } from "../bazel";
import {
BazelQuery,
IBazelCommandAdapter,
IBazelCommandOptions,
} from "../bazel";
import { getDefaultBazelExecutablePath } from "../extension/configuration";
import { IBazelCommandAdapter, IBazelCommandOptions } from "../bazel";
import { blaze_query } from "../protos";
import { BazelTargetTreeItem } from "./bazel_target_tree_item";
import { IBazelTreeItem } from "./bazel_tree_item";
import { IBazelQuerier } from "./querier";

/** A tree item representing a build package. */
export class BazelPackageTreeItem
Expand All @@ -32,7 +28,7 @@ export class BazelPackageTreeItem
* The array of subpackages that should be shown directly under this package
* item.
*/
public directSubpackages: BazelPackageTreeItem[] = [];
public directSubpackages: IBazelTreeItem[] = [];

/**
* Initializes a new tree item with the given workspace path and package path.
Expand All @@ -44,6 +40,7 @@ export class BazelPackageTreeItem
* {@code packagePath} should be stripped for the item's label.
*/
constructor(
private readonly querier: IBazelQuerier,
private readonly workspaceInfo: BazelWorkspaceInfo,
private readonly packagePath: string,
private readonly parentPackagePath: string,
Expand All @@ -54,17 +51,14 @@ export class BazelPackageTreeItem
}

public async getChildren(): Promise<IBazelTreeItem[]> {
const queryResult = await new BazelQuery(
getDefaultBazelExecutablePath(),
this.workspaceInfo.bazelWorkspacePath,
).queryTargets(`//${this.packagePath}:all`, {
ignoresErrors: true,
sortByRuleName: true,
});
const queryResult = await this.querier.queryChildrenTargets(
this.workspaceInfo,
this.packagePath,
);
const targets = queryResult.target.map((target: blaze_query.ITarget) => {
return new BazelTargetTreeItem(this.workspaceInfo, target);
});
return (this.directSubpackages as IBazelTreeItem[]).concat(targets);
return this.directSubpackages.concat(targets);
}

public getLabel(): string {
Expand Down
32 changes: 12 additions & 20 deletions src/workspace-tree/bazel_workspace_folder_tree_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@

import * as vscode from "vscode";
import { BazelWorkspaceInfo } from "../bazel";
import { BazelQuery } from "../bazel";
import { getDefaultBazelExecutablePath } from "../extension/configuration";
import { blaze_query } from "../protos";
import { BazelPackageTreeItem } from "./bazel_package_tree_item";
import { BazelTargetTreeItem } from "./bazel_target_tree_item";
import { IBazelTreeItem } from "./bazel_tree_item";
import { IBazelQuerier } from "./querier";

/** A tree item representing a workspace folder. */
export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
Expand All @@ -28,7 +27,10 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
*
* @param workspaceFolder The workspace folder that the tree item represents.
*/
constructor(private workspaceInfo: BazelWorkspaceInfo) {}
constructor(
private readonly querier: IBazelQuerier,
private readonly workspaceInfo: BazelWorkspaceInfo,
) {}

public mightHaveChildren(): boolean {
return true;
Expand Down Expand Up @@ -78,7 +80,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
packagePaths: string[],
startIndex: number,
endIndex: number,
treeItems: BazelPackageTreeItem[],
treeItems: IBazelTreeItem[],
parentPackagePath: string,
) {
// We can assume that the caller has sorted the packages, so we scan them to
Expand Down Expand Up @@ -123,6 +125,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
// tree node for the element at groupStart and then recursively call the
// algorithm again to group its children.
const item = new BazelPackageTreeItem(
this.querier,
this.workspaceInfo,
packagePath,
parentPackagePath,
Expand Down Expand Up @@ -156,15 +159,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
if (!this.workspaceInfo) {
return Promise.resolve([] as IBazelTreeItem[]);
}
const workspacePath = this.workspaceInfo.workspaceFolder.uri.fsPath;
const packagePaths = await new BazelQuery(
getDefaultBazelExecutablePath(),
workspacePath,
).queryPackages(
vscode.workspace
.getConfiguration("bazel.commandLine")
.get("queryExpression"),
);
const packagePaths = await this.querier.queryPackages(this.workspaceInfo);
const topLevelItems: BazelPackageTreeItem[] = [];
this.buildPackageTree(
packagePaths,
Expand All @@ -176,13 +171,10 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {

// 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)).
const queryResult = await new BazelQuery(
getDefaultBazelExecutablePath(),
workspacePath,
).queryTargets(`:all`, {
ignoresErrors: true,
sortByRuleName: true,
});
const queryResult = await this.querier.queryChildrenTargets(
this.workspaceInfo,
"",
);
const targets = queryResult.target.map((target: blaze_query.ITarget) => {
return new BazelTargetTreeItem(this.workspaceInfo, target);
});
Expand Down
11 changes: 9 additions & 2 deletions src/workspace-tree/bazel_workspace_tree_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as vscode from "vscode";
import { BazelWorkspaceInfo } from "../bazel";
import { IBazelTreeItem } from "./bazel_tree_item";
import { BazelWorkspaceFolderTreeItem } from "./bazel_workspace_folder_tree_item";
import { IBazelQuerier, ProcessBazelQuerier } from "./querier";

/**
* Provides a tree of Bazel build packages and targets for the VS Code explorer
Expand All @@ -38,8 +39,11 @@ export class BazelWorkspaceTreeProvider
* Initializes a new tree provider with the given extension context.
*
* @param context The VS Code extension context.
* @param querier The interface providing the `bazel query` results.
*/
constructor() {
constructor(
private readonly querier: IBazelQuerier = new ProcessBazelQuerier(),
) {
const buildFilesWatcher = vscode.workspace.createFileSystemWatcher(
"**/{BUILD,BUILD.bazel}",
false,
Expand Down Expand Up @@ -126,7 +130,10 @@ export class BazelWorkspaceTreeProvider
.map((folder) => {
const workspaceInfo = BazelWorkspaceInfo.fromWorkspaceFolder(folder);
if (workspaceInfo) {
return new BazelWorkspaceFolderTreeItem(workspaceInfo);
return new BazelWorkspaceFolderTreeItem(
this.querier,
workspaceInfo,
);
}
return undefined;
})
Expand Down
1 change: 1 addition & 0 deletions src/workspace-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
// limitations under the License.

export * from "./bazel_workspace_tree_provider";
export * from "./querier";
108 changes: 108 additions & 0 deletions src/workspace-tree/querier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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 vscode from "vscode";
import {
BazelQuery,
BazelWorkspaceInfo,
labelFromQueriedToAbsolute,
} from "../bazel";
import {
getDefaultBazelExecutablePath,
getDefaultQueryExpression,
} from "../extension/configuration";
import { blaze_query } from "../protos";
import { BazelInfo } from "../bazel/bazel_info";

/**
* Bazel querier for workspace tree.
*
* The interface defined here is to specifying the operation required for a
* workspace tree instead of all bazel query syntax and options supported.
*
* The function named with queryXxx are all for querying bazel informations.
*/
export interface IBazelQuerier {
/**
* Queries bazel workspace path by given vscode workspace folder.
*
* @param workspaceInfo the Bazel workspace info.
* @returns package name queries in absolute apparent paths.
*/
queryWorkspace(
workspaceFolder: vscode.WorkspaceFolder,
): Thenable<BazelWorkspaceInfo | undefined>;

/**
* Queries all Bazel packages in a workspace folder.
*
* @param workspaceInfo the Bazel workspace info.
* @returns package name queries in absolute apparent paths.
*/
queryPackages(workspaceInfo: BazelWorkspaceInfo): Thenable<string[]>;

/**
* Queries all children targets of a Bazel package.
*
* @param workspaceInfo the Bazel workspace info.
* @param packagePath the Bazel package path. Could be either in absolute label or
* relative to the opening vscode workspace in `workspaceInfo`.
*/
queryChildrenTargets(
workspaceInfo: BazelWorkspaceInfo,
packagePath: string,
): Thenable<blaze_query.IQueryResult>;
}

/**
* Calling Bazel process for the queries.
*/
export class ProcessBazelQuerier implements IBazelQuerier {
async queryWorkspace(
workspaceFolder: vscode.WorkspaceFolder,
): Promise<BazelWorkspaceInfo | undefined> {
try {
const bazelWorkspacePath = await new BazelInfo(
getDefaultBazelExecutablePath(),
workspaceFolder.uri.fsPath,
).getOne("workspace");
return new BazelWorkspaceInfo(bazelWorkspacePath, workspaceFolder);
} catch {
return undefined;
}
}

async queryPackages(workspaceInfo: BazelWorkspaceInfo): Promise<string[]> {
const packages = await new BazelQuery(
getDefaultBazelExecutablePath(),
workspaceInfo.workspaceFolder.uri.fsPath,
).queryPackages(getDefaultQueryExpression());
return packages.map(labelFromQueriedToAbsolute);
}

queryChildrenTargets(
workspaceInfo: BazelWorkspaceInfo,
packagePath: string,
): Promise<blaze_query.IQueryResult> {
// Getting all rules without files, thus using :all instead of :*.
const query = `${packagePath}:all`;
return new BazelQuery(
getDefaultBazelExecutablePath(),
workspaceInfo.workspaceFolder.uri.fsPath,
).queryTargets(query, {
ignoresErrors: true,
sortByRuleName: true,
});
}
}

0 comments on commit 3ca663e

Please sign in to comment.