Skip to content

Commit

Permalink
src/goTaskProvider: add default go task provider
Browse files Browse the repository at this point in the history
This new provider provides the execution for tasks
with type = "go". The task runs the `go` command and runs
the build or test command, along with optional 'args'.

The provider provides the default task definitions including
  - go build <current package>
  - go test <current package>
  - go build ./...
  - go test ./...

VS Code offers users to create custom task configurations from
the default task definition this provide provides.

By default a Go task runs from the workspace root folder with
the tools execution environment unless options "cwd"/"env" are
configured. Users who need to work with a module in a nested
directory have to either set up `go.work` in the workspace root
folder, or create custom task configurations that set options
"cwd" property.

Fixes golang#194

Change-Id: I3ee54af6e1b37031a60fdcb511fd1d961687314e
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/467697
Reviewed-by: Suzy Mueller <[email protected]>
TryBot-Result: kokoro <[email protected]>
Run-TryBot: Hyang-Ah Hana Kim <[email protected]>
  • Loading branch information
hyangah committed Feb 22, 2023
1 parent 599cc74 commit a0f2369
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 2 deletions.
5 changes: 5 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,11 @@ Default:

Prompt for surveys, including the gopls survey and the Go developer survey.

Default: `true`
### `go.tasks.provideDefault`

enable the default go build/test task provider.

Default: `true`
### `go.terminal.activateEnvironment`

Expand Down
56 changes: 55 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,12 @@
},
"additionalProperties": true
},
"go.tasks.provideDefault": {
"default": true,
"description": "enable the default go build/test task provider.",
"scope": "window",
"type": "boolean"
},
"go.terminal.activateEnvironment": {
"default": true,
"description": "Apply the Go & PATH environment variables used by the extension to all integrated terminals.",
Expand Down Expand Up @@ -2864,6 +2870,54 @@
"when": "go.hasProfiles"
}
]
}
},
"taskDefinitions": [
{
"type": "go",
"required": [
"command"
],
"properties": {
"label": {
"type": "string",
"description": "the name of go task"
},
"command": {
"type": "string",
"description": "go command to run",
"enum": [
"build",
"test"
],
"enumDescriptions": [
"go build",
"go test"
],
"default": "build"
},
"args": {
"type": "array",
"description": "additional arguments to pass to the go command (including the build target)",
"items": {
"type": "string"
}
},
"options": {
"type": "object",
"description": "additional command options",
"properties": {
"cwd": {
"type": "string",
"description": "the current working directory of the executed program or script. If omitted Code's current workspace root is used."
},
"env": {
"type": "object",
"markdownDescription": "Environment variables in the format of \"name\": \"value\"."
}
}
}
}
}
]
}
}
15 changes: 14 additions & 1 deletion src/goEnvironmentStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,24 @@ export function clearGoRuntimeBaseFromPATH() {
environmentVariableCollection?.delete(pathEnvVar);
}

function isTerminalOptions(
opts: vscode.TerminalOptions | vscode.ExtensionTerminalOptions
): opts is vscode.TerminalOptions {
return 'shellPath' in opts;
}

/**
* update the PATH variable in the given terminal to default to the currently selected Go
*/
export async function updateIntegratedTerminal(terminal: vscode.Terminal): Promise<void> {
if (!terminal) {
if (
!terminal ||
// don't interfere if this terminal was created to run a Go task (goTaskProvider.ts).
// Go task uses ProcessExecution which results in the terminal having `go` or `go.exe`
// as its shellPath.
(isTerminalOptions(terminal.creationOptions) &&
path.basename(terminal.creationOptions.shellPath || '') === correctBinname('go'))
) {
return;
}
const gorootBin = path.join(getCurrentGoRoot(), 'bin');
Expand Down
3 changes: 3 additions & 0 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { GoExplorerProvider } from './goExplorer';
import { GoExtensionContext } from './context';
import * as commands from './commands';
import { toggleVulncheckCommandFactory, VulncheckOutputLinkProvider } from './goVulncheck';
import { GoTaskProvider } from './goTaskProvider';

const goCtx: GoExtensionContext = {};

Expand Down Expand Up @@ -208,6 +209,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionA
wordPattern: /(-?\d*\.\d\w*)|([^`~!@#%^&*()\-=+[{\]}\\|;:'",.<>/?\s]+)/g
});

GoTaskProvider.setup(ctx, vscode.workspace);

// Vulncheck output link provider.
VulncheckOutputLinkProvider.activate(ctx);
registerCommand('go.vulncheck.toggle', toggleVulncheckCommandFactory);
Expand Down
158 changes: 158 additions & 0 deletions src/goTaskProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*---------------------------------------------------------
* 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.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
provideTasks(_: vscode.CancellationToken): 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".
// eslint-disable-next-line @typescript-eslint/no-unused-vars
resolveTask(_task: vscode.Task, _: vscode.CancellationToken): 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);
}
Loading

0 comments on commit a0f2369

Please sign in to comment.