feat: Automatically detect debug tasks (#1149)

This commit is contained in:
Shi Chen 2022-01-24 09:55:54 +08:00 коммит произвёл GitHub
Родитель f4cd0e9700
Коммит df18dec330
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 61 добавлений и 103 удалений

Просмотреть файл

@ -98,29 +98,14 @@ Don't re-use terminals for any tasks. A new terminal will be created for each ta
</details>
<details><summary>Debug JavaExec tasks</summary>
<details><summary>Debug tasks</summary>
This extension provides an experimental feature to debug [JavaExec](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html) tasks. Before using this feature you need to install the [Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug) and [Language Support for Java](https://marketplace.visualstudio.com/items?itemName=redhat.java) extensions.
This extension provides an experimental feature to debug [JavaExec](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html) and [Test](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html) tasks. Before using this feature you need to install the [Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug) and [Language Support for Java](https://marketplace.visualstudio.com/items?itemName=redhat.java) extensions.
To enable this feature you need to specify which tasks can be debugged within your project `.vscode/settings.json`.
You also need to specify whether you want to clean output cache before debugging, to ensure Gradle doesn't skip any tasks due to output caching (this is most useful when debugging tests).
You might need to specify whether you want to clean output cache before debugging, to ensure Gradle doesn't skip any tasks due to output caching (this is most useful when debugging tests).
> Output cache is cleaned by adding a `cleanTaskName` task (eg `cleanTest`) to the build.
Example config:
```json
"gradle.javaDebug": {
"tasks": [
"run",
"test",
"subproject:customJavaExecTask"
],
"clean": true
}
```
You should now see a `debug` command next to the `run` command in the Gradle Projects view. The `debug` command will start the Gradle task with [jdwp](https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/conninv.html#oracle-vm-invocation-options) `jvmArgs` and start the vscode Java debugger.
![Debug Screencast](images/debug-screencast.gif?1)
@ -213,7 +198,7 @@ We will continue improving the auto completion feature to support more cases in
- Supports nested projects (enabled via setting)
- Show flat or nested tasks in the explorer
- Gracefully cancel a running task
- Debug JavaExec tasks
- Debug tasks
- Run/debug a task with arguments (supports both build & task args, eg `gradle tasks --all --info`)
- Pin tasks
- List recent tasks
@ -234,7 +219,7 @@ This extension contributes the following settings:
- `gradle.focusTaskInExplorer`: Focus the task in the explorer when running a task (boolean)
- `gradle.nestedProjects`: Process nested projects (boolean or an array of directories)
- `gradle.reuseTerminals`: Reuse task terminals ("task" [default], "all", or "off")
- `gradle.javaDebug`: Debug JavaExec tasks (see below for usage)
- `gradle.javaDebug.cleanOutput`: Clean the task output cache before debugging (boolean)
- `gradle.debug`: Show extra debug info in the output panel (boolean)
- `gradle.disableConfirmations`: Disable the warning confirm messages when performing batch actions (eg clear tasks, stop daemons etc) (boolean)
- `gradle.allowParallelRun`: Allow to run tasks in parallel, each running will create a new terminal. This configuration will override `gradle.reuseTerminals` and always create new task terminals when running or debugging a task.

Просмотреть файл

@ -733,9 +733,16 @@
"scope": "window",
"description": "Focus the task in the explorer when running a task"
},
"gradle.javaDebug.cleanOutput": {
"type": "boolean",
"default": true,
"scope": "window",
"description": "Clean the task output cache before debugging"
},
"gradle.javaDebug": {
"type": "object",
"description": "Java debug options",
"deprecationMessage": "This setting will be removed in the future since the extension can automatically get debug tasks, for clean options, please see `gradle.javaDebug.cleanOutput`.",
"scope": "resource",
"properties": {
"tasks": {

Просмотреть файл

@ -343,7 +343,7 @@ export class Extension {
) {
await this.restartServer();
} else if (
event.affectsConfiguration("gradle.javaDebug") ||
event.affectsConfiguration("gradle.javaDebug.cleanOutput") ||
event.affectsConfiguration("gradle.nestedProjects")
) {
this.rootProjectsStore.clear();

Просмотреть файл

@ -37,7 +37,7 @@ import { COMMAND_REFRESH_DAEMON_STATUS, COMMAND_SHOW_LOGS, COMMAND_CANCEL_BUILD
import { RootProject } from "../rootProject/RootProject";
import { getBuildCancellationKey, getProjectsCancellationKey } from "./CancellationKeys";
import { EventWaiter } from "../util/EventWaiter";
import { getGradleConfig, getConfigJavaDebug } from "../util/config";
import { getGradleConfig, getJavaDebugCleanOutput } from "../util/config";
import { setDefault, unsetDefault } from "../views/defaultProject/DefaultProjectUtils";
function logBuildEnvironment(environment: Environment): void {
@ -310,8 +310,7 @@ export class GradleClient implements vscode.Disposable {
if (javaDebugPort > 0) {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(projectFolder));
if (workspaceFolder) {
const javaDebug = getConfigJavaDebug(workspaceFolder);
request.setJavaDebugCleanOutputCache(javaDebug.clean ?? true);
request.setJavaDebugCleanOutputCache(getJavaDebugCleanOutput());
}
}

Просмотреть файл

@ -1,14 +1,9 @@
import * as vscode from "vscode";
import { Environment } from "../proto/gradle_pb";
import { JavaDebug } from "../util/config";
export class RootProject {
private environment?: Environment;
constructor(
private readonly workspaceFolder: vscode.WorkspaceFolder,
private readonly projectUri: vscode.Uri,
private readonly javaDebug: JavaDebug
) {}
constructor(private readonly workspaceFolder: vscode.WorkspaceFolder, private readonly projectUri: vscode.Uri) {}
public setEnvironment(environment: Environment): void {
this.environment = environment;
@ -18,10 +13,6 @@ export class RootProject {
return this.environment;
}
public getJavaDebug(): JavaDebug {
return this.javaDebug;
}
public getWorkspaceFolder(): vscode.WorkspaceFolder {
return this.workspaceFolder;
}

Просмотреть файл

@ -1,6 +1,6 @@
import * as vscode from "vscode";
import * as path from "path";
import { getNestedProjectsConfig, getConfigJavaDebug } from "../util/config";
import { getNestedProjectsConfig } from "../util/config";
import { StoreMap } from ".";
import { isGradleRootProject } from "../util";
import { RootProject } from "../rootProject/RootProject";
@ -12,8 +12,7 @@ async function getNestedRootProjectFolders(): Promise<string[]> {
function buildRootFolder(folderUri: vscode.Uri): RootProject {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(folderUri)!;
const javaDebug = getConfigJavaDebug(workspaceFolder);
return new RootProject(workspaceFolder, folderUri, javaDebug);
return new RootProject(workspaceFolder, folderUri);
}
function getGradleProjectFoldersOutsideRoot(

Просмотреть файл

@ -22,7 +22,6 @@ import {
getGradleConfig,
getConfigIsAutoDetectionEnabled,
getConfigReuseTerminals,
getConfigJavaDebug,
getAllowParallelRun,
} from "../util/config";
@ -185,11 +184,9 @@ export function resolveTaskFromDefinition(
const resolvedWorkspaceFolder =
vscode.workspace.getWorkspaceFolder(vscode.Uri.file(resolvedTaskDefinition.workspaceFolder)) ||
workspaceFolder;
const javaDebug = getConfigJavaDebug(resolvedWorkspaceFolder);
const rootProject = new RootProject(
resolvedWorkspaceFolder,
vscode.Uri.file(resolvedTaskDefinition.projectFolder),
javaDebug
vscode.Uri.file(resolvedTaskDefinition.projectFolder)
);
const cancellationKey = getRunTaskCommandCancellationKey(
rootProject.getProjectUri().fsPath,
@ -226,8 +223,7 @@ function createVSCodeTaskFromGradleTask(
gradleTask: GradleTask,
rootProject: RootProject,
client: GradleClient,
args = "",
javaDebug = false
args = ""
): vscode.Task {
const taskPath = gradleTask.getPath();
const script = taskPath[0] === ":" ? taskPath.substr(1) : taskPath;
@ -243,7 +239,7 @@ function createVSCodeTaskFromGradleTask(
projectFolder: rootProject.getProjectUri().fsPath,
workspaceFolder: rootProject.getWorkspaceFolder().uri.fsPath,
args,
javaDebug,
javaDebug: gradleTask.getDebuggable(),
};
return createTaskFromDefinition(definition, rootProject, client);
}

Просмотреть файл

@ -102,7 +102,8 @@ describe(getSuiteName("Extension"), () => {
task.name,
"",
task.definition.description,
extension!.exports.getIcons()
extension!.exports.getIcons(),
false
);
await vscode.commands.executeCommand(COMMAND_RUN_TASK_WITH_ARGS, treeItem);
});

Просмотреть файл

@ -14,10 +14,7 @@ const mockWorkspaceFolder: vscode.WorkspaceFolder = {
name: "folder1",
};
const mockRootProject = new RootProject(mockWorkspaceFolder, vscode.Uri.file("folder1"), {
tasks: [],
clean: false,
});
const mockRootProject = new RootProject(mockWorkspaceFolder, vscode.Uri.file("folder1"));
const mockClient = buildMockClient();
function buildProject(project: string, rootProject: string, isRoot: boolean, tasksPerProject: number): GradleProject {

Просмотреть файл

@ -79,17 +79,8 @@ export function setShowStoppedDaemons(value: boolean): void {
void vscode.workspace.getConfiguration("gradle").update("showStoppedDaemons", value, true);
}
export type JavaDebug = {
tasks: ReadonlyArray<string>;
clean: boolean;
};
export function getConfigJavaDebug(workspaceFolder: vscode.WorkspaceFolder): JavaDebug {
const defaultValue = {
tasks: ["run", "runBoot", "test", "intTest", "integration"],
clean: true,
};
return vscode.workspace.getConfiguration("gradle", workspaceFolder.uri).get<JavaDebug>("javaDebug", defaultValue);
export function getJavaDebugCleanOutput(): boolean {
return vscode.workspace.getConfiguration("gradle").get<boolean>("javaDebug.cleanOutput", true);
}
export function getAllowParallelRun(): boolean {

Просмотреть файл

@ -1,6 +1,5 @@
import * as vscode from "vscode";
import { Icons } from "../../icons";
import { JavaDebug } from "../../util/config";
import { TASK_STATE_RUNNING_REGEX } from "../constants";
import { getTreeItemState } from "../viewUtil";
@ -12,7 +11,7 @@ export class GradleTaskTreeItem extends vscode.TreeItem {
public tooltip: string,
public description: string,
protected readonly icons: Icons,
protected readonly javaDebug?: JavaDebug
protected readonly javaDebug: boolean
) {
super(label, vscode.TreeItemCollapsibleState.None);
this.command = {
@ -23,7 +22,7 @@ export class GradleTaskTreeItem extends vscode.TreeItem {
}
public setContext(): void {
this.contextValue = getTreeItemState(this.task, this.javaDebug);
this.contextValue = getTreeItemState(this.task);
this.setIconState();
}

Просмотреть файл

@ -206,7 +206,7 @@ export class GradleTasksTreeDataProvider implements vscode.TreeDataProvider<vsco
definition.description || taskName,
"",
icons,
rootProject.getJavaDebug()
definition.javaDebug
);
taskTreeItem.setContext();

Просмотреть файл

@ -7,7 +7,7 @@ export class PinnedTaskTreeItem extends GradleTaskTreeItem {
const definition = this.task.definition as GradleTaskDefinition;
// Update the state of this treeItem when the args match, to prevent showing a running state
// for a task without args AND a tag with args
this.contextValue = getTreeItemState(this.task, this.javaDebug, this.task.definition.args);
this.contextValue = getTreeItemState(this.task, this.task.definition.args);
this.tooltip = (definition.args ? `(args: ${definition.args}) ` : "") + (definition.description || this.label);
this.setIconState();
}

Просмотреть файл

@ -7,7 +7,6 @@ import { isWorkspaceFolder } from "../../util";
import { PinnedTasksStore, RootProjectsStore } from "../../stores";
import { TaskId, TaskArgs } from "../../stores/types";
import { cloneTask, isGradleTask } from "../../tasks/taskUtil";
import { RootProject } from "../../rootProject/RootProject";
import { Icons } from "../../icons";
import { GradleClient } from "../../client";
@ -22,7 +21,6 @@ export function getPinnedTasksTreeItemMap(): Map<string, PinnedTaskTreeItem> {
function buildTaskTreeItem(
gradleProjectTreeItem: PinnedTasksRootProjectTreeItem,
task: vscode.Task,
rootProject: RootProject,
icons: Icons
): GradleTaskTreeItem {
const definition = task.definition as GradleTaskDefinition;
@ -34,13 +32,13 @@ function buildTaskTreeItem(
definition.description || taskName, // tooltip
"", // description
icons,
rootProject.getJavaDebug()
definition.javaDebug
);
pinnedTaskTreeItem.setContext();
return pinnedTaskTreeItem;
}
function buildGradleProjectTreeItem(task: vscode.Task, rootProject: RootProject, icons: Icons): void {
function buildGradleProjectTreeItem(task: vscode.Task, icons: Icons): void {
const definition = task.definition as GradleTaskDefinition;
if (isWorkspaceFolder(task.scope) && isGradleTask(task)) {
let gradleProjectTreeItem = pinnedTasksGradleProjectTreeItemMap.get(definition.projectFolder);
@ -49,7 +47,7 @@ function buildGradleProjectTreeItem(task: vscode.Task, rootProject: RootProject,
pinnedTasksGradleProjectTreeItemMap.set(definition.projectFolder, gradleProjectTreeItem);
}
const pinnedTaskTreeItem = buildTaskTreeItem(gradleProjectTreeItem, task, rootProject, icons);
const pinnedTaskTreeItem = buildTaskTreeItem(gradleProjectTreeItem, task, icons);
pinnedTasksTreeItemMap.set(definition.id + definition.args, pinnedTaskTreeItem);
gradleProjectTreeItem.addTask(pinnedTaskTreeItem);
@ -132,7 +130,7 @@ export class PinnedTasksTreeDataProvider implements vscode.TreeDataProvider<vsco
if (taskArgs) {
Array.from(taskArgs.values()).forEach((args: TaskArgs) => {
const pinnedTask = cloneTask(this.rootProjectsStore, task, args, this.client);
buildGradleProjectTreeItem(pinnedTask, rootProject, this.icons);
buildGradleProjectTreeItem(pinnedTask, this.icons);
});
}
});

Просмотреть файл

@ -3,7 +3,6 @@ import { GradleTaskTreeItem } from "..";
import { Icons } from "../../icons";
import { TaskTerminalsStore } from "../../stores";
import { GradleTaskDefinition } from "../../tasks";
import { JavaDebug } from "../../util/config";
import { getTreeItemState } from "../viewUtil";
function getRecentTaskTreeItemState(gradleTaskTreeItemState: string, numTerminals: number): string {
@ -18,7 +17,7 @@ export class RecentTaskTreeItem extends GradleTaskTreeItem {
description: string,
icons: Icons,
private readonly taskTerminalsStore: TaskTerminalsStore,
protected readonly javaDebug: JavaDebug = { tasks: [], clean: true }
protected readonly javaDebug: boolean
) {
// On construction, don't set a description, this will be set in setContext
super(parentTreeItem, task, label, description || label, "", icons, javaDebug);
@ -30,10 +29,7 @@ export class RecentTaskTreeItem extends GradleTaskTreeItem {
const taskTerminalsStore = this.taskTerminalsStore.getItem(definition.id + definition.args);
const numTerminals = taskTerminalsStore ? taskTerminalsStore.size : 0;
this.description = `(${numTerminals})`;
this.contextValue = getRecentTaskTreeItemState(
getTreeItemState(this.task, this.javaDebug, definition.args),
numTerminals
);
this.contextValue = getRecentTaskTreeItemState(getTreeItemState(this.task, definition.args), numTerminals);
this.setIconState();
}
}

Просмотреть файл

@ -7,7 +7,6 @@ import { GradleTaskTreeItem } from "..";
import { isWorkspaceFolder } from "../../util";
import { TaskId, TaskArgs } from "../../stores/types";
import { cloneTask, isGradleTask } from "../../tasks/taskUtil";
import { RootProject } from "../../rootProject/RootProject";
import { GradleClient } from "../../client";
import { Icons } from "../../icons";
@ -22,7 +21,6 @@ export function getRecentTaskTreeItemMap(): Map<string, RecentTaskTreeItem> {
function buildTaskTreeItem(
gradleProjectTreeItem: RecentTasksRootProjectTreeItem,
task: vscode.Task,
rootProject: RootProject,
taskTerminalsStore: TaskTerminalsStore,
icons: Icons
): RecentTaskTreeItem {
@ -35,18 +33,13 @@ function buildTaskTreeItem(
definition.description || taskName, // used for tooltip
icons,
taskTerminalsStore,
rootProject.getJavaDebug()
definition.javaDebug
);
recentTaskTreeItem.setContext();
return recentTaskTreeItem;
}
function buildGradleProjectTreeItem(
task: vscode.Task,
rootProject: RootProject,
taskTerminalsStore: TaskTerminalsStore,
icons: Icons
): void {
function buildGradleProjectTreeItem(task: vscode.Task, taskTerminalsStore: TaskTerminalsStore, icons: Icons): void {
const definition = task.definition as GradleTaskDefinition;
if (isWorkspaceFolder(task.scope) && isGradleTask(task)) {
let gradleProjectTreeItem = recentTasksGradleProjectTreeItemMap.get(definition.projectFolder);
@ -55,13 +48,7 @@ function buildGradleProjectTreeItem(
recentTasksGradleProjectTreeItemMap.set(definition.projectFolder, gradleProjectTreeItem);
}
const recentTaskTreeItem = buildTaskTreeItem(
gradleProjectTreeItem,
task,
rootProject,
taskTerminalsStore,
icons
);
const recentTaskTreeItem = buildTaskTreeItem(gradleProjectTreeItem, task, taskTerminalsStore, icons);
recentTasksTreeItemMap.set(definition.id + definition.args, recentTaskTreeItem);
gradleProjectTreeItem.addTask(recentTaskTreeItem);
@ -162,7 +149,7 @@ export class RecentTasksTreeDataProvider implements vscode.TreeDataProvider<vsco
if (taskArgs) {
Array.from(taskArgs.values()).forEach((args: TaskArgs) => {
const recentTask = cloneTask(this.rootProjectsStore, task, args, this.client, definition.javaDebug);
buildGradleProjectTreeItem(recentTask, rootProject, this.taskTerminalsStore, this.icons);
buildGradleProjectTreeItem(recentTask, this.taskTerminalsStore, this.icons);
});
}
});

Просмотреть файл

@ -21,7 +21,6 @@ import {
PinnedTasksTreeDataProvider,
RecentTasksTreeDataProvider,
} from ".";
import { JavaDebug } from "../util/config";
export function treeItemSortCompareFunc(a: vscode.TreeItem, b: vscode.TreeItem): number {
return a.label!.toString().localeCompare(b.label!.toString());
@ -123,8 +122,9 @@ export async function focusProjectInGradleTasksTree(
}
}
function getTreeItemRunningState(task: vscode.Task, javaDebug?: JavaDebug, args?: TaskArgs): string {
const isDebug = javaDebug && javaDebug.tasks.includes(task.definition.script);
function getTreeItemRunningState(task: vscode.Task, args?: TaskArgs): string {
const definition = task.definition as GradleTaskDefinition;
const isDebug = definition.javaDebug;
if (isTaskCancelling(task, args)) {
return TREE_ITEM_STATE_TASK_CANCELLING;
}
@ -134,7 +134,7 @@ function getTreeItemRunningState(task: vscode.Task, javaDebug?: JavaDebug, args?
return isDebug ? TREE_ITEM_STATE_TASK_DEBUG_IDLE : TREE_ITEM_STATE_TASK_IDLE;
}
export function getTreeItemState(task: vscode.Task, javaDebug?: JavaDebug, args?: TaskArgs): string {
const runningState = getTreeItemRunningState(task, javaDebug, args);
export function getTreeItemState(task: vscode.Task, args?: TaskArgs): string {
const runningState = getTreeItemRunningState(task, args);
return args ? `${runningState}WithArgs` : runningState;
}

Просмотреть файл

@ -11,4 +11,5 @@ public interface GradleTask {
String getBuildFile();
String getRootProject();
String getDescription();
boolean getDebuggable();
}

Просмотреть файл

@ -14,9 +14,10 @@ public class DefaultGradleTask implements Serializable, GradleTask {
private String buildFile;
private String rootProject;
private String description;
private boolean debuggable;
public DefaultGradleTask(String name, String group, String path, String project, String buildFile,
String rootProject, String description) {
String rootProject, String description, boolean debuggable) {
this.name = name;
this.group = group;
this.path = path;
@ -24,6 +25,7 @@ public class DefaultGradleTask implements Serializable, GradleTask {
this.buildFile = buildFile;
this.rootProject = rootProject;
this.description = description;
this.debuggable = debuggable;
}
public String getName() {
@ -53,4 +55,8 @@ public class DefaultGradleTask implements Serializable, GradleTask {
public String getDescription() {
return description;
}
public boolean getDebuggable() {
return debuggable;
}
}

Просмотреть файл

@ -33,7 +33,9 @@ import org.gradle.api.plugins.Convention;
import org.gradle.api.plugins.ExtensionsSchema;
import org.gradle.api.plugins.ExtensionsSchema.ExtensionSchema;
import org.gradle.api.reflect.TypeOf;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.testing.Test;
import org.gradle.internal.classpath.ClassPath;
import org.gradle.tooling.provider.model.ToolingModelBuilder;
@ -58,7 +60,7 @@ public class GradleProjectModelBuilder implements ToolingModelBuilder {
taskNames.add(task.getName());
GradleTask newTask = new DefaultGradleTask(task.getName(), task.getGroup(), task.getPath(),
project.getName(), project.getBuildscript().getSourceFile().getAbsolutePath(),
task.getRootProject(), task.getDescription());
task.getRootProject(), task.getDescription(), task.getDebuggable());
rootModel.getTasks().add(newTask);
}
}
@ -191,8 +193,9 @@ public class GradleProjectModelBuilder implements ToolingModelBuilder {
String buildFile = task.getProject().getBuildscript().getSourceFile().getAbsolutePath();
String rootProjectName = rootProject.getName();
String description = task.getDescription() == null ? null : task.getDescription();
boolean debuggable = (task instanceof JavaExec) || (task instanceof Test);
GradleTask newTask = new DefaultGradleTask(name, group, path, projectName, buildFile, rootProjectName,
description);
description, debuggable);
tasks.add(newTask);
cachedTasks.add(newTask);
});

Просмотреть файл

@ -201,7 +201,8 @@ public class GetBuildHandler {
model.getTasks().forEach(task -> {
GradleTask.Builder builder = GradleTask.newBuilder();
builder.setName(task.getName()).setPath(task.getPath()).setProject(task.getProject())
.setBuildFile(task.getBuildFile()).setRootProject(task.getRootProject());
.setBuildFile(task.getBuildFile()).setRootProject(task.getRootProject())
.setDebuggable(task.getDebuggable());
String group = task.getGroup();
if (group != null) {
builder.setGroup(group);

Просмотреть файл

@ -192,6 +192,7 @@ message GradleTask {
string buildFile = 5;
string rootProject = 6;
string description = 7;
bool debuggable = 8;
}
message Cancelled {