Skip to content

Commit 5ec4e94

Browse files
authored
Ensure the extension activates with a .bsp folder (#1865)
1 parent 31d58ac commit 5ec4e94

File tree

9 files changed

+141
-10
lines changed

9 files changed

+141
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- Syntax highlighting for `*.swift.gyb` files ([#1515](https://github.com/swiftlang/vscode-swift/pull/1515))
8+
- Activate the extension if a workspace folder contains a `.bsp` folder ([#1865](https://github.com/swiftlang/vscode-swift/pull/1865))
89

910
## 2.12.0 - 2025-10-29
1011

package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"workspaceContains:**/compile_commands.json",
3030
"workspaceContains:**/compile_flags.txt",
3131
"workspaceContains:**/buildServer.json",
32+
"workspaceContains:**/.bsp/*.json",
3233
"onDebugResolve:swift-lldb",
3334
"onDebugResolve:swift"
3435
],
@@ -546,6 +547,22 @@
546547
"markdownDescription": "Search sub-folders of workspace folder for Swift Packages at start up.",
547548
"scope": "machine-overridable"
548549
},
550+
"swift.ignoreSearchingForPackagesInSubfolders": {
551+
"type": "array",
552+
"items": {
553+
"type": "string"
554+
},
555+
"default": [
556+
".",
557+
".build",
558+
"Packages",
559+
"out",
560+
"bazel-out",
561+
"bazel-bin"
562+
],
563+
"markdownDescription": "A list of folders to ignore when searching sub-folders for Swift Packages. The `swift.searchSubfoldersForPackages` must be `true` for this setting to have an effect. Always use forward-slashes in glob expressions regardless of platform. This is combined with VS Code's `files.exclude` setting.",
564+
"scope": "machine-overridable"
565+
},
549566
"swift.autoGenerateLaunchConfigurations": {
550567
"type": "boolean",
551568
"default": true,

src/WorkspaceContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ export class WorkspaceContext implements vscode.Disposable {
448448
workspaceFolder.uri,
449449
configuration.disableSwiftPMIntegration,
450450
configuration.folder(workspaceFolder).searchSubfoldersForPackages,
451+
configuration.folder(workspaceFolder).ignoreSearchingForPackagesInSubfolders,
451452
this.globalToolchainSwiftVersion
452453
);
453454

src/configuration.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ export interface FolderConfiguration {
7878
readonly additionalTestArguments: string[];
7979
/** search sub-folder of workspace folder for Swift Packages */
8080
readonly searchSubfoldersForPackages: boolean;
81+
/** Folders to ignore when searching for Swift Packages */
82+
readonly ignoreSearchingForPackagesInSubfolders: string[];
8183
/** auto-generate launch.json configurations */
8284
readonly autoGenerateLaunchConfigurations: boolean;
8385
/** disable automatic running of swift package resolve */
@@ -232,6 +234,15 @@ const configuration = {
232234
.getConfiguration("swift", workspaceFolder)
233235
.get<boolean>("searchSubfoldersForPackages", false);
234236
},
237+
/** Folders to ignore when searching for Swift Packages */
238+
get ignoreSearchingForPackagesInSubfolders(): string[] {
239+
return vscode.workspace
240+
.getConfiguration("swift", workspaceFolder)
241+
.get<
242+
string[]
243+
>("ignoreSearchingForPackagesInSubfolders", [".", ".build", "Packages", "out", "bazel-out", "bazel-bin"])
244+
.map(substituteVariablesInString);
245+
},
235246
get attachmentsPath(): string {
236247
return substituteVariablesInString(
237248
vscode.workspace

src/utilities/filesystem.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,19 @@ export async function touch(path: string): Promise<void> {
6060
}
6161
}
6262

63+
/**
64+
* Checks if a folder exists at the supplied path.
65+
* @param pathComponents The folder path to check for existence
66+
* @returns Whether or not the folder exists at the path
67+
*/
68+
export async function folderExists(...pathComponents: string[]): Promise<boolean> {
69+
try {
70+
return (await fs.stat(path.join(...pathComponents))).isDirectory();
71+
} catch (e) {
72+
return false;
73+
}
74+
}
75+
6376
/**
6477
* Return whether a file/folder is inside a folder.
6578
* @param subpath child file/folder

src/utilities/workspace.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,34 @@ import * as path from "path";
1616
import { basename } from "path";
1717
import * as vscode from "vscode";
1818

19-
import { globDirectory, pathExists } from "./filesystem";
19+
import { folderExists, globDirectory, pathExists } from "./filesystem";
2020
import { Version } from "./version";
2121

2222
export async function searchForPackages(
2323
folder: vscode.Uri,
2424
disableSwiftPMIntegration: boolean,
2525
searchSubfoldersForPackages: boolean,
26+
skipFolders: Array<string>,
2627
swiftVersion: Version
2728
): Promise<Array<vscode.Uri>> {
2829
const folders: Array<vscode.Uri> = [];
2930

3031
async function search(folder: vscode.Uri) {
31-
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists
32+
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json/.bsp exists
3233
if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration, swiftVersion)) {
3334
folders.push(folder);
3435
}
35-
// should I search sub-folders for more Swift Packages
36+
37+
// If sub-folder searches are disabled, don't search subdirectories
3638
if (!searchSubfoldersForPackages) {
3739
return;
3840
}
3941

4042
await globDirectory(folder, { onlyDirectories: true }).then(async entries => {
43+
const skip = new Set<string>(skipFolders);
4144
for (const entry of entries) {
42-
if (basename(entry) !== "." && basename(entry) !== "Packages") {
45+
const base = basename(entry);
46+
if (!skip.has(base)) {
4347
await search(vscode.Uri.file(entry));
4448
}
4549
}
@@ -67,7 +71,7 @@ export async function hasBSPConfigurationFile(
6771
const bspStat = await fs.stat(bspDir).catch(() => undefined);
6872
if (bspStat && bspStat.isDirectory()) {
6973
const files = await fs.readdir(bspDir).catch(() => []);
70-
if (files.some((f: string) => f.endsWith(".json"))) {
74+
if (files.some(f => f.endsWith(".json"))) {
7175
return true;
7276
}
7377
}
@@ -94,11 +98,11 @@ export async function isValidWorkspaceFolder(
9498
return true;
9599
}
96100

97-
if (await pathExists(folder, "build")) {
101+
if (await folderExists(folder, "build")) {
98102
return true;
99103
}
100104

101-
if (await pathExists(folder, "out")) {
105+
if (await folderExists(folder, "out")) {
102106
return true;
103107
}
104108

test/integration-tests/utilities/workspace.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ suite("Workspace Utilities Test Suite", () => {
2626
(vscode.workspace.workspaceFolders ?? [])[0]!.uri,
2727
false,
2828
true,
29+
[],
2930
testSwiftVersion
3031
);
3132

test/unit-tests/debugger/buildConfig.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ suite("BuildConfig Test Suite", () => {
3838
testEnvironmentVariables: {},
3939
additionalTestArguments,
4040
searchSubfoldersForPackages: false,
41+
ignoreSearchingForPackagesInSubfolders: [],
4142
autoGenerateLaunchConfigurations: false,
4243
disableAutoResolve: false,
4344
attachmentsPath: "",

test/unit-tests/utilities/workspace.test.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,95 @@ suite("Workspace Utilities Unit Test Suite", () => {
2727
const testSwiftVersion = new Version(5, 9, 0);
2828

2929
test("returns only root package when search for subpackages disabled", async () => {
30-
const folders = await searchForPackages(packageFolder, false, false, testSwiftVersion);
30+
const folders = await searchForPackages(
31+
packageFolder,
32+
false,
33+
false,
34+
[],
35+
testSwiftVersion
36+
);
3137

32-
expect(folders.map(folder => folder.fsPath)).eql([packageFolder.fsPath]);
38+
expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]);
3339
});
3440

3541
test("returns subpackages when search for subpackages enabled", async () => {
36-
const folders = await searchForPackages(packageFolder, false, true, testSwiftVersion);
42+
const folders = await searchForPackages(
43+
packageFolder,
44+
false,
45+
true,
46+
[],
47+
testSwiftVersion
48+
);
49+
50+
expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
51+
packageFolder.fsPath,
52+
firstModuleFolder.fsPath,
53+
secondModuleFolder.fsPath,
54+
]);
55+
});
56+
57+
test("skips specified folders when skipFolders contains Module1", async () => {
58+
const folders = await searchForPackages(
59+
packageFolder,
60+
false,
61+
true,
62+
["Module1"],
63+
testSwiftVersion
64+
);
65+
66+
expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
67+
packageFolder.fsPath,
68+
secondModuleFolder.fsPath,
69+
]);
70+
});
71+
72+
test("skips specified folders when skipFolders contains Module2", async () => {
73+
const folders = await searchForPackages(
74+
packageFolder,
75+
false,
76+
true,
77+
["Module2"],
78+
testSwiftVersion
79+
);
80+
81+
expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
82+
packageFolder.fsPath,
83+
firstModuleFolder.fsPath,
84+
]);
85+
});
86+
87+
test("skips multiple folders when skipFolders contains both modules", async () => {
88+
const folders = await searchForPackages(
89+
packageFolder,
90+
false,
91+
true,
92+
["Module1", "Module2"],
93+
testSwiftVersion
94+
);
95+
96+
expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]);
97+
});
98+
99+
test("skipFolders has no effect when search for subpackages is disabled", async () => {
100+
const folders = await searchForPackages(
101+
packageFolder,
102+
false,
103+
false,
104+
["Module1", "Module2"],
105+
testSwiftVersion
106+
);
107+
108+
expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]);
109+
});
110+
111+
test("skipFolders with non-existent folder names does not affect results", async () => {
112+
const folders = await searchForPackages(
113+
packageFolder,
114+
false,
115+
true,
116+
["NonExistentModule", "AnotherFakeModule"],
117+
testSwiftVersion
118+
);
37119

38120
expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
39121
packageFolder.fsPath,

0 commit comments

Comments
 (0)