| /*--------------------------------------------------------- | |
| * Copyright 2023 The Go Authors. All rights reserved. | |
| * Licensed under the MIT License. See LICENSE in the project root for license information. | |
| *--------------------------------------------------------*/ | |
| import * as vscode from 'vscode'; | |
| import { getGoConfig } from './config'; | |
| import { toolExecutionEnvironment } from './goEnv'; | |
| import { getBinPath } from './util'; | |
| const TASK_TYPE = 'go'; | |
| type GoCommand = 'build' | 'test'; // TODO(hyangah): run, install? | |
| interface GoTaskDefinition extends vscode.TaskDefinition { | |
| label?: string; | |
| command: GoCommand; | |
| args?: string[]; | |
| options?: vscode.ProcessExecutionOptions; | |
| // TODO(hyangah): plumb go.testFlags and go.buildFlags | |
| } | |
| type Workspace = Pick<typeof vscode.workspace, 'workspaceFolders' | 'getWorkspaceFolder'>; | |
| // GoTaskProvider provides default tasks | |
| // - build/test the current package | |
| // - build/test all packages in the current workspace | |
| // This default task provider can be disabled by `go.tasks.provideDefault`. | |
| // | |
| // Note that these tasks run from the workspace root folder. If the workspace root | |
| // folder and the package to test are not in the same module nor in the same workspace | |
| // defined by a go.work file, the build/test task will fail because the tested package | |
| // is not visible from the workspace root folder. | |
| export class GoTaskProvider implements vscode.TaskProvider { | |
| private constructor(private workspace: Workspace) {} | |
| static setup(ctx: vscode.ExtensionContext, workspace: Workspace): GoTaskProvider | undefined { | |
| if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { | |
| const provider = new GoTaskProvider(workspace); | |
| ctx.subscriptions.push(vscode.tasks.registerTaskProvider('go', provider)); | |
| return provider; | |
| } | |
| return undefined; | |
| } | |
| // provides the default tasks. | |
| provideTasks(): vscode.ProviderResult<vscode.Task[]> { | |
| const folders = this.workspace.workspaceFolders; | |
| if (!folders || !folders.length) { | |
| // zero workspace folder setup. | |
| // In zero-workspace folder setup, vscode.TaskScope.Workspace doesn't seem to work. | |
| // The task API does not implement vscode.TaskScope.Global yet. | |
| // Once Global scope is supported, we can consider to add tasks like `go build ${fileDirname}`. | |
| return undefined; | |
| } | |
| const opened = vscode.window.activeTextEditor?.document?.uri; | |
| const goCfg = getGoConfig(opened); | |
| if (!goCfg.get('tasks.provideDefault')) { | |
| return undefined; | |
| } | |
| // Explicitly specify the workspace folder directory based on the current open file. | |
| // Behavior of tasks constructed with vscode.TaskScope.Workspace as scope | |
| // is not well-defined when handling multiple folder workspace. | |
| const folder = (opened && this.workspace.getWorkspaceFolder(opened)) || folders[0]; | |
| return [ | |
| // all tasks run from the chosen workspace root folder. | |
| buildGoTask(folder, { | |
| type: TASK_TYPE, | |
| label: 'build package', | |
| command: 'build', | |
| args: ['${fileDirname}'] | |
| }), | |
| buildGoTask(folder, { type: TASK_TYPE, label: 'test package', command: 'test', args: ['${fileDirname}'] }), | |
| buildGoTask(folder, { type: TASK_TYPE, label: 'build workspace', command: 'build', args: ['./...'] }), | |
| buildGoTask(folder, { type: TASK_TYPE, label: 'test workspace', command: 'test', args: ['./...'] }) | |
| ]; | |
| } | |
| // fill an incomplete task definition ('tasks.json') whose type is "go". | |
| resolveTask(task: vscode.Task): vscode.ProviderResult<vscode.Task> { | |
| // vscode calls resolveTask for every 'go' type task in tasks.json. | |
| const def = task.definition; | |
| if (def && def.type === TASK_TYPE) { | |
| if (!def.command) { | |
| def.command = 'build'; | |
| } | |
| return buildGoTask(task.scope ?? vscode.TaskScope.Workspace, def as GoTaskDefinition); | |
| } | |
| return undefined; | |
| } | |
| } | |
| function buildGoTask(scope: vscode.WorkspaceFolder | vscode.TaskScope, definition: GoTaskDefinition): vscode.Task { | |
| const cwd = definition.options?.cwd ?? (isWorkspaceFolder(scope) ? scope.uri.fsPath : undefined); | |
| const task = new vscode.Task( | |
| definition, | |
| scope, | |
| definition.label ?? defaultTaskName(definition), | |
| TASK_TYPE, | |
| new vscode.ProcessExecution(getBinPath('go'), [definition.command, ...(definition.args ?? [])], { | |
| cwd, | |
| env: mergedToolExecutionEnv(scope, definition.options?.env) | |
| }), | |
| ['$go'] | |
| ); | |
| task.group = taskGroup(definition.command); | |
| task.detail = defaultTaskDetail(definition, cwd); | |
| task.runOptions = { reevaluateOnRerun: true }; | |
| task.isBackground = false; | |
| task.presentationOptions.clear = true; | |
| task.presentationOptions.echo = true; | |
| task.presentationOptions.showReuseMessage = true; | |
| task.presentationOptions.panel = vscode.TaskPanelKind.Dedicated; | |
| return task; | |
| } | |
| function defaultTaskName({ command, args }: GoTaskDefinition): string { | |
| return `go ${command} ${(args ?? []).join(' ')}`; | |
| } | |
| function defaultTaskDetail(def: GoTaskDefinition, cwd: string | undefined): string { | |
| const cd = cwd ? `cd ${cwd}; ` : ''; | |
| return `${cd}${defaultTaskName(def)}`; | |
| } | |
| function taskGroup(command: GoCommand): vscode.TaskGroup | undefined { | |
| switch (command) { | |
| case 'build': | |
| return vscode.TaskGroup.Build; | |
| case 'test': | |
| return vscode.TaskGroup.Test; | |
| default: | |
| return undefined; | |
| } | |
| } | |
| function isWorkspaceFolder(scope: vscode.WorkspaceFolder | vscode.TaskScope): scope is vscode.WorkspaceFolder { | |
| return typeof scope !== 'number' && 'uri' in scope; | |
| } | |
| function mergedToolExecutionEnv( | |
| scope: vscode.WorkspaceFolder | vscode.TaskScope, | |
| toAdd: { [key: string]: string } = {} | |
| ): { [key: string]: string } { | |
| const env = toolExecutionEnvironment(isWorkspaceFolder(scope) ? scope.uri : undefined, /* addProcessEnv: */ false); | |
| Object.keys(env).forEach((key) => { | |
| if (env[key] === undefined) { | |
| env[key] = ''; | |
| } | |
| }); // unset | |
| return Object.assign(env, toAdd); | |
| } |