diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 023347095..1b17545be 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -26,7 +26,7 @@ The extension models the project hierarchical structure using the vscode treeVie Gradle tasks can be run through either the [treeView](https://code.visualstudio.com/api/extension-guides/tree-view) or the Command Palette. -The tasks use the gRPC client to call the `runTask` server method. Similar to getting project data, Gradle progress and output (`STDERR` & `STDOUT`) is streamed to the client. Tasks are run in a custom vscode terminal. +Tasks are run via the `runBuild` gRPC method. Similar to getting project data, Gradle progress and output (`STDERR` & `STDOUT`) is streamed to the client. Tasks are run in a custom vscode terminal. The `runBuild` gRPC method accepts a list of arguments which are passed to the `BuildLauncher`. ## The Build System diff --git a/README.md b/README.md index 68bb04595..55d31836c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,11 @@ When you expand a project, tasks are listed in a tree, grouped by the task group
Run tasks -Tasks can be run via the `Gradle Tasks`, `Pinned Tasks` or `Recent Tasks` treeviews, or as vscode tasks via `Command Palette => Run Task`. +Tasks can be run via: + +- `Gradle Tasks`, `Pinned Tasks` or `Recent Tasks` treeviews +- `Run Task` command +- `Run a Gradle Build` command A running task will be shown with an animated "spinner" icon in the treeviews, along with `Cancel Task` & `Restart Task` buttons. The `Cancel Task` button will gracefully cancel the task. The `Restart Task` button will first cancel the task, then restart it. @@ -45,6 +49,10 @@ Send a SIGINT signal (ctrl/cmd + c) in the terminal to gracefully cancel it. Gradle Tasks Output +Tasks run via the `Run a Gradle Build` command are not reflected in any of the treeviews. Use this command to specify your own Gradle build arguments, for example to run multiple tasks or to exclude tasks. + +Run Gradle Build +
Debug JavaExec tasks @@ -135,7 +143,7 @@ This extension contributes the following settings: - `gradle.autoDetect`: Automatically detect Gradle tasks - `gradle.focusTaskInExplorer`: Focus the task in the explorer when running a task -- `gradle.nestedProjects`: Support nested projects (boolean or an array of directories) +- `gradle.nestedProjects`: Process nested projects (boolean or an array of directories) - `gradle.javaDebug`: Debug JavaExec tasks (see below for usage) - `gradle.debug`: Show extra debug info in the output panel - `gradle.disableConfirmations`: Disable the warning confirm messages when performing batch actions (eg clear tasks, stop daemons etc) diff --git a/build.gradle b/build.gradle index f2edc0b1c..b4078e7d3 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ allprojects { subprojects { apply plugin: 'java' apply plugin: 'com.google.protobuf' + apply plugin: 'com.diffplug.gradle.spotless' repositories { maven { diff --git a/extension/package.json b/extension/package.json index a07bcc001..ca9d3d836 100644 --- a/extension/package.json +++ b/extension/package.json @@ -93,6 +93,14 @@ "dark": "resources/dark/run.svg" } }, + { + "command": "gradle.runBuild", + "title": "Run a Gradle Build", + "icon": { + "light": "resources/light/console.svg", + "dark": "resources/dark/console.svg" + } + }, { "command": "gradle.pinTask", "title": "Pin Task" @@ -186,7 +194,7 @@ "title": "Open Task Build File" }, { - "command": "gradle.cancelTask", + "command": "gradle.cancelBuild", "title": "Cancel Task" }, { @@ -221,10 +229,6 @@ "dark": "resources/dark/list-tree.svg" } }, - { - "command": "gradle.killGradleProcess", - "title": "Kill Gradle task process" - }, { "command": "gradle.showProcessMessage", "title": "Show Gradle process information message box" @@ -341,7 +345,7 @@ "when": "false" }, { - "command": "gradle.cancelTask", + "command": "gradle.cancelBuild", "when": "false" }, { @@ -402,6 +406,10 @@ }, { "command": "gradle.openSettings", + "when": "view == gradleTasksView" + }, + { + "command": "gradle.runBuild", "when": "view == gradleTasksView", "group": "navigation@0" }, diff --git a/extension/src/api/Api.ts b/extension/src/api/Api.ts index 846c73114..488b1b53d 100644 --- a/extension/src/api/Api.ts +++ b/extension/src/api/Api.ts @@ -4,6 +4,7 @@ import { logger } from '../logger'; import { GradleTasksTreeDataProvider } from '../views'; import { GradleTaskDefinition } from '../tasks'; import { GradleClient } from '../client'; +import { getRunTaskCommandCancellationKey } from '../client/CancellationKeys'; export interface RunTaskOpts { projectFolder: string; @@ -30,12 +31,20 @@ export class Api { public async runTask(opts: RunTaskOpts): Promise { const task = await this.findTask(opts.projectFolder, opts.taskName); - return this.client.runTask( + const buildArgs = [task.definition.script] + .concat(opts.args) + .filter(Boolean); + const cancellationKey = getRunTaskCommandCancellationKey( opts.projectFolder, - task, - opts.args, + opts.taskName + ); + return this.client.runBuild( + opts.projectFolder, + cancellationKey, + buildArgs, opts.input, 0, + task, opts.onOutput, opts.showOutputColors ); @@ -43,7 +52,11 @@ export class Api { public async cancelRunTask(opts: CancelTaskOpts): Promise { const task = await this.findTask(opts.projectFolder, opts.taskName); - return this.client.cancelRunTask(task); + const cancellationKey = getRunTaskCommandCancellationKey( + opts.projectFolder, + opts.taskName + ); + return this.client.cancelBuild(cancellationKey, task); } private async findTask( diff --git a/extension/src/client/CancellationKeys.ts b/extension/src/client/CancellationKeys.ts new file mode 100644 index 000000000..77dfcd89c --- /dev/null +++ b/extension/src/client/CancellationKeys.ts @@ -0,0 +1,17 @@ +export function getBuildCancellationKey(rootProjectFolder: string): string { + return 'getBuild' + rootProjectFolder; +} + +export function getRunBuildCancellationKey( + rootProjectFolder: string, + args: ReadonlyArray +): string { + return 'runBuild' + rootProjectFolder + args.join(''); +} + +export function getRunTaskCommandCancellationKey( + rootProjectFolder: string, + taskName: string +): string { + return 'runTask' + rootProjectFolder + taskName; +} diff --git a/extension/src/client/GradleClient.ts b/extension/src/client/GradleClient.ts index 77e73e003..15a241ba1 100644 --- a/extension/src/client/GradleClient.ts +++ b/extension/src/client/GradleClient.ts @@ -3,19 +3,11 @@ import * as grpc from '@grpc/grpc-js'; import { connectivityState as ConnectivityState } from '@grpc/grpc-js'; import { - RunTaskRequest, - RunTaskReply, Output, GetBuildRequest, GetBuildReply, - CancelGetBuildsRequest, - CancelGetBuildsReply, - CancelRunTaskRequest, - CancelRunTaskReply, - CancelRunTasksReply, Cancelled, GradleBuild, - CancelRunTasksRequest, Environment, GradleConfig, GetDaemonsStatusReply, @@ -24,6 +16,10 @@ import { StopDaemonsRequest, StopDaemonRequest, StopDaemonReply, + RunBuildRequest, + RunBuildReply, + CancelBuildRequest, + CancelBuildReply, } from '../proto/gradle_pb'; import { GradleClient as GrpcClient } from '../proto/gradle_grpc_pb'; @@ -32,14 +28,14 @@ import { EventWaiter } from '../events'; import { GradleServer } from '../server'; import { ProgressHandler } from '../progress'; import { getGradleConfig } from '../config'; -import { GradleTaskDefinition } from '../tasks'; import { removeCancellingTask, restartQueuedTask } from '../tasks/taskUtil'; import { COMMAND_REFRESH_DAEMON_STATUS, COMMAND_SHOW_LOGS, - COMMAND_CANCEL_TASK, + COMMAND_CANCEL_BUILD, } from '../commands'; import { RootProject } from '../rootProject/RootProject'; +import { getBuildCancellationKey } from './CancellationKeys'; function logBuildEnvironment(environment: Environment): void { const javaEnv = environment.getJavaEnvironment()!; @@ -150,14 +146,18 @@ export class GradleClient implements vscode.Disposable { progress, 'Configure project' ); + const cancellationKey = getBuildCancellationKey( + rootProject.getProjectUri().fsPath + ); - token.onCancellationRequested(() => this.cancelGetBuilds()); + token.onCancellationRequested(() => this.cancelBuild(cancellationKey)); const stdOutLoggerStream = new LoggerStream(logger, LogVerbosity.INFO); const stdErrLoggerStream = new LoggerStream(logger, LogVerbosity.ERROR); const request = new GetBuildRequest(); request.setProjectDir(rootProject.getProjectUri().fsPath); + request.setCancellationKey(cancellationKey); request.setGradleConfig(gradleConfig); request.setShowOutputColors(showOutputColors); const getBuildStream = this.grpcClient!.getBuild(request); @@ -223,12 +223,13 @@ export class GradleClient implements vscode.Disposable { ); } - public async runTask( + public async runBuild( projectFolder: string, - task: vscode.Task, - args: ReadonlyArray = [], + cancellationKey: string, + args: ReadonlyArray, input = '', javaDebugPort = 0, + task?: vscode.Task, onOutput?: (output: Output) => void, showOutputColors = true ): Promise { @@ -245,7 +246,11 @@ export class GradleClient implements vscode.Disposable { token: vscode.CancellationToken ) => { token.onCancellationRequested(() => - vscode.commands.executeCommand(COMMAND_CANCEL_TASK, task) + vscode.commands.executeCommand( + COMMAND_CANCEL_BUILD, + cancellationKey, + task + ) ); const progressHandler = new ProgressHandler(progress); @@ -255,35 +260,35 @@ export class GradleClient implements vscode.Disposable { }); const gradleConfig = getGradleConfig(); - const request = new RunTaskRequest(); + const request = new RunBuildRequest(); request.setProjectDir(projectFolder); - request.setTask(task.definition.script); + request.setCancellationKey(cancellationKey); request.setArgsList(args as string[]); request.setGradleConfig(gradleConfig); - request.setJavaDebug(task.definition.javaDebug); request.setShowOutputColors(showOutputColors); request.setJavaDebugPort(javaDebugPort); request.setInput(input); - const runTaskStream = this.grpcClient!.runTask(request); + const runBuildStream = this.grpcClient!.runBuild(request); try { await new Promise((resolve, reject) => { - runTaskStream - .on('data', (runTaskReply: RunTaskReply) => { - switch (runTaskReply.getKindCase()) { - case RunTaskReply.KindCase.PROGRESS: + runBuildStream + .on('data', (runBuildReply: RunBuildReply) => { + switch (runBuildReply.getKindCase()) { + case RunBuildReply.KindCase.PROGRESS: progressHandler.report( - runTaskReply.getProgress()!.getMessage().trim() + runBuildReply.getProgress()!.getMessage().trim() ); break; - case RunTaskReply.KindCase.OUTPUT: + case RunBuildReply.KindCase.OUTPUT: if (onOutput) { - onOutput(runTaskReply.getOutput()!); + onOutput(runBuildReply.getOutput()!); } break; - case RunTaskReply.KindCase.CANCELLED: - this.handleRunTaskCancelled( - task, - runTaskReply.getCancelled()! + case RunBuildReply.KindCase.CANCELLED: + this.handleRunBuildCancelled( + args, + runBuildReply.getCancelled()!, + task ); break; } @@ -291,109 +296,54 @@ export class GradleClient implements vscode.Disposable { .on('error', reject) .on('end', resolve); }); - logger.info('Completed task:', task.definition.script); + logger.info('Completed build:', args.join(' ')); } catch (err) { - logger.error('Error running task:', err.details || err.message); + logger.error( + 'Error running build:', + `${args.join(' ')}:`, + err.details || err.message + ); throw err; } finally { vscode.commands.executeCommand(COMMAND_REFRESH_DAEMON_STATUS); - restartQueuedTask(task); + if (task) { + restartQueuedTask(task); + } } } ); } - public async cancelRunTask(task: vscode.Task): Promise { + public async cancelBuild( + cancellationKey: string, + task?: vscode.Task + ): Promise { await this.waitForConnect(); this.statusBarItem.hide(); - const definition = task.definition as GradleTaskDefinition; - const request = new CancelRunTaskRequest(); - request.setProjectDir(definition.projectFolder); - request.setTask(definition.script); + const request = new CancelBuildRequest(); + request.setCancellationKey(cancellationKey); try { - const reply: CancelRunTaskReply = await new Promise((resolve, reject) => { - this.grpcClient!.cancelRunTask( + const reply: CancelBuildReply = await new Promise((resolve, reject) => { + this.grpcClient!.cancelBuild( request, ( err: grpc.ServiceError | null, - cancelRunTaskReply: CancelRunTaskReply | undefined + cancelRunBuildReply: CancelBuildReply | undefined ) => { if (err) { reject(err); } else { - resolve(cancelRunTaskReply); + resolve(cancelRunBuildReply); } } ); }); - logger.debug(reply.getMessage()); - if (!reply.getTaskRunning()) { + logger.info('Cancel build:', reply.getMessage()); + if (!reply.getBuildRunning() && task) { removeCancellingTask(task); } } catch (err) { - logger.error( - 'Error cancelling running task:', - err.details || err.message - ); - } finally { - vscode.commands.executeCommand(COMMAND_REFRESH_DAEMON_STATUS); - } - } - - public async cancelRunTasks(): Promise { - await this.waitForConnect(); - const request = new CancelRunTasksRequest(); - try { - const reply: CancelRunTasksReply = await new Promise( - (resolve, reject) => { - this.grpcClient!.cancelRunTasks( - request, - ( - err: grpc.ServiceError | null, - cancelRunTasksReply: CancelRunTasksReply | undefined - ) => { - if (err) { - reject(err); - } else { - resolve(cancelRunTasksReply); - } - } - ); - } - ); - logger.debug(reply.getMessage()); - } catch (err) { - logger.error( - 'Error cancelling running tasks:', - err.details || err.message - ); - } - } - - public async cancelGetBuilds(): Promise { - await this.waitForConnect(); - const request = new CancelGetBuildsRequest(); - try { - const reply: CancelGetBuildsReply = await new Promise( - (resolve, reject) => { - this.grpcClient!.cancelGetBuilds( - request, - ( - err: grpc.ServiceError | null, - cancelGetBuildsReply: CancelGetBuildsReply | undefined - ) => { - if (err) { - reject(err); - } else { - resolve(cancelGetBuildsReply); - } - } - ); - } - ); - logger.debug(reply.getMessage()); - } catch (err) { - logger.error('Error cancelling get builds:', err.details || err.message); + logger.error('Error cancelling build:', err.details || err.message); } } @@ -488,14 +438,17 @@ export class GradleClient implements vscode.Disposable { } } - private handleRunTaskCancelled = ( - task: vscode.Task, - cancelled: Cancelled + private handleRunBuildCancelled = ( + args: ReadonlyArray, + cancelled: Cancelled, + task?: vscode.Task ): void => { logger.info( - `Task cancelled: ${task.definition.script}: ${cancelled.getMessage()}` + `Build cancelled: ${args.join(' ')}: ${cancelled.getMessage()}` ); - removeCancellingTask(task); + if (task) { + removeCancellingTask(task); + } }; private handleGetBuildCancelled = (cancelled: Cancelled): void => { diff --git a/extension/src/commands/cancelBuildCommand.ts b/extension/src/commands/cancelBuildCommand.ts new file mode 100644 index 000000000..115c7084b --- /dev/null +++ b/extension/src/commands/cancelBuildCommand.ts @@ -0,0 +1,15 @@ +import * as vscode from 'vscode'; +import { cancelBuild } from '../tasks/taskUtil'; +import { logger } from '../logger'; +export const COMMAND_CANCEL_BUILD = 'gradle.cancelBuild'; + +export async function cancelBuildCommand( + cancellationKey: string, + task?: vscode.Task +): Promise { + try { + await cancelBuild(cancellationKey, task); + } catch (e) { + logger.error('Error cancelling task:', e.message); + } +} diff --git a/extension/src/commands/cancelTaskCommand.ts b/extension/src/commands/cancelTaskCommand.ts deleted file mode 100644 index 7a43ca67f..000000000 --- a/extension/src/commands/cancelTaskCommand.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as vscode from 'vscode'; -import { cancelTask } from '../tasks/taskUtil'; -import { logger } from '../logger'; -export const COMMAND_CANCEL_TASK = 'gradle.cancelTask'; - -export async function cancelTaskCommand(task: vscode.Task): Promise { - try { - await cancelTask(task); - } catch (e) { - logger.error('Error cancelling task:', e.message); - } -} diff --git a/extension/src/commands/cancelTreeItemTaskCommand.ts b/extension/src/commands/cancelTreeItemTaskCommand.ts index c7d6ce462..161a3f0ba 100644 --- a/extension/src/commands/cancelTreeItemTaskCommand.ts +++ b/extension/src/commands/cancelTreeItemTaskCommand.ts @@ -1,12 +1,29 @@ import * as vscode from 'vscode'; import { GradleTaskTreeItem } from '../views'; -import { COMMAND_CANCEL_TASK } from '.'; +import { COMMAND_CANCEL_BUILD } from '.'; +import { getRunTaskCommandCancellationKey } from '../client/CancellationKeys'; +import { GradleTaskDefinition } from '../tasks'; +import { getRunningGradleTask } from '../tasks/taskUtil'; export const COMMAND_CANCEL_TREE_ITEM_TASK = 'gradle.cancelTreeItemTask'; export async function cancelTreeItemTaskCommand( treeItem: GradleTaskTreeItem ): Promise { if (treeItem && treeItem.task) { - return vscode.commands.executeCommand(COMMAND_CANCEL_TASK, treeItem.task); + // We get the running task as we could be cancelling a task that is running with args + const runningTask = getRunningGradleTask(treeItem.task); + if (!runningTask) { + return; + } + const definition = runningTask.definition as GradleTaskDefinition; + const cancellationKey = getRunTaskCommandCancellationKey( + definition.projectFolder, + runningTask.name + ); + return vscode.commands.executeCommand( + COMMAND_CANCEL_BUILD, + cancellationKey, + runningTask + ); } } diff --git a/extension/src/commands/index.ts b/extension/src/commands/index.ts index 98c2fe4b2..5e0056112 100644 --- a/extension/src/commands/index.ts +++ b/extension/src/commands/index.ts @@ -1,4 +1,4 @@ -export * from './cancelTaskCommand'; +export * from './cancelBuildCommand'; export * from './cancelTreeItemTaskCommand'; export * from './cancellingTreeItemTaskCommand'; export * from './clearAllPinnedTasksCommand'; @@ -30,3 +30,4 @@ export * from './showTaskTerminalCommand'; export * from './showTasksCommand'; export * from './stopDaemonCommand'; export * from './stopDaemonsCommand'; +export * from './runBuildCommand'; diff --git a/extension/src/commands/refreshCommand.ts b/extension/src/commands/refreshCommand.ts index e8680b52c..ac64d49c8 100644 --- a/extension/src/commands/refreshCommand.ts +++ b/extension/src/commands/refreshCommand.ts @@ -4,6 +4,8 @@ export const COMMAND_REFRESH = 'gradle.refresh'; export async function refreshCommand(): Promise { const extension = Extension.getInstance(); extension.getGradleTaskProvider().clearTasksCache(); + // Explicitly load tasks as the views might not be visible + extension.getGradleTaskProvider().loadTasks(); extension.getGradleTasksTreeDataProvider().refresh(); extension.getPinnedTasksTreeDataProvider().refresh(); extension.getRecentTasksTreeDataProvider().refresh(); diff --git a/extension/src/commands/register.ts b/extension/src/commands/register.ts index 3cc7f08c2..ea2431c52 100644 --- a/extension/src/commands/register.ts +++ b/extension/src/commands/register.ts @@ -14,8 +14,8 @@ import { debugTaskWithArgsCommand, COMMAND_RENDER_TASK, renderTaskCommand, - COMMAND_CANCEL_TASK, - cancelTaskCommand, + COMMAND_CANCEL_BUILD, + cancelBuildCommand, COMMAND_CANCEL_TREE_ITEM_TASK, cancelTreeItemTaskCommand, COMMAND_REFRESH, @@ -60,6 +60,8 @@ import { clearAllPinnedTasksCommand, COMMAND_REMOVE_RECENT_TASK, removeRecentTaskCommand, + COMMAND_RUN_BUILD, + runBuildCommand, } from '.'; export function registerCommands(context: vscode.ExtensionContext): void { @@ -77,7 +79,7 @@ export function registerCommands(context: vscode.ExtensionContext): void { debugTaskWithArgsCommand ), vscode.commands.registerCommand(COMMAND_RENDER_TASK, renderTaskCommand), - vscode.commands.registerCommand(COMMAND_CANCEL_TASK, cancelTaskCommand), + vscode.commands.registerCommand(COMMAND_CANCEL_BUILD, cancelBuildCommand), vscode.commands.registerCommand( COMMAND_CANCEL_TREE_ITEM_TASK, cancelTreeItemTaskCommand @@ -135,6 +137,7 @@ export function registerCommands(context: vscode.ExtensionContext): void { vscode.commands.registerCommand( COMMAND_REMOVE_RECENT_TASK, removeRecentTaskCommand - ) + ), + vscode.commands.registerCommand(COMMAND_RUN_BUILD, runBuildCommand) ); } diff --git a/extension/src/commands/runBuildCommand.ts b/extension/src/commands/runBuildCommand.ts new file mode 100644 index 000000000..756e25ca5 --- /dev/null +++ b/extension/src/commands/runBuildCommand.ts @@ -0,0 +1,43 @@ +import * as vscode from 'vscode'; +import { getGradleCommand, getRootProjectFolder } from '../input'; +import { GradleRunnerTerminal } from '../terminal'; +import { getRunBuildCancellationKey } from '../client/CancellationKeys'; +export const COMMAND_RUN_BUILD = 'gradle.runBuild'; + +export async function runBuildCommand(): Promise { + const rootProject = await getRootProjectFolder(); + if (!rootProject) { + return; + } + const gradleCommand = await getGradleCommand(); + if (!gradleCommand) { + return; + } + const args: string[] = gradleCommand.split(' ').filter(Boolean); + const cancellationKey = getRunBuildCancellationKey( + rootProject.getProjectUri().fsPath, + args + ); + const terminal = new GradleRunnerTerminal(rootProject, args, cancellationKey); + const task = new vscode.Task( + { + type: 'gradle', + }, + rootProject.getWorkspaceFolder(), + gradleCommand, + 'gradle', + new vscode.CustomExecution( + async (): Promise => terminal + ), + ['$gradle'] + ); + task.presentationOptions = { + showReuseMessage: false, + clear: true, + echo: true, + focus: true, + panel: vscode.TaskPanelKind.Shared, + reveal: vscode.TaskRevealKind.Always, + }; + vscode.tasks.executeTask(task); +} diff --git a/extension/src/extension/Extension.ts b/extension/src/extension/Extension.ts index a0c1bec68..380271be0 100644 --- a/extension/src/extension/Extension.ts +++ b/extension/src/extension/Extension.ts @@ -90,7 +90,8 @@ export class Extension { this.icons = new Icons(context); this.gradleTasksTreeDataProvider = new GradleTasksTreeDataProvider( - this.context + this.context, + this.rootProjectsStore ); this.gradleTasksTreeView = vscode.window.createTreeView(GRADLE_TASKS_VIEW, { treeDataProvider: this.gradleTasksTreeDataProvider, @@ -175,15 +176,7 @@ export class Extension { } private loadTasks(): void { - this.client.onDidConnect(async () => { - this.gradleTaskProvider.clearTasksCache(); - // Explicitly load tasks as the views might not be visible on editor start - this.gradleTaskProvider.loadTasks(); - // If the server crashes and is restarted, we need to review the views - this.gradleTasksTreeDataProvider.refresh(); - this.pinnedTasksTreeDataProvider.refresh(); - this.recentTasksTreeDataProvider.refresh(); - }); + this.client.onDidConnect(() => this.refresh()); } private handleTaskEvents(): void { @@ -203,26 +196,21 @@ export class Extension { private handleWatchEvents(): void { this.buildFileWatcher.onDidChange((uri: vscode.Uri) => { logger.debug('Build file changed:', uri.fsPath); - this.reloadTasks(); + this.refresh(); }); this.gradleWrapperWatcher.onDidChange((uri: vscode.Uri) => { logger.debug('Gradle wrapper properties changed:', uri.fsPath); this.client.close(); const disposable = this.client.onDidConnect(() => { disposable.dispose(); - this.reloadTasks(); + this.refresh(); }); this.server.restart(); }); } - private reloadTasks(): void { - if (this.gradleTasksTreeView.visible) { - vscode.commands.executeCommand(COMMAND_REFRESH); - } else { - this.getGradleTaskProvider().clearTasksCache(); - this.getGradleTaskProvider().loadTasks(); - } + private refresh(): void { + vscode.commands.executeCommand(COMMAND_REFRESH); } private handleEditorEvents(): void { @@ -235,15 +223,12 @@ export class Extension { ) { this.server.restart(); } - if (event.affectsConfiguration('gradle.nestedProjects')) { - this.rootProjectsStore.clear(); - this.reloadTasks(); - } if ( event.affectsConfiguration('gradle.javaDebug') || event.affectsConfiguration('gradle.nestedProjects') ) { - vscode.commands.executeCommand(COMMAND_REFRESH); + this.rootProjectsStore.clear(); + this.refresh(); } if (event.affectsConfiguration('gradle.debug')) { const debug = getConfigIsDebugEnabled(); @@ -256,9 +241,7 @@ export class Extension { vscode.window.onDidCloseTerminal((terminal: vscode.Terminal) => { this.taskTerminalsStore.removeTerminal(terminal); }), - vscode.workspace.onDidChangeWorkspaceFolders(() => { - vscode.commands.executeCommand(COMMAND_REFRESH); - }) + vscode.workspace.onDidChangeWorkspaceFolders(() => this.refresh()) ); } diff --git a/extension/src/input/index.ts b/extension/src/input/index.ts index bfb68cab0..86acff5f3 100644 --- a/extension/src/input/index.ts +++ b/extension/src/input/index.ts @@ -1,6 +1,14 @@ import * as vscode from 'vscode'; import { TaskArgs } from '../stores/types'; import { getDisableConfirmations } from '../config'; +import { Extension } from '../extension'; +import { RootProject } from '../rootProject/RootProject'; + +function returnTrimmedInput(value: string | undefined): string | undefined { + if (value !== undefined) { + return value.trim(); + } +} export function getTaskArgs(): Thenable { return vscode.window @@ -8,11 +16,43 @@ export function getTaskArgs(): Thenable { placeHolder: 'For example: --info', ignoreFocusOut: true, }) - .then((value: string | undefined) => { - if (value !== undefined) { - return value.trim(); - } - }); + .then(returnTrimmedInput); +} + +export function getGradleCommand(): Thenable { + return vscode.window + .showInputBox({ + prompt: [ + 'Enter a command for gradle to run.', + 'This can include built-in Gradle commands or tasks.', + 'Not all Gradle command line options are supported.', + ].join(' '), + placeHolder: 'For example: build --info', + ignoreFocusOut: true, + }) + .then(returnTrimmedInput); +} + +export async function getRootProjectFolder(): Promise { + const rootProjects = await Extension.getInstance() + .getRootProjectsStore() + .buildAndGetProjectRoots(); + if (rootProjects.length === 1) { + return Promise.resolve(rootProjects[0]); + } + const rootProjectPaths = rootProjects.map( + (rootProject) => rootProject.getProjectUri().fsPath + ); + const selectedRootProjectPath = await vscode.window.showQuickPick( + rootProjectPaths, + { + canPickMany: false, + placeHolder: 'Select the root project', + } + ); + if (selectedRootProjectPath) { + return rootProjects[rootProjectPaths.indexOf(selectedRootProjectPath)]; + } } export async function confirmModal(message: string): Promise { diff --git a/extension/src/rootProject/RootProject.ts b/extension/src/rootProject/RootProject.ts index 7153f28c3..568f02654 100644 --- a/extension/src/rootProject/RootProject.ts +++ b/extension/src/rootProject/RootProject.ts @@ -1,11 +1,13 @@ import * as vscode from 'vscode'; import { Environment } from '../proto/gradle_pb'; +import { JavaDebug } from '../config'; export class RootProject { private environment?: Environment; constructor( private readonly workspaceFolder: vscode.WorkspaceFolder, - private readonly projectUri: vscode.Uri + private readonly projectUri: vscode.Uri, + private readonly javaDebug: JavaDebug ) {} public setEnvironment(environment: Environment): void { @@ -16,6 +18,10 @@ export class RootProject { return this.environment; } + public getJavaDebug(): JavaDebug { + return this.javaDebug; + } + public getWorkspaceFolder(): vscode.WorkspaceFolder { return this.workspaceFolder; } diff --git a/extension/src/stores/RootProjectsStore.ts b/extension/src/stores/RootProjectsStore.ts index 0e2ba55ff..852339e08 100644 --- a/extension/src/stores/RootProjectsStore.ts +++ b/extension/src/stores/RootProjectsStore.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; -import { getNestedProjectsConfig } from '../config'; +import { getNestedProjectsConfig, getConfigJavaDebug } from '../config'; import { StoreMap } from '.'; import { isGradleRootProject } from '../util'; import { RootProject } from '../rootProject/RootProject'; @@ -19,10 +19,9 @@ async function getNestedRootProjectFolders(): Promise { } function buildRootFolder(folderUri: vscode.Uri): RootProject { - return new RootProject( - vscode.workspace.getWorkspaceFolder(folderUri)!, - folderUri - ); + const workspaceFolder = vscode.workspace.getWorkspaceFolder(folderUri)!; + const javaDebug = getConfigJavaDebug(workspaceFolder); + return new RootProject(workspaceFolder, folderUri, javaDebug); } export class RootProjectsStore extends StoreMap { diff --git a/extension/src/stores/StoreMap.ts b/extension/src/stores/StoreMap.ts index 814951ba8..a4b50b153 100644 --- a/extension/src/stores/StoreMap.ts +++ b/extension/src/stores/StoreMap.ts @@ -11,6 +11,10 @@ export abstract class StoreMap extends EventedStore { return this.data; } + public get(key: K): V | undefined { + return this.getData().get(key); + } + public clear(fireOnDidChange = true): void { this.data.clear(); if (fireOnDidChange) { diff --git a/extension/src/tasks/GradleTaskProvider.ts b/extension/src/tasks/GradleTaskProvider.ts index 4039432a8..48e95754b 100644 --- a/extension/src/tasks/GradleTaskProvider.ts +++ b/extension/src/tasks/GradleTaskProvider.ts @@ -6,6 +6,7 @@ import { createTaskFromDefinition, loadTasksForProjectRoots } from './taskUtil'; import { TaskId } from '../stores/types'; import { RootProjectsStore } from '../stores'; import { RootProject } from '../rootProject/RootProject'; +import { getConfigJavaDebug } from '../config'; export class GradleTaskProvider implements vscode.TaskProvider, vscode.Disposable { @@ -52,9 +53,11 @@ export class GradleTaskProvider ); return undefined; } + const javaDebug = getConfigJavaDebug(workspaceFolder); const rootProject = new RootProject( workspaceFolder, - vscode.Uri.file(gradleTaskDefinition.projectFolder) + vscode.Uri.file(gradleTaskDefinition.projectFolder), + javaDebug ); return createTaskFromDefinition(gradleTaskDefinition, rootProject); } diff --git a/extension/src/tasks/taskUtil.ts b/extension/src/tasks/taskUtil.ts index bb6e95fb4..b13be4068 100644 --- a/extension/src/tasks/taskUtil.ts +++ b/extension/src/tasks/taskUtil.ts @@ -3,7 +3,7 @@ import { GradleTask, GradleProject, GradleBuild } from '../proto/gradle_pb'; import { TaskArgs } from '../stores/types'; import { Extension } from '../extension'; import { GradleTaskDefinition } from '.'; -import { CustomBuildTaskTerminal } from '../terminal'; +import { GradleRunnerTerminal } from '../terminal'; import { logger } from '../logger'; import { getGradleConfig, getConfigIsAutoDetectionEnabled } from '../config'; import { @@ -17,6 +17,7 @@ import { import { getTaskArgs } from '../input'; import { COMMAND_RENDER_TASK } from '../commands'; import { RootProject } from '../rootProject/RootProject'; +import { getRunTaskCommandCancellationKey } from '../client/CancellationKeys'; const cancellingTasks: Map = new Map(); const restartingTasks: Map = new Map(); @@ -50,16 +51,25 @@ export function getRunningGradleTasks(): vscode.Task[] { .map(({ task }) => task); } +export function getRunningGradleTask(task: vscode.Task): vscode.Task | void { + return getRunningGradleTasks().find((runningTask) => + isTask(runningTask, task) + ); +} + export function isTaskRunning(task: vscode.Task, args?: TaskArgs): boolean { return getTaskExecution(task, args) !== undefined; } -export async function cancelTask(task: vscode.Task): Promise { - if (isTaskRunning(task)) { +export async function cancelBuild( + cancellationKey: string, + task?: vscode.Task +): Promise { + if (task && isTaskRunning(task)) { cancellingTasks.set(task.definition.id, task); await vscode.commands.executeCommand(COMMAND_RENDER_TASK, task); - Extension.getInstance().getClient().cancelRunTask(task); } + Extension.getInstance().getClient().cancelBuild(cancellationKey, task); } export function isTaskCancelling(task: vscode.Task, args?: TaskArgs): boolean { @@ -102,19 +112,23 @@ export async function restartQueuedTask(task: vscode.Task): Promise { } } -export async function removeCancellingTask(task: vscode.Task): Promise { +export function removeCancellingTask(task: vscode.Task): void { const cancellingTask = getCancellingTask(task); if (cancellingTask) { cancellingTasks.delete(cancellingTask.definition.id); - await vscode.commands.executeCommand(COMMAND_RENDER_TASK, task); } } export function queueRestartTask(task: vscode.Task): void { if (isTaskRunning(task)) { - restartingTasks.set(task.definition.id, task); + const definition = task.definition as GradleTaskDefinition; + restartingTasks.set(definition.id, task); + const cancellationKey = getRunTaskCommandCancellationKey( + definition.projectFolder, + task.name + ); // Once the task is cancelled it will restart via onDidEndTask - cancelTask(task); + cancelBuild(cancellationKey, task); } } @@ -136,14 +150,19 @@ export function createTaskFromDefinition( rootProject: RootProject ): vscode.Task { const taskTerminalsStore = Extension.getInstance().getTaskTerminalsStore(); - const terminal = new CustomBuildTaskTerminal( - rootProject.getWorkspaceFolder(), - rootProject.getProjectUri() + const args = [definition.script].concat( + definition.args.split(' ').filter(Boolean) + ); + const taskName = buildTaskName(definition); + const cancellationKey = getRunTaskCommandCancellationKey( + rootProject.getProjectUri().fsPath, + definition.script ); + const terminal = new GradleRunnerTerminal(rootProject, args, cancellationKey); const task = new vscode.Task( definition, rootProject.getWorkspaceFolder(), - buildTaskName(definition), + taskName, 'gradle', new vscode.CustomExecution( async (): Promise => { @@ -321,9 +340,8 @@ export function cloneTask( args, javaDebug, }; - const rootProject = new RootProject( - task.scope as vscode.WorkspaceFolder, - vscode.Uri.file(definition.projectFolder) - ); - return createTaskFromDefinition(definition, rootProject); + const rootProject = Extension.getInstance() + .getRootProjectsStore() + .get(definition.projectFolder); + return createTaskFromDefinition(definition, rootProject!); } diff --git a/extension/src/terminal/CustomBuildTaskTerminal.ts b/extension/src/terminal/GradleRunnerTerminal.ts similarity index 78% rename from extension/src/terminal/CustomBuildTaskTerminal.ts rename to extension/src/terminal/GradleRunnerTerminal.ts index ccbce1c87..142b0d27c 100644 --- a/extension/src/terminal/CustomBuildTaskTerminal.ts +++ b/extension/src/terminal/GradleRunnerTerminal.ts @@ -1,45 +1,47 @@ import * as vscode from 'vscode'; import * as util from 'util'; -import getPort from 'get-port'; -import { isTaskRunning } from '../tasks/taskUtil'; -import { waitOnTcp, isTest } from '../util'; +import { isTest, waitOnTcp } from '../util'; import { logger, LoggerStream, LogVerbosity } from '../logger'; -import { Extension } from '../extension'; import { Output } from '../proto/gradle_pb'; -import { COMMAND_CANCEL_TASK } from '../commands'; import { ServiceError } from '@grpc/grpc-js'; +import { RootProject } from '../rootProject/RootProject'; +import { isTaskRunning } from '../tasks/taskUtil'; +import getPort from 'get-port'; +import { Extension } from '../extension'; +import { COMMAND_CANCEL_BUILD } from '../commands'; const NL = '\n'; const CR = '\r'; const nlRegExp = new RegExp(`${NL}([^${CR}])`, 'g'); -export class CustomBuildTaskTerminal implements vscode.Pseudoterminal { +export class GradleRunnerTerminal { private readonly writeEmitter = new vscode.EventEmitter(); - public readonly onDidWrite: vscode.Event = this.writeEmitter.event; + private stdOutLoggerStream: LoggerStream; private readonly closeEmitter = new vscode.EventEmitter(); private task?: vscode.Task; + public readonly onDidWrite: vscode.Event = this.writeEmitter.event; public readonly onDidClose?: vscode.Event = this.closeEmitter.event; - private stdOutLoggerStream: LoggerStream; constructor( - private readonly workspaceFolder: vscode.WorkspaceFolder, - private readonly projectFolder: vscode.Uri + private readonly rootProject: RootProject, + private readonly args: string[], + private readonly cancellationKey: string ) { // TODO: this is only needed for the tests. Find a better way to test task output in the tests. this.stdOutLoggerStream = new LoggerStream(logger, LogVerbosity.INFO); } - public setTask(task: vscode.Task): void { - this.task = task; + public open(): void { + this.runBuild(); } - public open(): void { - this.doBuild(); + public setTask(task: vscode.Task): void { + this.task = task; } public close(): void { if (this.task && isTaskRunning(this.task)) { - vscode.commands.executeCommand(COMMAND_CANCEL_TASK, this.task); + this.cancelCommand(); } } @@ -47,7 +49,7 @@ export class CustomBuildTaskTerminal implements vscode.Pseudoterminal { try { await waitOnTcp('localhost', javaDebugPort); const startedDebugging = await vscode.debug.startDebugging( - this.workspaceFolder, + this.rootProject.getWorkspaceFolder(), { type: 'java', name: 'Debug (Attach) via Gradle Tasks', @@ -65,21 +67,19 @@ export class CustomBuildTaskTerminal implements vscode.Pseudoterminal { } } - private async doBuild(): Promise { - const args: string[] = this.task!.definition.args.split(' ').filter( - Boolean - ); + private async runBuild(): Promise { + const javaDebugEnabled = this.task ? this.task.definition.javaDebug : false; try { - const javaDebugEnabled = this.task!.definition.javaDebug; const javaDebugPort = javaDebugEnabled ? await getPort() : 0; const runTask = Extension.getInstance() .getClient() - .runTask( - this.projectFolder.fsPath, - this.task!, - args, + .runBuild( + this.rootProject.getProjectUri().fsPath, + this.cancellationKey, + this.args, '', javaDebugPort, + this.task, this.handleOutput, true ); @@ -94,6 +94,14 @@ export class CustomBuildTaskTerminal implements vscode.Pseudoterminal { } } + private cancelCommand(): void { + vscode.commands.executeCommand( + COMMAND_CANCEL_BUILD, + this.cancellationKey, + this.task + ); + } + private handleOutput = (output: Output): void => { const messageBytes = output.getOutputBytes_asU8(); if (messageBytes.length) { @@ -111,7 +119,7 @@ export class CustomBuildTaskTerminal implements vscode.Pseudoterminal { public handleInput(data: string): void { // sigint eg cmd/ctrl+C if (data === '\x03') { - vscode.commands.executeCommand(COMMAND_CANCEL_TASK, this.task); + this.cancelCommand(); } } diff --git a/extension/src/terminal/index.ts b/extension/src/terminal/index.ts index 38c469f60..79e11b45d 100644 --- a/extension/src/terminal/index.ts +++ b/extension/src/terminal/index.ts @@ -1 +1 @@ -export * from './CustomBuildTaskTerminal'; +export * from './GradleRunnerTerminal'; diff --git a/extension/src/test/integration/gradle/extension.test.ts b/extension/src/test/integration/gradle/extension.test.ts index ac95277ca..c715f5b2f 100644 --- a/extension/src/test/integration/gradle/extension.test.ts +++ b/extension/src/test/integration/gradle/extension.test.ts @@ -84,7 +84,7 @@ describe(getSuiteName('Extension'), () => { }); assert.ok(loggerAppendSpy.calledWith(sinon.match('Hello, World!'))); assert.ok( - loggerAppendLineSpy.calledWith(sinon.match('Completed task: hello')) + loggerAppendLineSpy.calledWith(sinon.match('Completed build: hello')) ); }); diff --git a/extension/src/test/unit/gradleDaemons.test.ts b/extension/src/test/unit/gradleDaemons.test.ts index 15c39cfa0..f41242123 100644 --- a/extension/src/test/unit/gradleDaemons.test.ts +++ b/extension/src/test/unit/gradleDaemons.test.ts @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -// TODO: add tests for filtering out duplicate versions - import * as assert from 'assert'; import * as vscode from 'vscode'; import * as sinon from 'sinon'; diff --git a/extension/src/test/unit/gradleTasks.test.ts b/extension/src/test/unit/gradleTasks.test.ts index 53f0094f3..a52ff9ee3 100644 --- a/extension/src/test/unit/gradleTasks.test.ts +++ b/extension/src/test/unit/gradleTasks.test.ts @@ -41,11 +41,12 @@ import { COMMAND_SHOW_LOGS, explorerFlatCommand, explorerTreeCommand, - cancelTaskCommand, COMMAND_RENDER_TASK, + cancelBuildCommand, } from '../../commands'; import { removeCancellingTask } from '../../tasks/taskUtil'; import { RootProjectsStore } from '../../stores'; +import { getRunTaskCommandCancellationKey } from '../../client/CancellationKeys'; const mockContext = buildMockContext(); const mockExtension = buildMockExtension(); @@ -101,10 +102,11 @@ mockGradleBuildWithoutTasks.setProject(mockGradleProjectWithoutTasks); describe(getSuiteName('Gradle tasks'), () => { beforeEach(() => { const icons = new Icons(mockContext); + const rootProjectsStore = new RootProjectsStore(); const gradleTasksTreeDataProvider = new GradleTasksTreeDataProvider( - mockContext + mockContext, + rootProjectsStore ); - const rootProjectsStore = new RootProjectsStore(); const gradleTaskProvider = new GradleTaskProvider(rootProjectsStore); const mockClient = buildMockClient(); mockExtension.getRootProjectsStore.returns(rootProjectsStore); @@ -124,13 +126,10 @@ describe(getSuiteName('Gradle tasks'), () => { }); describe('Without a multi-root workspace', () => { - beforeEach(() => { - sinon - .stub(vscode.workspace, 'workspaceFolders') - .value([mockWorkspaceFolder1]); - }); - describe('With no gradle tasks', () => { + beforeEach(() => { + stubWorkspaceFolders([mockWorkspaceFolder1]); + }); it('should build a "No Tasks" tree item when no tasks are found', async () => { mockExtension .getClient() @@ -382,7 +381,11 @@ describe(getSuiteName('Gradle tasks'), () => { vscode.commands, 'executeCommand' ); - cancelTaskCommand(task); + const cancellationKey = getRunTaskCommandCancellationKey( + mockTaskDefinition1ForFolder1.projectFolder, + task.name + ); + cancelBuildCommand(cancellationKey, task); assert.ok( executeCommandStub.calledWith(COMMAND_RENDER_TASK, task), 'Task was not rendered' diff --git a/extension/src/test/unit/pinnedTasks.test.ts b/extension/src/test/unit/pinnedTasks.test.ts index 0e361e3d6..6809ac89b 100644 --- a/extension/src/test/unit/pinnedTasks.test.ts +++ b/extension/src/test/unit/pinnedTasks.test.ts @@ -78,10 +78,11 @@ mockGradleBuild.setProject(mockGradleProject); describe(getSuiteName('Pinned tasks'), () => { beforeEach(() => { const icons = new Icons(mockContext); + const rootProjectsStore = new RootProjectsStore(); const gradleTasksTreeDataProvider = new GradleTasksTreeDataProvider( - mockContext + mockContext, + rootProjectsStore ); - const rootProjectsStore = new RootProjectsStore(); const gradleTaskProvider = new GradleTaskProvider(rootProjectsStore); const pinnedTasksStore = new PinnedTasksStore(mockContext); const pinnedTasksTreeDataProvider = new PinnedTasksTreeDataProvider( @@ -101,6 +102,7 @@ describe(getSuiteName('Pinned tasks'), () => { gradleTasksTreeDataProvider ); mockExtension.getIcons.returns(icons); + mockExtension.getRootProjectsStore.returns(rootProjectsStore); sinon.stub(Extension, 'getInstance').returns(mockExtension); logger.reset(); logger.setLoggingChannel(buildMockOutputChannel()); diff --git a/extension/src/test/unit/recentTasks.test.ts b/extension/src/test/unit/recentTasks.test.ts index 8c8b4618c..81abec1b0 100644 --- a/extension/src/test/unit/recentTasks.test.ts +++ b/extension/src/test/unit/recentTasks.test.ts @@ -99,6 +99,7 @@ describe(getSuiteName('Recent tasks'), () => { mockExtension.getGradleTaskProvider.returns(gradleTaskProvider); mockExtension.getTaskTerminalsStore.returns(taskTerminalsStore); mockExtension.getIcons.returns(icons); + mockExtension.getRootProjectsStore.returns(rootProjectsStore); sinon.stub(Extension, 'getInstance').returns(mockExtension); logger.reset(); diff --git a/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts b/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts index 7c069ba75..f65512b9b 100644 --- a/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts +++ b/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts @@ -9,28 +9,30 @@ import { TreeItemWithTasksOrGroups, } from '..'; -import { JavaDebug, getConfigJavaDebug } from '../../config'; import { GradleTaskDefinition } from '../../tasks'; import { isWorkspaceFolder } from '../../util'; import { Extension } from '../../extension'; import { isGradleTask } from '../../tasks/taskUtil'; +import { RootProjectsStore } from '../../stores'; -// eslint-disable-next-line sonarjs/no-unused-collection -export const gradleTaskTreeItemMap: Map = new Map(); -export const gradleProjectTreeItemMap: Map< - string, - RootProjectTreeItem -> = new Map(); -export const projectTreeItemMap: Map = new Map(); -export const groupTreeItemMap: Map = new Map(); -export const gradleProjectJavaDebugMap: Map = new Map(); +const gradleTaskTreeItemMap: Map = new Map(); +const gradleProjectTreeItemMap: Map = new Map(); +const projectTreeItemMap: Map = new Map(); +const groupTreeItemMap: Map = new Map(); + +export function getGradleTaskTreeItemMap(): Map { + return gradleTaskTreeItemMap; +} + +export function getProjectTreeItemMap(): Map { + return projectTreeItemMap; +} function resetCachedTreeItems(): void { gradleTaskTreeItemMap.clear(); gradleProjectTreeItemMap.clear(); projectTreeItemMap.clear(); groupTreeItemMap.clear(); - gradleProjectJavaDebugMap.clear(); } export class GradleTasksTreeDataProvider @@ -41,7 +43,10 @@ export class GradleTasksTreeDataProvider public readonly onDidChangeTreeData: vscode.Event = this ._onDidChangeTreeData.event; - constructor(private readonly context: vscode.ExtensionContext) { + constructor( + private readonly context: vscode.ExtensionContext, + private readonly rootProjectStore: RootProjectsStore + ) { const collapsed = this.context.workspaceState.get( 'gradleTasksCollapsed', false @@ -125,13 +130,17 @@ export class GradleTasksTreeDataProvider tasks.forEach((task) => { const definition = task.definition as GradleTaskDefinition; if (isWorkspaceFolder(task.scope) && isGradleTask(task)) { + const rootProject = this.rootProjectStore.get(definition.projectFolder); + if (!rootProject) { + return; + } gradleProjectTreeItem = gradleProjectTreeItemMap.get( definition.projectFolder ); if (!gradleProjectTreeItem) { gradleProjectTreeItem = new RootProjectTreeItem( path.basename(definition.projectFolder), - vscode.Uri.file(definition.projectFolder) + rootProject.getProjectUri() ); gradleProjectTreeItemMap.set( definition.projectFolder, @@ -139,19 +148,6 @@ export class GradleTasksTreeDataProvider ); } - if (!gradleProjectJavaDebugMap.has(definition.projectFolder)) { - const workspaceFolder = vscode.workspace.getWorkspaceFolder( - vscode.Uri.file(definition.projectFolder) - ); - if (!workspaceFolder) { - return; - } - gradleProjectJavaDebugMap.set( - definition.projectFolder, - getConfigJavaDebug(workspaceFolder) - ); - } - let projectTreeItem = projectTreeItemMap.get(definition.buildFile); if (!projectTreeItem) { projectTreeItem = new ProjectTreeItem( @@ -189,7 +185,7 @@ export class GradleTasksTreeDataProvider taskName, definition.description || taskName, '', - gradleProjectJavaDebugMap.get(definition.projectFolder) + rootProject.getJavaDebug() ); taskTreeItem.setContext(); diff --git a/extension/src/views/pinnedTasks/PinnedTasksTreeDataProvider.ts b/extension/src/views/pinnedTasks/PinnedTasksTreeDataProvider.ts index e39340e6a..0db160efe 100644 --- a/extension/src/views/pinnedTasks/PinnedTasksTreeDataProvider.ts +++ b/extension/src/views/pinnedTasks/PinnedTasksTreeDataProvider.ts @@ -5,43 +5,49 @@ import { PinnedTaskTreeItem, NoPinnedTasksTreeItem, } from '.'; -import { GradleTaskTreeItem, gradleProjectJavaDebugMap } from '..'; +import { GradleTaskTreeItem } from '..'; import { GradleTaskDefinition } from '../../tasks'; import { isWorkspaceFolder } from '../../util'; import { PinnedTasksStore, RootProjectsStore } from '../../stores'; import { Extension } from '../../extension'; import { TaskId, TaskArgs } from '../../stores/types'; import { cloneTask, isGradleTask } from '../../tasks/taskUtil'; +import { RootProject } from '../../rootProject/RootProject'; const pinnedTasksGradleProjectTreeItemMap: Map< string, PinnedTasksRootProjectTreeItem > = new Map(); -// eslint-disable-next-line sonarjs/no-unused-collection -export const pinnedTasksTreeItemMap: Map< - string, - PinnedTaskTreeItem -> = new Map(); +const pinnedTasksTreeItemMap: Map = new Map(); + +export function getPinnedTasksTreeItemMap(): Map { + return pinnedTasksTreeItemMap; +} function buildTaskTreeItem( gradleProjectTreeItem: PinnedTasksRootProjectTreeItem, - task: vscode.Task + task: vscode.Task, + rootProject: RootProject ): GradleTaskTreeItem { const definition = task.definition as GradleTaskDefinition; + const taskName = task.name; const pinnedTaskTreeItem = new PinnedTaskTreeItem( gradleProjectTreeItem, task, - task.name, - definition.description || task.name, - '', - gradleProjectJavaDebugMap.get(definition.projectFolder) + taskName, + definition.description || taskName, // tooltip + '', // description + rootProject.getJavaDebug() ); pinnedTaskTreeItem.setContext(); return pinnedTaskTreeItem; } -function buildGradleProjectTreeItem(task: vscode.Task): void { +function buildGradleProjectTreeItem( + task: vscode.Task, + rootProject: RootProject +): void { const definition = task.definition as GradleTaskDefinition; if (isWorkspaceFolder(task.scope) && isGradleTask(task)) { let gradleProjectTreeItem = pinnedTasksGradleProjectTreeItemMap.get( @@ -57,7 +63,11 @@ function buildGradleProjectTreeItem(task: vscode.Task): void { ); } - const pinnedTaskTreeItem = buildTaskTreeItem(gradleProjectTreeItem, task); + const pinnedTaskTreeItem = buildTaskTreeItem( + gradleProjectTreeItem, + task, + rootProject + ); pinnedTasksTreeItemMap.set( definition.id + definition.args, pinnedTaskTreeItem @@ -136,11 +146,16 @@ export class PinnedTasksTreeDataProvider if (!task) { return; } + const definition = task.definition as GradleTaskDefinition; + const rootProject = this.rootProjectsStore.get(definition.projectFolder); + if (!rootProject) { + return; + } const taskArgs = pinnedTasks.get(taskId) || ''; if (taskArgs) { Array.from(taskArgs.values()).forEach((args: TaskArgs) => { const pinnedTask = cloneTask(task, args); - buildGradleProjectTreeItem(pinnedTask); + buildGradleProjectTreeItem(pinnedTask, rootProject); }); } }); diff --git a/extension/src/views/recentTasks/RecentTaskTreeItem.ts b/extension/src/views/recentTasks/RecentTaskTreeItem.ts index 319e5672e..a31929dfa 100644 --- a/extension/src/views/recentTasks/RecentTaskTreeItem.ts +++ b/extension/src/views/recentTasks/RecentTaskTreeItem.ts @@ -23,6 +23,7 @@ export class RecentTaskTreeItem extends GradleTaskTreeItem { javaDebug: JavaDebug = { tasks: [] }, private readonly taskTerminalsStore: TaskTerminalsStore ) { + // On construction, don't set a description, this will be set in setContext super(parentTreeItem, task, label, description || label, '', javaDebug); } diff --git a/extension/src/views/recentTasks/RecentTasksTreeDataProvider.ts b/extension/src/views/recentTasks/RecentTasksTreeDataProvider.ts index c458ee6b9..5fabe6874 100644 --- a/extension/src/views/recentTasks/RecentTasksTreeDataProvider.ts +++ b/extension/src/views/recentTasks/RecentTasksTreeDataProvider.ts @@ -11,35 +11,38 @@ import { RootProjectsStore, } from '../../stores'; import { GradleTaskDefinition } from '../../tasks'; -import { gradleProjectJavaDebugMap, GradleTaskTreeItem } from '..'; +import { GradleTaskTreeItem } from '..'; import { isWorkspaceFolder } from '../../util'; import { Extension } from '../../extension'; import { TaskId, TaskArgs } from '../../stores/types'; import { cloneTask, isGradleTask } from '../../tasks/taskUtil'; +import { RootProject } from '../../rootProject/RootProject'; const recentTasksGradleProjectTreeItemMap: Map< string, RecentTasksRootProjectTreeItem > = new Map(); -// eslint-disable-next-line sonarjs/no-unused-collection -export const recentTasksTreeItemMap: Map< - string, - RecentTaskTreeItem -> = new Map(); +const recentTasksTreeItemMap: Map = new Map(); + +export function getRecentTaskTreeItemMap(): Map { + return recentTasksTreeItemMap; +} function buildTaskTreeItem( gradleProjectTreeItem: RecentTasksRootProjectTreeItem, task: vscode.Task, + rootProject: RootProject, taskTerminalsStore: TaskTerminalsStore ): RecentTaskTreeItem { const definition = task.definition as GradleTaskDefinition; + const taskName = task.name; const recentTaskTreeItem = new RecentTaskTreeItem( gradleProjectTreeItem, task, - task.name, - definition.description, - gradleProjectJavaDebugMap.get(definition.projectFolder), + taskName, + definition.description || taskName, // used for tooltip + rootProject.getJavaDebug(), taskTerminalsStore ); recentTaskTreeItem.setContext(); @@ -48,6 +51,7 @@ function buildTaskTreeItem( function buildGradleProjectTreeItem( task: vscode.Task, + rootProject: RootProject, taskTerminalsStore: TaskTerminalsStore ): void { const definition = task.definition as GradleTaskDefinition; @@ -68,6 +72,7 @@ function buildGradleProjectTreeItem( const recentTaskTreeItem = buildTaskTreeItem( gradleProjectTreeItem, task, + rootProject, taskTerminalsStore ); recentTasksTreeItemMap.set( @@ -169,12 +174,19 @@ export class RecentTasksTreeDataProvider return; } const definition = task.definition as GradleTaskDefinition; + const rootProject = this.rootProjectsStore.get(definition.projectFolder); + if (!rootProject) { + return; + } const taskArgs = recentTasks.get(taskId) || ''; - if (taskArgs) { Array.from(taskArgs.values()).forEach((args: TaskArgs) => { const recentTask = cloneTask(task, args, definition.javaDebug); - buildGradleProjectTreeItem(recentTask, this.taskTerminalsStore); + buildGradleProjectTreeItem( + recentTask, + rootProject, + this.taskTerminalsStore + ); }); } }); diff --git a/extension/src/views/viewUtil.ts b/extension/src/views/viewUtil.ts index 0e69a4c34..5ffd31fc4 100644 --- a/extension/src/views/viewUtil.ts +++ b/extension/src/views/viewUtil.ts @@ -6,18 +6,18 @@ import { TREE_ITEM_STATE_TASK_DEBUG_IDLE, TREE_ITEM_STATE_TASK_IDLE, } from './constants'; -import { - gradleTaskTreeItemMap, - pinnedTasksTreeItemMap, - recentTasksTreeItemMap, - projectTreeItemMap, -} from '.'; import { GradleTaskDefinition } from '../tasks'; import { logger } from '../logger'; import { JavaDebug } from '../config'; import { TaskArgs } from '../stores/types'; import { isTaskCancelling, isTaskRunning } from '../tasks/taskUtil'; -import { GradleTaskTreeItem } from './gradleTasks'; +import { + GradleTaskTreeItem, + getGradleTaskTreeItemMap, + getProjectTreeItemMap, + getPinnedTasksTreeItemMap, + getRecentTaskTreeItemMap, +} from '.'; import { Extension } from '../extension'; export function treeItemSortCompareFunc( @@ -48,17 +48,17 @@ export function getTreeItemForTask( task: vscode.Task ): GradleTaskTreeItem | null { const definition = task.definition as GradleTaskDefinition; - const gradleTaskTreeItem = gradleTaskTreeItemMap.get(definition.id); + const gradleTaskTreeItem = getGradleTaskTreeItemMap().get(definition.id); if (gradleTaskTreeItem && gradleTaskTreeItem.task === task) { return gradleTaskTreeItem; } - const pinnedTaskTreeItem = pinnedTasksTreeItemMap.get( + const pinnedTaskTreeItem = getPinnedTasksTreeItemMap().get( definition.id + definition.args ); if (pinnedTaskTreeItem && pinnedTaskTreeItem.task === task) { return pinnedTaskTreeItem; } - const recentTaskTreeItem = recentTasksTreeItemMap.get( + const recentTaskTreeItem = getRecentTaskTreeItemMap().get( definition.id + definition.args ); if (recentTaskTreeItem && recentTaskTreeItem.task === task) { @@ -69,20 +69,20 @@ export function getTreeItemForTask( export function updateGradleTreeItemStateForTask(task: vscode.Task): void { const definition = task.definition as GradleTaskDefinition; - const gradleTaskTreeItem = gradleTaskTreeItemMap.get(definition.id); + const gradleTaskTreeItem = getGradleTaskTreeItemMap().get(definition.id); const extension = Extension.getInstance(); if (gradleTaskTreeItem) { gradleTaskTreeItem.setContext(); extension.getGradleTasksTreeDataProvider().refresh(gradleTaskTreeItem); } - const pinTaskTreeItem = pinnedTasksTreeItemMap.get( + const pinTaskTreeItem = getPinnedTasksTreeItemMap().get( definition.id + definition.args ); if (pinTaskTreeItem) { pinTaskTreeItem.setContext(); extension.getPinnedTasksTreeDataProvider().refresh(pinTaskTreeItem); } - const recentTaskTreeItem = recentTasksTreeItemMap.get( + const recentTaskTreeItem = getRecentTaskTreeItemMap().get( definition.id + definition.args ); if (recentTaskTreeItem) { @@ -98,7 +98,7 @@ export async function focusTaskInGradleTasksTree( const definition = task.definition as GradleTaskDefinition; const treeItem = getTreeItemForTask(task); // null if running task from command palette if (treeItem === null || treeItem.constructor === GradleTaskTreeItem) { - const gradleTaskTreeItem = gradleTaskTreeItemMap.get(definition.id); + const gradleTaskTreeItem = getGradleTaskTreeItemMap().get(definition.id); if (gradleTaskTreeItem) { await Extension.getInstance() .getGradleTasksTreeView() @@ -119,7 +119,7 @@ export async function focusProjectInGradleTasksTree( await vscode.commands.executeCommand( `workbench.view.extension.${GRADLE_CONTAINER_VIEW}` ); - const treeItem = projectTreeItemMap.get(uri.fsPath); + const treeItem = getProjectTreeItemMap().get(uri.fsPath); if (treeItem) { await Extension.getInstance().getGradleTasksTreeView().reveal(treeItem, { focus: true, diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index 63ab24b5d..8d45ba0da 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -2,7 +2,6 @@ plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '6.0.0' - id 'com.diffplug.gradle.spotless' } description = 'vscode-gradle :: gradle-server' @@ -123,9 +122,5 @@ task serverStartScripts(type: CreateStartScripts) { ) } -task(format) { - dependsOn(spotlessApply) -} - -compileJava.dependsOn 'generateProto', spotlessCheck +compileJava.dependsOn 'generateProto', 'spotlessCheck' assemble.dependsOn serverStartScripts diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleBuildCancellation.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleBuildCancellation.java new file mode 100644 index 000000000..766a635fa --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleBuildCancellation.java @@ -0,0 +1,38 @@ +package com.github.badsyntax.gradle; + +import com.github.badsyntax.gradle.exceptions.GradleCancellationException; +import com.google.common.base.Strings; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.gradle.tooling.CancellationToken; +import org.gradle.tooling.CancellationTokenSource; +import org.gradle.tooling.GradleConnector; + +public class GradleBuildCancellation { + private static final ConcurrentMap tokens = + new ConcurrentHashMap<>(); + + private GradleBuildCancellation() {} + + public static CancellationToken buildToken(String cancellationKey) { + CancellationTokenSource cancellationTokenSource = GradleConnector.newCancellationTokenSource(); + tokens.put(cancellationKey, cancellationTokenSource); + return cancellationTokenSource.token(); + } + + public static void clearToken(String cancellationKey) { + tokens.remove(cancellationKey); + } + + public static void cancelBuild(String cancellationKey) throws GradleCancellationException { + if (Strings.isNullOrEmpty(cancellationKey)) { + throw new GradleCancellationException("No cancellation key specified"); + } + CancellationTokenSource cancellationTokenSource = tokens.get(cancellationKey); + if (cancellationTokenSource == null) { + throw new GradleCancellationException("Build is not running for key: " + cancellationKey); + } else { + cancellationTokenSource.cancel(); + } + } +} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleBuildRunner.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleBuildRunner.java new file mode 100644 index 000000000..3b27be966 --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleBuildRunner.java @@ -0,0 +1,127 @@ +package com.github.badsyntax.gradle; + +import com.github.badsyntax.gradle.exceptions.GradleBuildRunnerException; +import com.github.badsyntax.gradle.exceptions.GradleConnectionException; +import com.google.common.base.Strings; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.gradle.tooling.BuildLauncher; +import org.gradle.tooling.CancellationToken; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.gradle.tooling.events.OperationType; +import org.gradle.tooling.events.ProgressListener; + +public class GradleBuildRunner { + private static final String JAVA_TOOL_OPTIONS_ENV = "JAVA_TOOL_OPTIONS"; + + private String projectDir; + private List args; + private GradleConfig gradleConfig; + private String cancellationKey; + private Boolean colorOutput; + private int javaDebugPort; + private OutputStream standardOutputStream; + private OutputStream standardErrorStream; + private InputStream standardInputStream; + private ProgressListener progressListener; + + public GradleBuildRunner( + String projectDir, + List args, + GradleConfig gradleConfig, + String cancellationKey, + Boolean colorOutput, + int javaDebugPort) { + this.projectDir = projectDir; + this.args = args; + this.gradleConfig = gradleConfig; + this.cancellationKey = cancellationKey; + this.colorOutput = colorOutput; + this.javaDebugPort = javaDebugPort; + } + + public GradleBuildRunner( + String projectDir, List args, GradleConfig gradleConfig, String cancellationKey) { + this(projectDir, args, gradleConfig, cancellationKey, true, 0); + } + + public GradleBuildRunner setStandardOutputStream(OutputStream standardOutputStream) { + this.standardOutputStream = standardOutputStream; + return this; + } + + public GradleBuildRunner setStandardInputStream(InputStream standardInputStream) { + this.standardInputStream = standardInputStream; + return this; + } + + public GradleBuildRunner setStandardErrorStream(OutputStream standardErrorStream) { + this.standardErrorStream = standardErrorStream; + return this; + } + + public GradleBuildRunner setProgressListener(ProgressListener progressListener) { + this.progressListener = progressListener; + return this; + } + + public void run() throws GradleConnectionException, IOException, GradleBuildRunnerException { + GradleConnector gradleConnector = GradleProjectConnector.build(projectDir, gradleConfig); + try (ProjectConnection connection = gradleConnector.connect()) { + runBuild(connection); + } finally { + GradleBuildCancellation.clearToken(cancellationKey); + } + } + + private void runBuild(ProjectConnection connection) + throws GradleBuildRunnerException, IOException { + Set progressEvents = new HashSet<>(); + progressEvents.add(OperationType.PROJECT_CONFIGURATION); + progressEvents.add(OperationType.TASK); + progressEvents.add(OperationType.TRANSFORM); + + CancellationToken cancellationToken = GradleBuildCancellation.buildToken(cancellationKey); + + BuildLauncher build = + connection + .newBuild() + .withCancellationToken(cancellationToken) + .addProgressListener(progressListener, progressEvents) + .setStandardOutput(standardOutputStream) + .setStandardError(standardErrorStream) + .setColorOutput(colorOutput) + .withArguments(args); + + if (this.standardInputStream != null) { + build.setStandardInput(standardInputStream); + } + + if (javaDebugPort != 0) { + build.setEnvironmentVariables(buildJavaEnvVarsWithJwdp(javaDebugPort)); + } + + if (!Strings.isNullOrEmpty(gradleConfig.getJvmArguments())) { + build.setJvmArguments(gradleConfig.getJvmArguments()); + } + + build.run(); + } + + private static Map buildJavaEnvVarsWithJwdp(int javaDebugPort) { + HashMap envVars = new HashMap<>(System.getenv()); + envVars.put( + JAVA_TOOL_OPTIONS_ENV, + String.format( + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:%d", + javaDebugPort)); + return envVars; + } +} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java index 3b3f7094a..650f76484 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java @@ -1,10 +1,9 @@ package com.github.badsyntax.gradle; -import com.github.badsyntax.gradle.cancellation.CancellationHandler; -import com.github.badsyntax.gradle.handlers.CancelTaskHandler; +import com.github.badsyntax.gradle.handlers.CancelBuildHandler; import com.github.badsyntax.gradle.handlers.GetBuildHandler; import com.github.badsyntax.gradle.handlers.GetDaemonsStatusHandler; -import com.github.badsyntax.gradle.handlers.RunTaskHandler; +import com.github.badsyntax.gradle.handlers.RunBuildHandler; import com.github.badsyntax.gradle.handlers.StopDaemonHandler; import com.github.badsyntax.gradle.handlers.StopDaemonsHandler; import io.grpc.stub.StreamObserver; @@ -18,34 +17,16 @@ public void getBuild(GetBuildRequest req, StreamObserver response } @Override - public void runTask(RunTaskRequest req, StreamObserver responseObserver) { - RunTaskHandler runTaskHandler = new RunTaskHandler(req, responseObserver); - runTaskHandler.run(); + public void runBuild(RunBuildRequest req, StreamObserver responseObserver) { + RunBuildHandler runBuildHandler = new RunBuildHandler(req, responseObserver); + runBuildHandler.run(); } @Override - public void cancelGetBuilds( - CancelGetBuildsRequest req, StreamObserver responseObserver) { - CancellationHandler.cancelAllRunningBuilds(); - responseObserver.onNext( - CancelGetBuildsReply.newBuilder().setMessage("Cancel build projects requested").build()); - responseObserver.onCompleted(); - } - - @Override - public void cancelRunTask( - CancelRunTaskRequest req, StreamObserver responseObserver) { - CancelTaskHandler cancelTaskHandler = new CancelTaskHandler(req, responseObserver); - cancelTaskHandler.run(); - } - - @Override - public void cancelRunTasks( - CancelRunTasksRequest req, StreamObserver responseObserver) { - CancellationHandler.cancelAllRunningTasks(); - responseObserver.onNext( - CancelRunTasksReply.newBuilder().setMessage("Cancel running tasks requested").build()); - responseObserver.onCompleted(); + public void cancelBuild( + CancelBuildRequest req, StreamObserver responseObserver) { + CancelBuildHandler cancelRunBuildHandler = new CancelBuildHandler(req, responseObserver); + cancelRunBuildHandler.run(); } @Override diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/cancellation/CancellationHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/cancellation/CancellationHandler.java deleted file mode 100644 index 0c0c10c89..000000000 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/cancellation/CancellationHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.badsyntax.gradle.cancellation; - -import com.github.badsyntax.gradle.exceptions.GradleCancellationException; -import org.gradle.tooling.CancellationToken; -import org.gradle.tooling.CancellationTokenSource; -import org.gradle.tooling.GradleConnector; - -public class CancellationHandler { - private static final CancellationTokenPool cancellationTokenPool = new CancellationTokenPool(); - - private CancellationHandler() {} - - public static CancellationToken getCancellationToken( - CancellationTokenPool.TYPE cancellationType, String cancellationKey) { - CancellationTokenSource cancellationTokenSource = GradleConnector.newCancellationTokenSource(); - cancellationTokenPool.put(cancellationType, cancellationKey, cancellationTokenSource); - return cancellationTokenSource.token(); - } - - public static CancellationToken getBuildCancellationToken(String cancellationKey) { - return getCancellationToken(CancellationTokenPool.TYPE.GET, cancellationKey); - } - - public static CancellationToken getRunTaskCancellationToken(String cancellationKey) { - return getCancellationToken(CancellationTokenPool.TYPE.RUN, cancellationKey); - } - - public static void clearToken( - CancellationTokenPool.TYPE cancellationType, String cancellationKey) { - cancellationTokenPool.remove(cancellationType, cancellationKey); - } - - public static void clearBuildToken(String cancellationKey) { - clearToken(CancellationTokenPool.TYPE.GET, cancellationKey); - } - - public static void clearRunTaskToken(String cancellationKey) { - clearToken(CancellationTokenPool.TYPE.RUN, cancellationKey); - } - - public static void cancelRunTask(String cancellationKey) throws GradleCancellationException { - CancellationTokenSource cancellationTokenSource = - cancellationTokenPool.get(CancellationTokenPool.TYPE.RUN, cancellationKey); - if (cancellationTokenSource == null) { - throw new GradleCancellationException("Task is not running"); - } else { - cancellationTokenSource.cancel(); - } - } - - public static void cancelAllRunningBuilds() { - cancellationTokenPool.cancel(CancellationTokenPool.TYPE.GET); - } - - public static void cancelAllRunningTasks() { - cancellationTokenPool.cancel(CancellationTokenPool.TYPE.RUN); - } -} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/cancellation/CancellationTokenPool.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/cancellation/CancellationTokenPool.java deleted file mode 100644 index fc41f9397..000000000 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/cancellation/CancellationTokenPool.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.github.badsyntax.gradle.cancellation; - -import java.util.EnumMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import org.gradle.tooling.CancellationTokenSource; - -public class CancellationTokenPool { - - public enum TYPE { - RUN, - GET - } - - private final ConcurrentMap runTaskTokens = - new ConcurrentHashMap<>(); - private final ConcurrentMap getTasksTokens = - new ConcurrentHashMap<>(); - private final EnumMap> pool = - new EnumMap<>(TYPE.class); - - public CancellationTokenPool() { - pool.put(TYPE.RUN, runTaskTokens); - pool.put(TYPE.GET, getTasksTokens); - } - - public CancellationTokenSource get(TYPE type, String key) { - return pool.get(type).get(key); - } - - public void put(TYPE type, String key, CancellationTokenSource tokenSource) { - pool.get(type).put(key, tokenSource); - } - - public void remove(TYPE type, String key) { - pool.get(type).remove(key); - } - - public Map getPoolType(TYPE type) { - return pool.get(type); - } - - public Map> getPool() { - return pool; - } - - public void cancelAll() { - pool.keySet().stream() - .forEach( - typeKey -> - pool.get(typeKey).keySet().stream() - .forEach(poolKey -> pool.get(typeKey).get(poolKey).cancel())); - } - - public void cancel(CancellationTokenPool.TYPE type) { - Map poolOfType = getPoolType(type); - poolOfType.keySet().stream().forEach(key -> poolOfType.get(key).cancel()); - } -} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/exceptions/GradleBuildRunnerException.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/exceptions/GradleBuildRunnerException.java new file mode 100644 index 000000000..1172f132f --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/exceptions/GradleBuildRunnerException.java @@ -0,0 +1,13 @@ +package com.github.badsyntax.gradle.exceptions; + +public class GradleBuildRunnerException extends Exception { + private static final long serialVersionUID = 1L; + + public GradleBuildRunnerException(String message) { + super(message); + } + + public GradleBuildRunnerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/exceptions/GradleTaskRunnerException.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/exceptions/GradleTaskRunnerException.java deleted file mode 100644 index be03dee0f..000000000 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/exceptions/GradleTaskRunnerException.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.badsyntax.gradle.exceptions; - -public class GradleTaskRunnerException extends Exception { - private static final long serialVersionUID = 1L; - - public GradleTaskRunnerException(String message) { - super(message); - } - - public GradleTaskRunnerException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/CancelBuildHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/CancelBuildHandler.java new file mode 100644 index 000000000..2089a35a8 --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/CancelBuildHandler.java @@ -0,0 +1,47 @@ +package com.github.badsyntax.gradle.handlers; + +import com.github.badsyntax.gradle.CancelBuildReply; +import com.github.badsyntax.gradle.CancelBuildRequest; +import com.github.badsyntax.gradle.GradleBuildCancellation; +import com.github.badsyntax.gradle.exceptions.GradleCancellationException; +import io.grpc.stub.StreamObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CancelBuildHandler { + private static final Logger logger = LoggerFactory.getLogger(CancelBuildHandler.class.getName()); + + private CancelBuildRequest req; + private StreamObserver responseObserver; + + public CancelBuildHandler( + CancelBuildRequest req, StreamObserver responseObserver) { + this.req = req; + this.responseObserver = responseObserver; + } + + public void run() { + try { + GradleBuildCancellation.cancelBuild(req.getCancellationKey()); + replyWithCancelledSuccess(); + } catch (GradleCancellationException e) { + logger.error(e.getMessage()); + replyWithCancelError(e); + } finally { + responseObserver.onCompleted(); + } + } + + private void replyWithCancelledSuccess() { + responseObserver.onNext( + CancelBuildReply.newBuilder() + .setMessage("Cancel run build requested") + .setBuildRunning(true) + .build()); + } + + private void replyWithCancelError(Exception e) { + responseObserver.onNext( + CancelBuildReply.newBuilder().setMessage(e.getMessage()).setBuildRunning(false).build()); + } +} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/CancelTaskHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/CancelTaskHandler.java deleted file mode 100644 index eb54e300d..000000000 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/CancelTaskHandler.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.badsyntax.gradle.handlers; - -import com.github.badsyntax.gradle.CancelRunTaskReply; -import com.github.badsyntax.gradle.CancelRunTaskRequest; -import com.github.badsyntax.gradle.cancellation.CancellationHandler; -import com.github.badsyntax.gradle.exceptions.GradleCancellationException; -import io.grpc.stub.StreamObserver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CancelTaskHandler { - private static final Logger logger = LoggerFactory.getLogger(CancelTaskHandler.class.getName()); - - private CancelRunTaskRequest req; - private StreamObserver responseObserver; - - public CancelTaskHandler( - CancelRunTaskRequest req, StreamObserver responseObserver) { - this.req = req; - this.responseObserver = responseObserver; - } - - public void run() { - try { - CancellationHandler.cancelRunTask( - RunTaskHandler.getCancellationKey(req.getProjectDir(), req.getTask())); - replyWithCancelledSuccess(); - } catch (GradleCancellationException e) { - logger.error(e.getMessage()); - replyWithCancelError(e); - } finally { - responseObserver.onCompleted(); - } - } - - private void replyWithCancelledSuccess() { - responseObserver.onNext( - CancelRunTaskReply.newBuilder() - .setMessage("Cancel run task requested") - .setTaskRunning(true) - .build()); - } - - private void replyWithCancelError(Exception e) { - responseObserver.onNext( - CancelRunTaskReply.newBuilder().setMessage(e.getMessage()).setTaskRunning(false).build()); - } -} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetBuildHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetBuildHandler.java index 756cd3a1f..14f7d06a1 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetBuildHandler.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetBuildHandler.java @@ -8,6 +8,7 @@ import com.github.badsyntax.gradle.GetBuildRequest; import com.github.badsyntax.gradle.GetBuildResult; import com.github.badsyntax.gradle.GradleBuild; +import com.github.badsyntax.gradle.GradleBuildCancellation; import com.github.badsyntax.gradle.GradleEnvironment; import com.github.badsyntax.gradle.GradleProject; import com.github.badsyntax.gradle.GradleProjectConnector; @@ -15,7 +16,6 @@ import com.github.badsyntax.gradle.JavaEnvironment; import com.github.badsyntax.gradle.Output; import com.github.badsyntax.gradle.Progress; -import com.github.badsyntax.gradle.cancellation.CancellationHandler; import com.github.badsyntax.gradle.exceptions.GradleConnectionException; import com.google.common.base.Strings; import com.google.protobuf.ByteString; @@ -25,6 +25,7 @@ import java.util.Set; import org.gradle.internal.service.ServiceCreationException; import org.gradle.tooling.BuildCancelledException; +import org.gradle.tooling.CancellationToken; import org.gradle.tooling.GradleConnector; import org.gradle.tooling.ModelBuilder; import org.gradle.tooling.ProjectConnection; @@ -46,14 +47,6 @@ public GetBuildHandler(GetBuildRequest req, StreamObserver respon this.responseObserver = responseObserver; } - public static String getCancellationKey(String projectDir) { - return projectDir; - } - - public String getCancellationKey() { - return GetBuildHandler.getCancellationKey(req.getProjectDir()); - } - public void run() { GradleConnector gradleConnector; try { @@ -77,7 +70,7 @@ public void run() { logger.error(e.getMessage()); replyWithError(e); } finally { - CancellationHandler.clearBuildToken(getCancellationKey()); + GradleBuildCancellation.clearToken(req.getCancellationKey()); } } @@ -97,8 +90,12 @@ private org.gradle.tooling.model.GradleProject buildGradleProject(ProjectConnect replyWithProgress(event); } }; + + CancellationToken cancellationToken = + GradleBuildCancellation.buildToken(req.getCancellationKey()); + projectBuilder - .withCancellationToken(CancellationHandler.getBuildCancellationToken(getCancellationKey())) + .withCancellationToken(cancellationToken) .addProgressListener(progressListener, progressEvents) .setStandardOutput( new ByteBufferOutputStream() { @@ -173,7 +170,7 @@ private Environment buildEnvironment(ProjectConnection connection) { .build(); } - public void replyWithProject(GradleProject gradleProject) { + private void replyWithProject(GradleProject gradleProject) { responseObserver.onNext( GetBuildReply.newBuilder() .setGetBuildResult( @@ -183,7 +180,7 @@ public void replyWithProject(GradleProject gradleProject) { responseObserver.onCompleted(); } - public void replyWithCancelled(BuildCancelledException e) { + private void replyWithCancelled(BuildCancelledException e) { responseObserver.onNext( GetBuildReply.newBuilder() .setCancelled( @@ -194,11 +191,11 @@ public void replyWithCancelled(BuildCancelledException e) { responseObserver.onCompleted(); } - public void replyWithError(Exception e) { + private void replyWithError(Exception e) { responseObserver.onError(ErrorMessageBuilder.build(e)); } - public void replyWithBuildEnvironment(Environment environment) { + private void replyWithBuildEnvironment(Environment environment) { responseObserver.onNext(GetBuildReply.newBuilder().setEnvironment(environment).build()); } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/RunBuildHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/RunBuildHandler.java new file mode 100644 index 000000000..f37ce799c --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/RunBuildHandler.java @@ -0,0 +1,145 @@ +package com.github.badsyntax.gradle.handlers; + +import com.github.badsyntax.gradle.ByteBufferOutputStream; +import com.github.badsyntax.gradle.Cancelled; +import com.github.badsyntax.gradle.ErrorMessageBuilder; +import com.github.badsyntax.gradle.GradleBuildRunner; +import com.github.badsyntax.gradle.Output; +import com.github.badsyntax.gradle.Progress; +import com.github.badsyntax.gradle.RunBuildReply; +import com.github.badsyntax.gradle.RunBuildRequest; +import com.github.badsyntax.gradle.RunBuildResult; +import com.github.badsyntax.gradle.exceptions.GradleBuildRunnerException; +import com.github.badsyntax.gradle.exceptions.GradleConnectionException; +import com.google.common.base.Strings; +import com.google.protobuf.ByteString; +import io.grpc.stub.StreamObserver; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.gradle.tooling.BuildCancelledException; +import org.gradle.tooling.BuildException; +import org.gradle.tooling.UnsupportedVersionException; +import org.gradle.tooling.events.ProgressEvent; +import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RunBuildHandler { + private static final Logger logger = LoggerFactory.getLogger(RunBuildHandler.class.getName()); + + private RunBuildRequest req; + private StreamObserver responseObserver; + + public RunBuildHandler(RunBuildRequest req, StreamObserver responseObserver) { + this.req = req; + this.responseObserver = responseObserver; + } + + public void run() { + GradleBuildRunner gradleRunner = + new GradleBuildRunner( + req.getProjectDir(), + req.getArgsList(), + req.getGradleConfig(), + req.getCancellationKey(), + req.getShowOutputColors(), + req.getJavaDebugPort()); + gradleRunner + .setProgressListener( + (ProgressEvent event) -> { + synchronized (RunBuildHandler.class) { + replyWithProgress(event); + } + }) + .setStandardOutputStream( + new ByteBufferOutputStream() { + @Override + public void onFlush(byte[] bytes) { + synchronized (RunBuildHandler.class) { + replyWithStandardOutput(bytes); + } + } + }) + .setStandardErrorStream( + new ByteBufferOutputStream() { + @Override + public void onFlush(byte[] bytes) { + synchronized (RunBuildHandler.class) { + replyWithStandardError(bytes); + } + } + }); + + if (!Strings.isNullOrEmpty(req.getInput())) { + gradleRunner.setStandardInputStream(new ByteArrayInputStream(req.getInput().getBytes())); + } + + try { + gradleRunner.run(); + replyWithSuccess(); + responseObserver.onCompleted(); + } catch (BuildCancelledException e) { + replyWithCancelled(e); + responseObserver.onCompleted(); + } catch (GradleConnectionException + | BuildException + | UnsupportedVersionException + | UnsupportedBuildArgumentException + | IllegalStateException + | IOException + | GradleBuildRunnerException e) { + logger.error(e.getMessage()); + replyWithError(e); + } + } + + public void replyWithCancelled(BuildCancelledException e) { + responseObserver.onNext( + RunBuildReply.newBuilder() + .setCancelled( + Cancelled.newBuilder() + .setMessage(e.getMessage()) + .setProjectDir(req.getProjectDir())) + .build()); + } + + public void replyWithError(Exception e) { + responseObserver.onError(ErrorMessageBuilder.build(e)); + } + + public void replyWithSuccess() { + responseObserver.onNext( + RunBuildReply.newBuilder() + .setRunBuildResult(RunBuildResult.newBuilder().setMessage("Successfully run build")) + .build()); + } + + private void replyWithProgress(ProgressEvent progressEvent) { + responseObserver.onNext( + RunBuildReply.newBuilder() + .setProgress(Progress.newBuilder().setMessage(progressEvent.getDisplayName())) + .build()); + } + + private void replyWithStandardOutput(byte[] bytes) { + ByteString byteString = ByteString.copyFrom(bytes); + responseObserver.onNext( + RunBuildReply.newBuilder() + .setOutput( + Output.newBuilder() + .setOutputType(Output.OutputType.STDOUT) + .setOutputBytes(byteString)) + .build()); + } + + private void replyWithStandardError(byte[] bytes) { + ByteString byteString = ByteString.copyFrom(bytes); + responseObserver.onNext( + RunBuildReply.newBuilder() + .setOutput( + Output.newBuilder() + .setOutputType(Output.OutputType.STDERR) + .setOutputBytes(byteString)) + .build()); + } +} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/RunTaskHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/RunTaskHandler.java deleted file mode 100644 index 00e7af507..000000000 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/RunTaskHandler.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.github.badsyntax.gradle.handlers; - -import com.github.badsyntax.gradle.ByteBufferOutputStream; -import com.github.badsyntax.gradle.Cancelled; -import com.github.badsyntax.gradle.ErrorMessageBuilder; -import com.github.badsyntax.gradle.GradleProjectConnector; -import com.github.badsyntax.gradle.Output; -import com.github.badsyntax.gradle.Progress; -import com.github.badsyntax.gradle.RunTaskReply; -import com.github.badsyntax.gradle.RunTaskRequest; -import com.github.badsyntax.gradle.RunTaskResult; -import com.github.badsyntax.gradle.cancellation.CancellationHandler; -import com.github.badsyntax.gradle.exceptions.GradleConnectionException; -import com.github.badsyntax.gradle.exceptions.GradleTaskRunnerException; -import com.google.common.base.Strings; -import com.google.protobuf.ByteString; -import io.grpc.stub.StreamObserver; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import org.gradle.tooling.BuildCancelledException; -import org.gradle.tooling.BuildException; -import org.gradle.tooling.BuildLauncher; -import org.gradle.tooling.GradleConnector; -import org.gradle.tooling.ProjectConnection; -import org.gradle.tooling.UnsupportedVersionException; -import org.gradle.tooling.events.OperationType; -import org.gradle.tooling.events.ProgressEvent; -import org.gradle.tooling.events.ProgressListener; -import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class RunTaskHandler { - private static final Logger logger = LoggerFactory.getLogger(RunTaskHandler.class.getName()); - private static final String JAVA_TOOL_OPTIONS_ENV = "JAVA_TOOL_OPTIONS"; - - private RunTaskRequest req; - private StreamObserver responseObserver; - - public RunTaskHandler(RunTaskRequest req, StreamObserver responseObserver) { - this.req = req; - this.responseObserver = responseObserver; - } - - public static String getCancellationKey(String projectDir, String task) { - return projectDir + task; - } - - public String getCancellationKey() { - return RunTaskHandler.getCancellationKey(req.getProjectDir(), req.getTask()); - } - - public void run() { - GradleConnector gradleConnector; - try { - gradleConnector = GradleProjectConnector.build(req.getProjectDir(), req.getGradleConfig()); - } catch (GradleConnectionException e) { - logger.error(e.getMessage()); - responseObserver.onError(ErrorMessageBuilder.build(e)); - return; - } - - try (ProjectConnection connection = gradleConnector.connect()) { - runTask(connection); - replyWithSuccess(); - responseObserver.onCompleted(); - } catch (BuildCancelledException e) { - replyWithCancelled(e); - responseObserver.onCompleted(); - } catch (BuildException - | UnsupportedVersionException - | UnsupportedBuildArgumentException - | IllegalStateException - | IOException - | GradleTaskRunnerException e) { - logger.error(e.getMessage()); - replyWithError(e); - } finally { - CancellationHandler.clearRunTaskToken(getCancellationKey()); - } - } - - public void runTask(ProjectConnection connection) throws GradleTaskRunnerException, IOException { - Set progressEvents = new HashSet<>(); - progressEvents.add(OperationType.PROJECT_CONFIGURATION); - progressEvents.add(OperationType.TASK); - progressEvents.add(OperationType.TRANSFORM); - - ProgressListener progressListener = - (ProgressEvent event) -> { - synchronized (RunTaskHandler.class) { - replyWithProgress(event); - } - }; - - // Specifying the tasks to run via build arguments provides support for task *and* build - // arguments. - // Using BuildLauncher.forTasks() prevents us from specifying task args. - ArrayList argsList = new ArrayList(req.getArgsList()); - argsList.add(0, req.getTask()); - - BuildLauncher build = - connection - .newBuild() - .withCancellationToken( - CancellationHandler.getRunTaskCancellationToken(getCancellationKey())) - .addProgressListener(progressListener, progressEvents) - .setStandardOutput( - new ByteBufferOutputStream() { - @Override - public void onFlush(byte[] bytes) { - synchronized (RunTaskHandler.class) { - replyWithStandardOutput(bytes); - } - } - }) - .setStandardError( - new ByteBufferOutputStream() { - @Override - public void onFlush(byte[] bytes) { - synchronized (RunTaskHandler.class) { - replyWithStandardError(bytes); - } - } - }) - .setColorOutput(req.getShowOutputColors()) - .withArguments(argsList); - - if (!Strings.isNullOrEmpty(req.getInput())) { - InputStream inputStream = new ByteArrayInputStream(req.getInput().getBytes()); - build.setStandardInput(inputStream); - } - - if (Boolean.TRUE.equals(req.getJavaDebug())) { - if (req.getJavaDebugPort() == 0) { - throw new GradleTaskRunnerException("Java debug port is not set"); - } - build.setEnvironmentVariables(buildJavaEnvVarsWithJwdp(req.getJavaDebugPort())); - } - - if (!Strings.isNullOrEmpty(req.getGradleConfig().getJvmArguments())) { - build.setJvmArguments(req.getGradleConfig().getJvmArguments()); - } - - build.run(); - } - - private static Map buildJavaEnvVarsWithJwdp(int javaDebugPort) { - HashMap envVars = new HashMap<>(System.getenv()); - envVars.put( - JAVA_TOOL_OPTIONS_ENV, - String.format( - "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:%d", - javaDebugPort)); - return envVars; - } - - public void replyWithCancelled(BuildCancelledException e) { - responseObserver.onNext( - RunTaskReply.newBuilder() - .setCancelled( - Cancelled.newBuilder() - .setMessage(e.getMessage()) - .setProjectDir(req.getProjectDir())) - .build()); - } - - public void replyWithError(Exception e) { - responseObserver.onError(ErrorMessageBuilder.build(e)); - } - - public void replyWithSuccess() { - responseObserver.onNext( - RunTaskReply.newBuilder() - .setRunTaskResult( - RunTaskResult.newBuilder() - .setMessage("Successfully run task") - .setTask(req.getTask())) - .build()); - } - - private void replyWithProgress(ProgressEvent progressEvent) { - responseObserver.onNext( - RunTaskReply.newBuilder() - .setProgress(Progress.newBuilder().setMessage(progressEvent.getDisplayName())) - .build()); - } - - private void replyWithStandardOutput(byte[] bytes) { - ByteString byteString = ByteString.copyFrom(bytes); - responseObserver.onNext( - RunTaskReply.newBuilder() - .setOutput( - Output.newBuilder() - .setOutputType(Output.OutputType.STDOUT) - .setOutputBytes(byteString)) - .build()); - } - - private void replyWithStandardError(byte[] bytes) { - ByteString byteString = ByteString.copyFrom(bytes); - responseObserver.onNext( - RunTaskReply.newBuilder() - .setOutput( - Output.newBuilder() - .setOutputType(Output.OutputType.STDERR) - .setOutputBytes(byteString)) - .build()); - } -} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonHandler.java index 422803b2f..9350cf948 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonHandler.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonHandler.java @@ -31,11 +31,11 @@ public void run() { } } - public void replyWithError(Exception e) { + private void replyWithError(Exception e) { responseObserver.onError(ErrorMessageBuilder.build(e)); } - public void replyWithSuccess(String message) { + private void replyWithSuccess(String message) { responseObserver.onNext(StopDaemonReply.newBuilder().setMessage(message).build()); responseObserver.onCompleted(); } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonsHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonsHandler.java index 91c7a6eb8..0dc8f896c 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonsHandler.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/StopDaemonsHandler.java @@ -35,11 +35,11 @@ public void run() { } } - public void replyWithError(Exception e) { + private void replyWithError(Exception e) { responseObserver.onError(ErrorMessageBuilder.build(e)); } - public void replyWithSuccess(String message) { + private void replyWithSuccess(String message) { responseObserver.onNext(StopDaemonsReply.newBuilder().setMessage(message).build()); } } diff --git a/gradle-server/src/test/java/com/github/badsyntax/gradle/GradleServerTest.java b/gradle-server/src/test/java/com/github/badsyntax/gradle/GradleServerTest.java index cbebafa50..296f765fd 100644 --- a/gradle-server/src/test/java/com/github/badsyntax/gradle/GradleServerTest.java +++ b/gradle-server/src/test/java/com/github/badsyntax/gradle/GradleServerTest.java @@ -291,29 +291,32 @@ public void getBuild_shouldStreamCorrectProgressEvents() throws IOException { } @Test - public void runTask_shouldSetProjectDirectory() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldSetProjectDirectory() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + ArrayList buildArgs = new ArrayList<>(); + buildArgs.add("tasks"); + + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) - .setTask("tasks") + .addAllArgs(buildArgs) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) .build(); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockConnector).forProjectDirectory(mockProjectDir); } @Test - public void runTask_shouldUseGradleUserHome() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldUseGradleUserHome() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig( GradleConfig.newBuilder() @@ -321,91 +324,72 @@ public void runTask_shouldUseGradleUserHome() throws IOException { .setWrapperEnabled(true)) .build(); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockConnector).useGradleUserHomeDir(mockGradleUserHome); } @Test - public void runTask_shouldThrowIfWrapperNotEnabledAndNoVersionSpecified() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldThrowIfWrapperNotEnabledAndNoVersionSpecified() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(false)) .build(); ArgumentCaptor onError = ArgumentCaptor.forClass(Throwable.class); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver).onError(onError.capture()); assertEquals("INTERNAL: Gradle version is required", onError.getValue().getMessage()); } @Test - public void runTask_shouldSetGradleVersionWrapperNotEnabledVersionSpecified() throws Exception { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldSetGradleVersionWrapperNotEnabledVersionSpecified() throws Exception { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(false).setVersion("6.3")) .build(); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); mockResponseObserver.onCompleted(); verify(mockResponseObserver, never()).onError(any()); verify(mockConnector).useGradleVersion("6.3"); } @Test - public void runTask_shouldUseJvmArgs() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldUseJvmArgs() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); String jvmArgs = "-Xmx64m -Xms64m"; - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig( GradleConfig.newBuilder().setJvmArguments(jvmArgs).setWrapperEnabled(true)) .build(); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockBuildLauncher).setJvmArguments(jvmArgs); } @Test - public void runTask_shouldThrowIfDebugAndNotPortSpecified() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); - - RunTaskRequest req = - RunTaskRequest.newBuilder() - .setProjectDir(mockProjectDir.getAbsolutePath().toString()) - .setJavaDebug(true) - .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) - .build(); - - ArgumentCaptor onError = ArgumentCaptor.forClass(Throwable.class); - stub.runTask(req, mockResponseObserver); - verify(mockResponseObserver).onError(onError.capture()); - assertEquals("INTERNAL: Java debug port is not set", onError.getValue().getMessage()); - } - - @Test - public void runTask_shouldSetJwdpEnvironmentVarIfDebug() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldSetJwdpEnvironmentVarIfDebug() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) - .setJavaDebug(true) .setJavaDebugPort(1111) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) .build(); @@ -413,7 +397,7 @@ public void runTask_shouldSetJwdpEnvironmentVarIfDebug() throws IOException { ArgumentCaptor> setEnvironmentVariables = ArgumentCaptor.forClass(HashMap.class); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockBuildLauncher).setEnvironmentVariables(setEnvironmentVariables.capture()); assertEquals( @@ -422,12 +406,12 @@ public void runTask_shouldSetJwdpEnvironmentVarIfDebug() throws IOException { } @Test - public void runTask_shouldSetStandardInput() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldSetStandardInput() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) .setInput("An input string") @@ -435,7 +419,7 @@ public void runTask_shouldSetStandardInput() throws IOException { ArgumentCaptor inputStream = ArgumentCaptor.forClass(InputStream.class); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockBuildLauncher).setStandardInput(inputStream.capture()); InputStreamReader isReader = new InputStreamReader(inputStream.getValue()); @@ -449,40 +433,40 @@ public void runTask_shouldSetStandardInput() throws IOException { } @Test - public void runTask_shouldSetColorOutput() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldSetColorOutput() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req1 = - RunTaskRequest.newBuilder() + RunBuildRequest req1 = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) .setShowOutputColors(false) .build(); - stub.runTask(req1, mockResponseObserver); + stub.runBuild(req1, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockBuildLauncher).setColorOutput(false); - RunTaskRequest req2 = - RunTaskRequest.newBuilder() + RunBuildRequest req2 = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) .setShowOutputColors(true) .build(); - stub.runTask(req2, mockResponseObserver); + stub.runBuild(req2, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockBuildLauncher).setColorOutput(true); } @Test - public void runTask_shouldStreamCorrectProgressEvents() throws IOException { - StreamObserver mockResponseObserver = - (StreamObserver) mock(StreamObserver.class); + public void runBuild_shouldStreamCorrectProgressEvents() throws IOException { + StreamObserver mockResponseObserver = + (StreamObserver) mock(StreamObserver.class); - RunTaskRequest req = - RunTaskRequest.newBuilder() + RunBuildRequest req = + RunBuildRequest.newBuilder() .setProjectDir(mockProjectDir.getAbsolutePath().toString()) .setGradleConfig(GradleConfig.newBuilder().setWrapperEnabled(true)) .setShowOutputColors(true) @@ -490,7 +474,7 @@ public void runTask_shouldStreamCorrectProgressEvents() throws IOException { ArgumentCaptor> onAddProgressListener = ArgumentCaptor.forClass(Set.class); - stub.runTask(req, mockResponseObserver); + stub.runBuild(req, mockResponseObserver); verify(mockResponseObserver, never()).onError(any()); verify(mockBuildLauncher) .addProgressListener( diff --git a/images/run-build.png b/images/run-build.png new file mode 100644 index 000000000..a50492ac8 Binary files /dev/null and b/images/run-build.png differ diff --git a/npm-package/index.ts b/npm-package/index.ts index c51d57697..ed3d0c65c 100644 --- a/npm-package/index.ts +++ b/npm-package/index.ts @@ -1,15 +1,15 @@ import { Output, - RunTaskRequest, - CancelRunTaskRequest, + RunBuildRequest, + CancelBuildRequest, } from './lib/proto/gradle_pb'; import type { Api, RunTaskOpts, CancelTaskOpts } from './lib/api/Api'; export { Output, - RunTaskRequest, + RunBuildRequest, RunTaskOpts, CancelTaskOpts, - CancelRunTaskRequest, + CancelBuildRequest, Api as ExtensionApi, }; diff --git a/proto/gradle.proto b/proto/gradle.proto index 2170995da..26ea1d578 100644 --- a/proto/gradle.proto +++ b/proto/gradle.proto @@ -8,25 +8,18 @@ package gradle; service Gradle { rpc GetBuild(GetBuildRequest) returns (stream GetBuildReply) {} + rpc RunBuild(RunBuildRequest) returns (stream RunBuildReply) {} + rpc CancelBuild(CancelBuildRequest) returns (CancelBuildReply) {} rpc GetDaemonsStatus(GetDaemonsStatusRequest) returns (GetDaemonsStatusReply) {} rpc StopDaemons(StopDaemonsRequest) returns (StopDaemonsReply) {} rpc StopDaemon(StopDaemonRequest) returns (StopDaemonReply) {} - rpc RunTask(RunTaskRequest) returns (stream RunTaskReply) {} - rpc CancelGetBuilds(CancelGetBuildsRequest) returns (CancelGetBuildsReply) {} - rpc CancelRunTask(CancelRunTaskRequest) returns (CancelRunTaskReply) {} - rpc CancelRunTasks(CancelRunTasksRequest) returns (CancelRunTasksReply) {} } -message CancelGetBuildsRequest {} - -message CancelGetBuildsReply { string message = 1; } - -message GetTasksRequest { string project_dir = 1; } - message GetBuildRequest { string project_dir = 1; - GradleConfig gradle_config = 2; - bool show_output_colors = 8; + string cancellation_key = 2; + GradleConfig gradle_config = 3; + bool show_output_colors = 4; } message GetBuildReply { @@ -39,50 +32,42 @@ message GetBuildReply { } } -message GetTasksResult { - string message = 1; - repeated GradleTask tasks = 2; -} - message GetBuildResult { string message = 1; GradleBuild build = 2; } -message RunTaskRequest { - enum OutputStream { - BYTES = 0; - STRING = 1; - } +message RunBuildRequest { string project_dir = 1; - string task = 2; + string cancellation_key = 2; repeated string args = 3; - bool java_debug = 4; - int32 java_debug_port = 5; - GradleConfig gradle_config = 6; - string input = 7; - bool show_output_colors = 8; + int32 java_debug_port = 4; + GradleConfig gradle_config = 5; + string input = 6; + bool show_output_colors = 7; } -message RunTaskResult { +message RunBuildResult { string message = 1; - string task = 2; } -message CancelRunTaskRequest { - string project_dir = 1; - string task = 2; +message RunBuildReply { + oneof kind { + RunBuildResult run_build_result = 1; + Progress progress = 2; + Output output = 3; + Cancelled cancelled = 4; + } } -message CancelRunTaskReply { - string message = 1; - string task = 2; - bool task_running = 3; +message CancelBuildRequest { + string cancellation_key = 1; } -message CancelRunTasksRequest {} - -message CancelRunTasksReply { string message = 1; } +message CancelBuildReply { + string message = 1; + bool build_running = 2; +} message GetDaemonsStatusRequest { string project_dir = 1; @@ -121,15 +106,6 @@ message DaemonInfo { string info = 3; } -message RunTaskReply { - oneof kind { - RunTaskResult run_task_result = 1; - Progress progress = 2; - Output output = 3; - Cancelled cancelled = 4; - } -} - message GradleConfig { string user_home = 2; string jvm_arguments = 3; @@ -158,7 +134,6 @@ message GradleTask { message Cancelled { string message = 1; string project_dir = 2; - string task = 3; } message Progress { string message = 1; }