Skip to content
Open
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
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"workspaceContains:**/compile_commands.json",
"workspaceContains:**/compile_flags.txt",
"workspaceContains:**/buildServer.json",
"workspaceContains:**/.bsp/*.json",
"onDebugResolve:swift-lldb",
"onDebugResolve:swift"
],
Expand Down Expand Up @@ -544,6 +545,22 @@
"markdownDescription": "Search sub-folders of workspace folder for Swift Packages at start up.",
"scope": "machine-overridable"
},
"swift.ignoreSearchingForPackagesInSubfolders": {
"type": "array",
"items": {
"type": "string"
},
"default": [
".",
".build",
"Packages",
"out",
"bazel-out",
"bazel-bin"
],
"markdownDescription": "A list of glob patterns 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 default `files.exclude` setting.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually a glob pattern though? Seems like we just look at the basename of the folder and then skip if it matches something in this list?

Also wondering if the "default" could be removed from "This is combined with VS Code's default files.exclude setting.", and just say "This is combined with VS Code's files.exclude setting." to make it clearer?

"scope": "machine-overridable"
},
"swift.autoGenerateLaunchConfigurations": {
"type": "boolean",
"default": true,
Expand Down
1 change: 1 addition & 0 deletions src/WorkspaceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ export class WorkspaceContext implements vscode.Disposable {
workspaceFolder.uri,
configuration.disableSwiftPMIntegration,
configuration.folder(workspaceFolder).searchSubfoldersForPackages,
configuration.folder(workspaceFolder).ignoreSearchingForPackagesInSubfolders,
this.globalToolchainSwiftVersion
);

Expand Down
11 changes: 11 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export interface FolderConfiguration {
readonly additionalTestArguments: string[];
/** search sub-folder of workspace folder for Swift Packages */
readonly searchSubfoldersForPackages: boolean;
/** Folders to ignore when searching for Swift Packages */
readonly ignoreSearchingForPackagesInSubfolders: string[];
/** auto-generate launch.json configurations */
readonly autoGenerateLaunchConfigurations: boolean;
/** disable automatic running of swift package resolve */
Expand Down Expand Up @@ -232,6 +234,15 @@ const configuration = {
.getConfiguration("swift", workspaceFolder)
.get<boolean>("searchSubfoldersForPackages", false);
},
/** Folders to ignore when searching for Swift Packages */
get ignoreSearchingForPackagesInSubfolders(): string[] {
return vscode.workspace
.getConfiguration("swift", workspaceFolder)
.get<
string[]
>("ignoreSearchingForPackagesInSubfolders", [".", ".build", "Packages", "out", "bazel-out", "bazel-bin"])
.map(substituteVariablesInString);
},
get attachmentsPath(): string {
return substituteVariablesInString(
vscode.workspace
Expand Down
13 changes: 13 additions & 0 deletions src/utilities/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ export async function touch(path: string): Promise<void> {
}
}

/**
* Checks if a folder exists at the supplied path.
* @param pathComponents The folder path to check for existence
* @returns Whether or not the folder exists at the path
*/
export async function folderExists(...pathComponents: string[]): Promise<boolean> {
try {
return (await fs.stat(path.join(...pathComponents))).isDirectory();
} catch (e) {
return false;
}
}

/**
* Return whether a file/folder is inside a folder.
* @param subpath child file/folder
Expand Down
18 changes: 11 additions & 7 deletions src/utilities/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,34 @@ import * as path from "path";
import { basename } from "path";
import * as vscode from "vscode";

import { globDirectory, pathExists } from "./filesystem";
import { folderExists, globDirectory, pathExists } from "./filesystem";
import { Version } from "./version";

export async function searchForPackages(
folder: vscode.Uri,
disableSwiftPMIntegration: boolean,
searchSubfoldersForPackages: boolean,
skipFolders: Array<string>,
swiftVersion: Version
): Promise<Array<vscode.Uri>> {
const folders: Array<vscode.Uri> = [];

async function search(folder: vscode.Uri) {
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json/.bsp exists
if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration, swiftVersion)) {
folders.push(folder);
}
// should I search sub-folders for more Swift Packages

// If sub-folder searches are disabled, don't search subdirectories
if (!searchSubfoldersForPackages) {
return;
}

await globDirectory(folder, { onlyDirectories: true }).then(async entries => {
const skip = new Set<string>(skipFolders);
for (const entry of entries) {
if (basename(entry) !== "." && basename(entry) !== "Packages") {
const base = basename(entry);
if (!skip.has(base)) {
await search(vscode.Uri.file(entry));
}
}
Expand Down Expand Up @@ -67,7 +71,7 @@ export async function hasBSPConfigurationFile(
const bspStat = await fs.stat(bspDir).catch(() => undefined);
if (bspStat && bspStat.isDirectory()) {
const files = await fs.readdir(bspDir).catch(() => []);
if (files.some((f: string) => f.endsWith(".json"))) {
if (files.some(f => f.endsWith(".json"))) {
return true;
}
}
Expand All @@ -94,11 +98,11 @@ export async function isValidWorkspaceFolder(
return true;
}

if (await pathExists(folder, "build")) {
if (await folderExists(folder, "build")) {
return true;
}

if (await pathExists(folder, "out")) {
if (await folderExists(folder, "out")) {
return true;
}

Expand Down
1 change: 1 addition & 0 deletions test/integration-tests/utilities/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ suite("Workspace Utilities Test Suite", () => {
(vscode.workspace.workspaceFolders ?? [])[0]!.uri,
false,
true,
[],
testSwiftVersion
);

Expand Down
1 change: 1 addition & 0 deletions test/unit-tests/debugger/buildConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ suite("BuildConfig Test Suite", () => {
testEnvironmentVariables: {},
additionalTestArguments,
searchSubfoldersForPackages: false,
ignoreSearchingForPackagesInSubfolders: [],
autoGenerateLaunchConfigurations: false,
disableAutoResolve: false,
attachmentsPath: "",
Expand Down
88 changes: 85 additions & 3 deletions test/unit-tests/utilities/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,95 @@ suite("Workspace Utilities Unit Test Suite", () => {
const testSwiftVersion = new Version(5, 9, 0);

test("returns only root package when search for subpackages disabled", async () => {
const folders = await searchForPackages(packageFolder, false, false, testSwiftVersion);
const folders = await searchForPackages(
packageFolder,
false,
false,
[],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath)).eql([packageFolder.fsPath]);
expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]);
});

test("returns subpackages when search for subpackages enabled", async () => {
const folders = await searchForPackages(packageFolder, false, true, testSwiftVersion);
const folders = await searchForPackages(
packageFolder,
false,
true,
[],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
packageFolder.fsPath,
firstModuleFolder.fsPath,
secondModuleFolder.fsPath,
]);
});

test("skips specified folders when skipFolders contains Module1", async () => {
const folders = await searchForPackages(
packageFolder,
false,
true,
["Module1"],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
packageFolder.fsPath,
secondModuleFolder.fsPath,
]);
});

test("skips specified folders when skipFolders contains Module2", async () => {
const folders = await searchForPackages(
packageFolder,
false,
true,
["Module2"],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
packageFolder.fsPath,
firstModuleFolder.fsPath,
]);
});

test("skips multiple folders when skipFolders contains both modules", async () => {
const folders = await searchForPackages(
packageFolder,
false,
true,
["Module1", "Module2"],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]);
});

test("skipFolders has no effect when search for subpackages is disabled", async () => {
const folders = await searchForPackages(
packageFolder,
false,
false,
["Module1", "Module2"],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]);
});

test("skipFolders with non-existent folder names does not affect results", async () => {
const folders = await searchForPackages(
packageFolder,
false,
true,
["NonExistentModule", "AnotherFakeModule"],
testSwiftVersion
);

expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
packageFolder.fsPath,
Expand Down
Loading