Skip to content

Commit 397bad1

Browse files
committed
Support for multi-root .code-workspace workspaces
- Support workspace folder variable subsitution, ex. `${workspaceFolder:proj1}` - When a code-workspace add the configured `name` instead of the relative path as a postfix to the task name - Add same postfixes to plugin tasks - For Run and Debug Build commands, all fetching launch configurations from outside the workspace - Add a new test target to test a handful of currated suites to make sure that .code-workspace support does not break Issue: #1072
1 parent 17883e5 commit 397bad1

12 files changed

+264
-38
lines changed

.vscode-test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,39 @@ module.exports = defineConfig({
8888
reuseMachineInstall: !isCIBuild,
8989
installExtensions,
9090
},
91+
{
92+
label: "codeWorkspaceTests",
93+
files: [
94+
"dist/test/common.js",
95+
"dist/test/integration-tests/extension.test.js",
96+
"dist/test/integration-tests/WorkspaceContext.test.js",
97+
"dist/test/integration-tests/tasks/**/*.test.js",
98+
"dist/test/integration-tests/commands/build.test.js",
99+
],
100+
version: process.env["VSCODE_VERSION"] ?? "stable",
101+
workspaceFolder: "./assets/test.code-workspace",
102+
launchArgs,
103+
extensionDevelopmentPath: vsixPath
104+
? [`${__dirname}/.vscode-test/extensions/${publisher}.${name}-${version}`]
105+
: undefined,
106+
mocha: {
107+
ui: "tdd",
108+
color: true,
109+
timeout,
110+
forbidOnly: isCIBuild,
111+
grep: isFastTestRun ? "@slow" : undefined,
112+
invert: isFastTestRun,
113+
slow: 10000,
114+
reporter: path.join(__dirname, ".mocha-reporter.js"),
115+
reporterOptions: {
116+
jsonReporterOptions: {
117+
output: path.join(__dirname, "test-results", "code-workspace-tests.json"),
118+
},
119+
},
120+
},
121+
reuseMachineInstall: !isCIBuild,
122+
installExtensions,
123+
},
91124
{
92125
label: "unitTests",
93126
files: ["dist/test/common.js", "dist/test/unit-tests/**/*.test.js"],

.vscode/launch.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@
2929
},
3030
"preLaunchTask": "compile-tests"
3131
},
32+
{
33+
"name": "Code Workspace Tests",
34+
"type": "extensionHost",
35+
"request": "launch",
36+
"testConfiguration": "${workspaceFolder}/.vscode-test.js",
37+
"testConfigurationLabel": "codeWorkspaceTests",
38+
"args": ["--profile=testing-debug"],
39+
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
40+
"env": {
41+
"VSCODE_DEBUG": "1"
42+
},
43+
"preLaunchTask": "compile-tests"
44+
},
3245
{
3346
"name": "Unit Tests",
3447
"type": "extensionHost",

assets/test.code-workspace

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"folders": [
3+
{
4+
"name": "test",
5+
"path": "./test"
6+
},
7+
{
8+
"name": "defaultPackage",
9+
"path": "./test/defaultPackage"
10+
},
11+
{
12+
"name": "diagnostics",
13+
"path": "./test/diagnostics"
14+
},
15+
{
16+
"name": "command-plugin",
17+
"path": "./test/command-plugin"
18+
},
19+
],
20+
"settings": {
21+
"swift.disableAutoResolve": true,
22+
"swift.autoGenerateLaunchConfigurations": false,
23+
"swift.debugger.debugAdapter": "lldb-dap",
24+
"swift.debugger.setupCodeLLDB": "alwaysUpdateGlobal",
25+
"swift.additionalTestArguments": [
26+
"-Xswiftc",
27+
"-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING"
28+
],
29+
"lldb.verboseLogging": true,
30+
"swift.sourcekit-lsp.backgroundIndexing": "off"
31+
},
32+
"tasks": {
33+
"version": "2.0.0",
34+
"tasks": [
35+
{
36+
"type": "swift",
37+
"args": [
38+
"build",
39+
"--build-tests",
40+
"--verbose"
41+
],
42+
"cwd": "${workspaceFolder:defaultPackage}",
43+
"group": {
44+
"kind": "build",
45+
"isDefault": true
46+
},
47+
"label": "swift: Build All (defaultPackage)",
48+
"detail": "swift build --build-tests --verbose"
49+
},
50+
{
51+
"type": "swift",
52+
"args": [
53+
"build",
54+
"--show-bin-path"
55+
],
56+
"cwd": "${workspaceFolder:defaultPackage}",
57+
"group": "build",
58+
"label": "swift: Build All from tasks.json",
59+
"detail": "swift build --show-bin-path"
60+
},
61+
{
62+
"type": "swift-plugin",
63+
"command": "command_plugin",
64+
"args": [
65+
"--foo"
66+
],
67+
"cwd": "${workspaceFolder:command-plugin}",
68+
"disableSandbox": true,
69+
"label": "swift: command-plugin from tasks.json",
70+
"detail": "swift package command_plugin --foo"
71+
}
72+
]
73+
},
74+
"launch": {
75+
"version": "0.2.0",
76+
"configurations": [
77+
{
78+
"type": "swift",
79+
"request": "launch",
80+
"name": "Debug PackageExe (defaultPackage)",
81+
"program": "${workspaceFolder:defaultPackage}/.build/debug/PackageExe",
82+
"args": [],
83+
"cwd": "${workspaceFolder:defaultPackage}",
84+
"preLaunchTask": "swift: Build Debug PackageExe (defaultPackage)",
85+
"disableASLR": false,
86+
"initCommands": [
87+
"settings set target.disable-aslr false"
88+
],
89+
},
90+
{
91+
"type": "swift",
92+
"request": "launch",
93+
"name": "Release PackageExe (defaultPackage)",
94+
"program": "${workspaceFolder:defaultPackage}/.build/release/PackageExe",
95+
"args": [],
96+
"cwd": "${workspaceFolder:defaultPackage}",
97+
"preLaunchTask": "swift: Build Release PackageExe (defaultPackage)",
98+
"disableASLR": false,
99+
"initCommands": [
100+
"settings set target.disable-aslr false"
101+
],
102+
}
103+
],
104+
"compounds": []
105+
}
106+
}

src/configuration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,13 @@ function computeVscodeVar(varName: string): string | null {
496496

497497
const file = () => vscode.window.activeTextEditor?.document?.uri?.fsPath || "";
498498

499+
const regex = /workspaceFolder:(.*)/gm;
500+
const match = regex.exec(varName);
501+
if (match) {
502+
const name = match[1];
503+
return vscode.workspace.workspaceFolders?.find(f => f.name === name)?.uri.fsPath ?? null;
504+
}
505+
499506
// https://code.visualstudio.com/docs/editor/variables-reference
500507
// Variables to be substituted should be added here.
501508
const supportedVariables: { [k: string]: () => string } = {

src/debugger/launch.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,28 @@ export function getLaunchConfiguration(
110110
target: string,
111111
folderCtx: FolderContext
112112
): vscode.DebugConfiguration | undefined {
113-
const wsLaunchSection = vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder);
114-
const launchConfigs = wsLaunchSection.get<vscode.DebugConfiguration[]>("configurations") || [];
115-
const { folder } = getFolderAndNameSuffix(folderCtx);
116-
const targetPath = path.join(
117-
BuildFlags.buildDirectoryFromWorkspacePath(folder, true),
118-
"debug",
119-
target
120-
);
121-
// Users could be on different platforms with different path annotations,
122-
// so normalize before we compare.
123-
return launchConfigs.find(
124-
config => path.normalize(config.program) === path.normalize(targetPath)
125-
);
113+
for (const wsLaunchSection of [
114+
vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder),
115+
vscode.workspace.getConfiguration("launch"), // Needed for .code-workspace files
116+
]) {
117+
const launchConfigs =
118+
wsLaunchSection.get<vscode.DebugConfiguration[]>("configurations") || [];
119+
const { folder } = getFolderAndNameSuffix(folderCtx);
120+
const targetPath = path.join(
121+
BuildFlags.buildDirectoryFromWorkspacePath(folder, true),
122+
"debug",
123+
target
124+
);
125+
// Users could be on different platforms with different path annotations,
126+
// so normalize before we compare.
127+
const launchConfig = launchConfigs.find(
128+
config => path.normalize(config.program) === path.normalize(targetPath)
129+
);
130+
if (!launchConfig) {
131+
continue;
132+
}
133+
return launchConfig;
134+
}
126135
}
127136

128137
// Return array of DebugConfigurations for executables based on what is in Package.swift

src/tasks/SwiftPluginTaskProvider.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,26 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
5454
const tasks = [];
5555

5656
for (const folderContext of this.workspaceContext.folders) {
57+
let postfix = "";
5758
for (const plugin of folderContext.swiftPackage.plugins) {
59+
if (vscode.workspace.workspaceFile) {
60+
postfix = ` (${folderContext.name})`;
61+
} else if (folderContext.relativePath.length > 0) {
62+
postfix = ` (${folderContext.relativePath})`;
63+
}
5864
tasks.push(
59-
this.createSwiftPluginTask(plugin, folderContext.toolchain, {
60-
cwd: folderContext.folder,
61-
scope: folderContext.workspaceFolder,
62-
presentationOptions: {
63-
reveal: vscode.TaskRevealKind.Always,
65+
this.createSwiftPluginTask(
66+
plugin,
67+
folderContext.toolchain,
68+
{
69+
cwd: folderContext.folder,
70+
scope: folderContext.workspaceFolder,
71+
presentationOptions: {
72+
reveal: vscode.TaskRevealKind.Always,
73+
},
6474
},
65-
})
75+
postfix
76+
)
6677
);
6778
}
6879
}
@@ -121,7 +132,8 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
121132
createSwiftPluginTask(
122133
plugin: PackagePlugin,
123134
toolchain: SwiftToolchain,
124-
config: TaskConfig
135+
config: TaskConfig,
136+
postfix: string = ""
125137
): SwiftTask {
126138
const swift = toolchain.getToolchainExecutable("swift");
127139

@@ -156,7 +168,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
156168
} else {
157169
prefix = "";
158170
}
159-
task.detail = `${prefix}swift ${swiftArgs.join(" ")}`;
171+
task.detail = `${prefix}swift ${swiftArgs.join(" ")}${postfix}`;
160172
task.presentationOptions = presentation;
161173
return task as SwiftTask;
162174
}

src/tasks/SwiftTaskProvider.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ function buildAllTaskName(folderContext: FolderContext, release: boolean): strin
139139
let buildTaskName = release
140140
? `${SwiftTaskProvider.buildAllName} - Release`
141141
: SwiftTaskProvider.buildAllName;
142-
if (folderContext.relativePath.length > 0) {
142+
if (vscode.workspace.workspaceFile) {
143+
buildTaskName += ` (${folderContext.name})`;
144+
} else if (folderContext.relativePath.length > 0) {
143145
buildTaskName += ` (${folderContext.relativePath})`;
144146
}
145147
return buildTaskName;
@@ -189,11 +191,18 @@ export async function getBuildAllTask(
189191
// search for build all task in task.json first, that are valid for folder
190192
const tasks = await vscode.tasks.fetchTasks();
191193
const workspaceTasks = tasks.filter(task => {
192-
if (task.source !== "Workspace" || task.scope !== folderContext.workspaceFolder) {
194+
if (task.source !== "Workspace") {
193195
return false;
194196
}
197+
195198
const swiftExecutionOptions = (task.execution as SwiftExecution).options;
196199
let cwd = swiftExecutionOptions?.cwd;
200+
if (task.scope === vscode.TaskScope.Workspace) {
201+
return cwd && substituteVariablesInString(cwd) === folderContext.folder.fsPath;
202+
}
203+
if (task.scope !== folderContext.workspaceFolder) {
204+
return false;
205+
}
197206
if (cwd === "${workspaceFolder}" || cwd === undefined) {
198207
cwd = folderWorkingDir;
199208
}
@@ -233,7 +242,9 @@ export async function getBuildAllTask(
233242
function createBuildTasks(product: Product, folderContext: FolderContext): vscode.Task[] {
234243
const toolchain = folderContext.toolchain;
235244
let buildTaskNameSuffix = "";
236-
if (folderContext.relativePath.length > 0) {
245+
if (vscode.workspace.workspaceFile) {
246+
buildTaskNameSuffix = ` (${folderContext.name})`;
247+
} else if (folderContext.relativePath.length > 0) {
237248
buildTaskNameSuffix = ` (${folderContext.relativePath})`;
238249
}
239250

src/utilities/tasks.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@
1313
//===----------------------------------------------------------------------===//
1414
import * as path from "path";
1515
import * as vscode from "vscode";
16+
import { substituteVariablesInString } from "../configuration";
1617

1718
export function resolveTaskCwd(task: vscode.Task, cwd?: string): string | undefined {
1819
const scopeWorkspaceFolder = getScopeWorkspaceFolder(task);
1920
if (!cwd) {
2021
return scopeWorkspaceFolder;
2122
}
2223

24+
if (/\$\{.*\}/g.test(cwd)) {
25+
return substituteVariablesInString(cwd);
26+
}
27+
2328
if (path.isAbsolute(cwd)) {
2429
return cwd;
2530
} else if (scopeWorkspaceFolder) {

test/integration-tests/extension.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import * as assert from "assert";
1616
import { WorkspaceContext } from "../../src/WorkspaceContext";
1717
import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider";
1818
import { SwiftExecution } from "../../src/tasks/SwiftExecution";
19-
import { activateExtensionForTest } from "./utilities/testutilities";
19+
import { activateExtensionForTest, findFolderInWorkspace } from "./utilities/testutilities";
2020
import { expect } from "chai";
2121

2222
suite("Extension Test Suite", function () {
@@ -47,7 +47,7 @@ suite("Extension Test Suite", function () {
4747
this.timeout(60000);
4848
/** Verify tasks.json is being loaded */
4949
test("Tasks.json", async () => {
50-
const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage");
50+
const folder = findFolderInWorkspace("defaultPackage", workspaceContext);
5151
assert(folder);
5252
const buildAllTask = await getBuildAllTask(folder);
5353
const execution = buildAllTask.execution as SwiftExecution;

test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ suite("SwiftPluginTaskProvider Test Suite", function () {
245245
});
246246

247247
test("provides", () => {
248+
expect(task?.detail).to.equal("swift package command_plugin (command-plugin)");
248249
expect(task?.execution.args).to.deep.equal(
249250
folderContext.toolchain.buildFlags.withAdditionalFlags([
250251
"package",
@@ -271,7 +272,7 @@ suite("SwiftPluginTaskProvider Test Suite", function () {
271272
});
272273

273274
test("provides", () => {
274-
expect(task?.detail).to.include("swift package command_plugin --foo");
275+
expect(task?.detail).to.equal("swift package command_plugin --foo");
275276
});
276277

277278
test("executes", async () => {

test/integration-tests/tasks/TaskQueue.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { testAssetPath } from "../../fixtures";
1818
import { WorkspaceContext } from "../../../src/WorkspaceContext";
1919
import { SwiftExecOperation, TaskOperation, TaskQueue } from "../../../src/tasks/TaskQueue";
2020
import { waitForNoRunningTasks } from "../../utilities/tasks";
21-
import { activateExtensionForSuite } from "../utilities/testutilities";
21+
import { activateExtensionForSuite, findFolderInWorkspace } from "../utilities/testutilities";
2222

2323
suite("TaskQueue Test Suite", () => {
2424
let workspaceContext: WorkspaceContext;
@@ -160,7 +160,7 @@ suite("TaskQueue Test Suite", () => {
160160

161161
// check queuing task will return expected value
162162
test("swift exec", async () => {
163-
const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage");
163+
const folder = findFolderInWorkspace("defaultPackage", workspaceContext);
164164
assert(folder);
165165
const operation = new SwiftExecOperation(
166166
["--version"],
@@ -177,7 +177,7 @@ suite("TaskQueue Test Suite", () => {
177177

178178
// check queuing swift exec operation will throw expected error
179179
test("swift exec error", async () => {
180-
const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage");
180+
const folder = findFolderInWorkspace("defaultPackage", workspaceContext);
181181
assert(folder);
182182
const operation = new SwiftExecOperation(
183183
["--version"],

0 commit comments

Comments
 (0)