diff --git a/CHANGELOG.md b/CHANGELOG.md index bb9c3ce01..2e724fa7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Prompt to restart `SourceKit-LSP` after changing `.sourcekit-lsp/config.json` files ([#1744](https://github.com/swiftlang/vscode-swift/issues/1744)) - Prompt to cancel and replace the active test run if one is in flight ([#1774](https://github.com/swiftlang/vscode-swift/pull/1774)) - A walkthrough for first time extension users ([#1560](https://github.com/swiftlang/vscode-swift/issues/1560)) +- Allow `swift.backgroundCompilation` setting to accept an object where enabling the `useDefaultTask` property will run the default build task, and the `release` property will run the `release` variant of the Build All task ([#1857](https://github.com/swiftlang/vscode-swift/pull/1857)) ### Fixed diff --git a/package.json b/package.json index e9b0cfac0..21af60a52 100644 --- a/package.json +++ b/package.json @@ -593,10 +593,29 @@ "scope": "machine-overridable" }, "swift.backgroundCompilation": { - "type": "boolean", + "type": [ + "boolean", + "object" + ], "default": false, - "markdownDescription": "**Experimental**: Run `swift build` in the background whenever a file is saved. It is possible the background compilation will already be running when you attempt a compile yourself, so this is disabled by default.", - "scope": "machine-overridable" + "markdownDescription": "Run `swift build` in the background whenever a file is saved. Setting to `true` enables, or you can use `object` notation for more fine grained control. It is possible the background compilation will already be running when you attempt a compile yourself, so this is disabled by default.", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Enable/disable background compilation." + }, + "useDefaultTask": { + "type": "boolean", + "default": true, + "markdownDescription": "Use the default build task configured using the `Tasks: Configure Default Build Task` command when executing the background compilation. `#enabled#` property must be `true`." + }, + "release": { + "type": "boolean", + "default": false, + "markdownDescription": "Use the `release` variant of the `Build All` task when executing the background compilation. `#enabled#` property must be `true`. This is ignored if the `#useDefaultTask#` property is true." + } + } }, "swift.actionAfterBuildError": { "type": "string", diff --git a/src/BackgroundCompilation.ts b/src/BackgroundCompilation.ts index 12c9f57ed..8de59e168 100644 --- a/src/BackgroundCompilation.ts +++ b/src/BackgroundCompilation.ts @@ -31,7 +31,7 @@ export class BackgroundCompilation implements vscode.Disposable { // We only want to configure the file watcher if background compilation is enabled. this.configurationEventDisposable = vscode.workspace.onDidChangeConfiguration(event => { if (event.affectsConfiguration("swift.backgroundCompilation", folderContext.folder)) { - if (configuration.backgroundCompilation) { + if (configuration.backgroundCompilation.enabled) { this.setupFileWatching(); } else { this.stopFileWatching(); @@ -39,7 +39,7 @@ export class BackgroundCompilation implements vscode.Disposable { } }); - if (configuration.backgroundCompilation) { + if (configuration.backgroundCompilation.enabled) { this.setupFileWatching(); } } @@ -86,7 +86,7 @@ export class BackgroundCompilation implements vscode.Disposable { */ async runTask() { // create compile task and execute it - const backgroundTask = await getBuildAllTask(this.folderContext); + const backgroundTask = await this.getTask(); if (!backgroundTask) { return; } @@ -96,4 +96,12 @@ export class BackgroundCompilation implements vscode.Disposable { // can ignore if running task fails } } + + async getTask(): Promise { + return await getBuildAllTask( + this.folderContext, + configuration.backgroundCompilation.release, + configuration.backgroundCompilation.useDefaultTask + ); + } } diff --git a/src/configuration.ts b/src/configuration.ts index cd45c60ea..d01a19ee9 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -105,6 +105,15 @@ export interface PluginPermissionConfiguration { allowNetworkConnections?: string; } +export interface BackgroundCompilationConfiguration { + /** enable background compilation task on save */ + enabled: boolean; + /** use the default `swift` build task when background compilation is enabled */ + useDefaultTask: boolean; + /** Use the `release` variant of the build all task */ + release: boolean; +} + /** * Type-safe wrapper around configuration settings. */ @@ -414,10 +423,21 @@ const configuration = { .get("createTasksForLibraryProducts", false); }, /** background compilation */ - get backgroundCompilation(): boolean { - return vscode.workspace + get backgroundCompilation(): BackgroundCompilationConfiguration { + const value = vscode.workspace .getConfiguration("swift") - .get("backgroundCompilation", false); + .get("backgroundCompilation", false); + return { + get enabled(): boolean { + return typeof value === "boolean" ? value : value.enabled; + }, + get useDefaultTask(): boolean { + return typeof value === "boolean" ? true : (value.useDefaultTask ?? true); + }, + get release(): boolean { + return typeof value === "boolean" ? false : (value.release ?? false); + }, + }; }, /** background indexing */ get backgroundIndexing(): "on" | "off" | "auto" { diff --git a/src/extension.ts b/src/extension.ts index 93676588a..9ee3c1437 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -189,13 +189,13 @@ function handleFolderEvent(logger: SwiftLogger): (event: FolderEvent) => Promise async function folderAdded(folder: FolderContext, workspace: WorkspaceContext) { if ( !configuration.folder(folder.workspaceFolder).disableAutoResolve || - configuration.backgroundCompilation + configuration.backgroundCompilation.enabled ) { // if background compilation is set then run compile at startup unless // this folder is a sub-folder of the workspace folder. This is to avoid // kicking off compile for multiple projects at the same time if ( - configuration.backgroundCompilation && + configuration.backgroundCompilation.enabled && folder.workspaceFolder.uri === folder.folder ) { await folder.backgroundCompilation.runTask(); diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index 95d062f4a..140692bd4 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -183,7 +183,8 @@ export async function createBuildAllTask( */ export async function getBuildAllTask( folderContext: FolderContext, - release: boolean = false + release: boolean = false, + findDefault: boolean = true ): Promise { const buildTaskName = buildAllTaskName(folderContext, release); const folderWorkingDir = folderContext.workspaceFolder.uri.fsPath; @@ -208,11 +209,14 @@ export async function getBuildAllTask( }); // find default build task - let task = workspaceTasks.find( - task => task.group?.id === vscode.TaskGroup.Build.id && task.group?.isDefault === true - ); - if (task) { - return task; + let task; + if (findDefault) { + task = workspaceTasks.find( + task => task.group?.id === vscode.TaskGroup.Build.id && task.group?.isDefault === true + ); + if (task) { + return task; + } } // find task with name "swift: Build All" task = workspaceTasks.find(task => task.name === `swift: ${buildTaskName}`); diff --git a/test/integration-tests/BackgroundCompilation.test.ts b/test/integration-tests/BackgroundCompilation.test.ts index 460c895df..3237158cc 100644 --- a/test/integration-tests/BackgroundCompilation.test.ts +++ b/test/integration-tests/BackgroundCompilation.test.ts @@ -11,55 +11,150 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import * as assert from "assert"; +import { expect } from "chai"; import * as vscode from "vscode"; +import { BackgroundCompilation } from "@src/BackgroundCompilation"; +import { FolderContext } from "@src/FolderContext"; import { WorkspaceContext } from "@src/WorkspaceContext"; +import { createSwiftTask, getBuildAllTask } from "@src/tasks/SwiftTaskProvider"; +import { mockGlobalObject } from "../MockUtils"; import { testAssetUri } from "../fixtures"; import { tag } from "../tags"; import { closeAllEditors } from "../utilities/commands"; import { waitForNoRunningTasks } from "../utilities/tasks"; -import { activateExtensionForTest, updateSettings } from "./utilities/testutilities"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "./utilities/testutilities"; tag("large").suite("BackgroundCompilation Test Suite", () => { - let subscriptions: vscode.Disposable[]; let workspaceContext: WorkspaceContext; + let folderContext: FolderContext; + let buildAllTask: vscode.Task; - activateExtensionForTest({ - async setup(ctx) { - subscriptions = []; - workspaceContext = ctx; - assert.notEqual(workspaceContext.folders.length, 0); - return await updateSettings({ - "swift.backgroundCompilation": true, + async function setupFolder(ctx: WorkspaceContext) { + workspaceContext = ctx; + folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); + buildAllTask = await getBuildAllTask(folderContext); + } + + suite("build all on save", () => { + let subscriptions: vscode.Disposable[]; + + activateExtensionForSuite({ + async setup(ctx) { + subscriptions = []; + await setupFolder(ctx); + return await updateSettings({ + "swift.backgroundCompilation": true, + }); + }, + }); + + suiteTeardown(async () => { + subscriptions.forEach(s => s.dispose()); + await closeAllEditors(); + }); + + test("runs build task", async () => { + const taskStartPromise = new Promise(resolve => { + subscriptions.push( + vscode.tasks.onDidStartTask(e => { + const task = e.execution.task; + if (task.name.includes("Build All")) { + resolve(); + } + }) + ); }); - }, - }); - suiteTeardown(async () => { - subscriptions.forEach(s => s.dispose()); - await closeAllEditors(); - }); + const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); + const doc = await vscode.workspace.openTextDocument(uri.fsPath); + await vscode.window.showTextDocument(doc); + await vscode.workspace.save(uri); - test("build all on save", async () => { - const taskStartPromise = new Promise(resolve => { - subscriptions.push( - vscode.tasks.onDidStartTask(e => { - const task = e.execution.task; - if (task.name.includes("Build All")) { - resolve(); - } - }) - ); + await taskStartPromise; + await waitForNoRunningTasks(); }); + }); - const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); - const doc = await vscode.workspace.openTextDocument(uri.fsPath); - await vscode.window.showTextDocument(doc); - await vscode.workspace.save(uri); + suite("getTask", () => { + const tasksMock = mockGlobalObject(vscode, "tasks"); + let swiftTask: vscode.Task; + let nonSwiftTask: vscode.Task; + let backgroundConfiguration: BackgroundCompilation; + + suite("useDefaultTask", () => { + activateExtensionForSuite({ + async setup(ctx) { + await setupFolder(ctx); + nonSwiftTask = new vscode.Task( + { + type: "shell", + command: ["swift"], + args: ["build"], + group: { + id: "build", + isDefault: true, + }, + label: "shell build", + }, + folderContext.workspaceFolder, + "shell build", + "Workspace", + new vscode.ShellExecution("", { + cwd: testAssetUri("defaultPackage").fsPath, + }) + ); + swiftTask = createSwiftTask( + ["build"], + "swift build", + { + cwd: testAssetUri("defaultPackage"), + scope: folderContext.workspaceFolder, + }, + folderContext.toolchain + ); + swiftTask.source = "Workspace"; + return await updateSettings({ + "swift.backgroundCompilation": { + enabled: true, + useDefaultTask: true, + }, + }); + }, + }); - await taskStartPromise; - await waitForNoRunningTasks(); + setup(() => { + tasksMock.fetchTasks.withArgs().resolves([nonSwiftTask, swiftTask, buildAllTask]); + backgroundConfiguration = new BackgroundCompilation(folderContext); + }); + + teardown(() => { + backgroundConfiguration.dispose(); + }); + + test("swift default task", async () => { + swiftTask.group = { id: "build", isDefault: true }; + expect(await backgroundConfiguration.getTask()).to.equal(swiftTask); + }); + + test("non-swift default task", async () => { + nonSwiftTask.group = { id: "build", isDefault: true }; + expect(await backgroundConfiguration.getTask()).to.equal(nonSwiftTask); + }); + + test("don't use default task", async () => { + swiftTask.group = { id: "build", isDefault: true }; + await vscode.workspace.getConfiguration("swift").update("backgroundCompilation", { + enabled: true, + useDefaultTask: false, + }); + expect(await backgroundConfiguration.getTask()).to.equal(buildAllTask); + }); + }); }); });