Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
25 changes: 22 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 11 additions & 3 deletions src/BackgroundCompilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ 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();
}
}
});

if (configuration.backgroundCompilation) {
if (configuration.backgroundCompilation.enabled) {
this.setupFileWatching();
}
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -96,4 +96,12 @@ export class BackgroundCompilation implements vscode.Disposable {
// can ignore if running task fails
}
}

async getTask(): Promise<vscode.Task> {
return await getBuildAllTask(
this.folderContext,
configuration.backgroundCompilation.release,
configuration.backgroundCompilation.useDefaultTask
);
}
}
26 changes: 23 additions & 3 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -414,10 +423,21 @@ const configuration = {
.get<boolean>("createTasksForLibraryProducts", false);
},
/** background compilation */
get backgroundCompilation(): boolean {
return vscode.workspace
get backgroundCompilation(): BackgroundCompilationConfiguration {
const value = vscode.workspace
.getConfiguration("swift")
.get<boolean>("backgroundCompilation", false);
.get<BackgroundCompilationConfiguration | boolean>("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" {
Expand Down
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
16 changes: 10 additions & 6 deletions src/tasks/SwiftTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ export async function createBuildAllTask(
*/
export async function getBuildAllTask(
folderContext: FolderContext,
release: boolean = false
release: boolean = false,
findDefault: boolean = true
): Promise<vscode.Task> {
const buildTaskName = buildAllTaskName(folderContext, release);
const folderWorkingDir = folderContext.workspaceFolder.uri.fsPath;
Expand All @@ -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}`);
Expand Down
159 changes: 127 additions & 32 deletions test/integration-tests/BackgroundCompilation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(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<void>(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);
});
});
});
});