Skip to content

Fix Search Subfolders For Packages Not Working in Swift Package Workspaces #1425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 4 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
22 changes: 22 additions & 0 deletions assets/test/ModularPackage/Module1/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"configurations": [
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:Module1}",
"name": "Debug Module1Demo",
"program": "${workspaceFolder:Module1}/.build/debug/Module1Demo",
"preLaunchTask": "swift: Build Debug Module1Demo"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:Module1}",
"name": "Release Module1Demo",
"program": "${workspaceFolder:Module1}/.build/release/Module1Demo",
"preLaunchTask": "swift: Build Release Module1Demo"
}
]
}
23 changes: 23 additions & 0 deletions assets/test/ModularPackage/Module1/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// swift-tools-version:6.0

import PackageDescription

internal let package = Package(
name: "Module1",
products: [
.executable(name: "Module1Demo", targets: ["Module1Demo"]),
],
targets: [
.testTarget(
name: "Module1Tests",
dependencies: ["Module1"]
),
.executableTarget(
name: "Module1Demo",
dependencies: ["Module1"]
),
.target(
name: "Module1"
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public struct Module1 {
public init() {}

public func add(_ x: Int, _ y: Int) -> Int {
x + y
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Module1

private let module = Module1()

@MainActor
func check(_ x: Int, _ y: Int) {
print(module.add(x, y))
}

check(1, 2)
check(2, 3)
20 changes: 20 additions & 0 deletions assets/test/ModularPackage/Module1/Tests/Module1Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import XCTest
@testable import Module1

internal final class Module1Tests: XCTestCase {
private var sut: Module1!

override internal func setUp() {
super.setUp()
sut = .init()
}

override internal func tearDown() {
sut = nil
super.tearDown()
}

internal func test_add_with1And2_shouldReturn3() {
XCTAssertEqual(sut.add(1, 2), 3)
}
}
40 changes: 40 additions & 0 deletions assets/test/ModularPackage/Module2/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"configurations": [
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:Module1}",
"name": "Debug Module1Demo",
"program": "${workspaceFolder:Module1}/.build/debug/Module1Demo",
"preLaunchTask": "swift: Build Debug Module1Demo"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:Module1}",
"name": "Release Module1Demo",
"program": "${workspaceFolder:Module1}/.build/release/Module1Demo",
"preLaunchTask": "swift: Build Release Module1Demo"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:Module2}",
"name": "Debug Module2Demo",
"program": "${workspaceFolder:Module2}/.build/debug/Module2Demo",
"preLaunchTask": "swift: Build Debug Module2Demo"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:Module2}",
"name": "Release Module2Demo",
"program": "${workspaceFolder:Module2}/.build/release/Module2Demo",
"preLaunchTask": "swift: Build Release Module2Demo"
}
]
}
23 changes: 23 additions & 0 deletions assets/test/ModularPackage/Module2/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// swift-tools-version:6.0

import PackageDescription

internal let package = Package(
name: "Module2",
products: [
.executable(name: "Module2Demo", targets: ["Module2Demo"]),
],
targets: [
.testTarget(
name: "Module2Tests",
dependencies: ["Module2"]
),
.executableTarget(
name: "Module2Demo",
dependencies: ["Module2"]
),
.target(
name: "Module2"
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public struct Module2 {
public init() {}

public func subtract(_ x: Int, _ y: Int) -> Int {
x - y
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Module2

private let module = Module2()

@MainActor
func check(_ x: Int, _ y: Int) {
print(module.subtract(x, y))
}

check(1, 2)
check(2, 3)
20 changes: 20 additions & 0 deletions assets/test/ModularPackage/Module2/Tests/Module2Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import XCTest
@testable import Module2

internal final class Module2Tests: XCTestCase {
private var sut: Module2!

override internal func setUp() {
super.setUp()
sut = .init()
}

override internal func tearDown() {
sut = nil
super.tearDown()
}

internal func test_add_with5And2_shouldReturn3() {
XCTAssertEqual(sut.subtract(5, 2), 3)
}
}
11 changes: 11 additions & 0 deletions assets/test/ModularPackage/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// swift-tools-version:6.0

import PackageDescription

internal let package = Package(
name: "ModularPackage",
dependencies: [
.package(path: "Module1"),
.package(path: "Module2"),
]
)
48 changes: 12 additions & 36 deletions src/WorkspaceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { FolderContext } from "./FolderContext";
import { StatusItem } from "./ui/StatusItem";
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
import { swiftLibraryPathKey } from "./utilities/utilities";
import { pathExists, isPathInsidePath } from "./utilities/filesystem";
import { isPathInsidePath } from "./utilities/filesystem";
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
import { TemporaryFolder } from "./utilities/tempFolder";
import { TaskManager } from "./tasks/TaskManager";
Expand All @@ -34,6 +34,7 @@ import { DiagnosticsManager } from "./DiagnosticsManager";
import { DocumentationManager } from "./documentation/DocumentationManager";
import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions";
import { TestKind } from "./TestExplorer/TestKind";
import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace";

/**
* Context for whole workspace. Holds array of contexts for each workspace folder
Expand Down Expand Up @@ -391,38 +392,19 @@ export class WorkspaceContext implements vscode.Disposable {
* @param folder folder being added
*/
async addWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder) {
await this.searchForPackages(workspaceFolder.uri, workspaceFolder);

if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) {
await this.focusTextEditor(vscode.window.activeTextEditor);
}
}
const folders = await searchForPackages(
workspaceFolder.uri,
configuration.disableSwiftPMIntegration,
configuration.folder(workspaceFolder).searchSubfoldersForPackages
);

async searchForPackages(folder: vscode.Uri, workspaceFolder: vscode.WorkspaceFolder) {
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists
if (await this.isValidWorkspaceFolder(folder.fsPath)) {
for (const folder of folders) {
await this.addPackageFolder(folder, workspaceFolder);
return;
}
// should I search sub-folders for more Swift Packages
if (!configuration.folder(workspaceFolder).searchSubfoldersForPackages) {
return;
}

await vscode.workspace.fs.readDirectory(folder).then(async entries => {
for (const entry of entries) {
if (
entry[1] === vscode.FileType.Directory &&
entry[0][0] !== "." &&
entry[0] !== "Packages"
) {
await this.searchForPackages(
vscode.Uri.joinPath(folder, entry[0]),
workspaceFolder
);
}
}
});
if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) {
await this.focusTextEditor(vscode.window.activeTextEditor);
}
}

public async addPackageFolder(
Expand Down Expand Up @@ -597,13 +579,7 @@ export class WorkspaceContext implements vscode.Disposable {
* Package.swift or a CMake compile_commands.json, compile_flags.txt, or a BSP buildServer.json.
*/
async isValidWorkspaceFolder(folder: string): Promise<boolean> {
return (
((await pathExists(folder, "Package.swift")) &&
!configuration.disableSwiftPMIntegration) ||
(await pathExists(folder, "compile_commands.json")) ||
(await pathExists(folder, "compile_flags.txt")) ||
(await pathExists(folder, "buildServer.json"))
);
return await isValidWorkspaceFolder(folder, configuration.disableSwiftPMIntegration);
}

/** send unfocus event to current focussed folder and clear current folder */
Expand Down
63 changes: 63 additions & 0 deletions src/utilities/workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2022 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { pathExists } from "./filesystem";

export async function searchForPackages(
folder: vscode.Uri,
disableSwiftPMIntegration: boolean,
searchSubfoldersForPackages: boolean
): 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
if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration)) {
folders.push(folder);
}
// should I search sub-folders for more Swift Packages
if (!searchSubfoldersForPackages) {
return;
}

await vscode.workspace.fs.readDirectory(folder).then(async entries => {
for (const entry of entries) {
if (
entry[1] === vscode.FileType.Directory &&
entry[0][0] !== "." &&
entry[0] !== "Packages"
) {
await search(vscode.Uri.joinPath(folder, entry[0]));
}
}
});
}

await search(folder);

return folders;
}

export async function isValidWorkspaceFolder(
folder: string,
disableSwiftPMIntegration: boolean
): Promise<boolean> {
return (
(!disableSwiftPMIntegration && (await pathExists(folder, "Package.swift"))) ||
(await pathExists(folder, "compile_commands.json")) ||
(await pathExists(folder, "compile_flags.txt")) ||
(await pathExists(folder, "buildServer.json"))
);
}
42 changes: 42 additions & 0 deletions test/unit-tests/utilities/workspace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2024 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { searchForPackages } from "../../../src/utilities/workspace";
import { testAssetUri } from "../../fixtures";
import { expect } from "chai";

suite("Workspace Utilities Unit Test Suite", () => {
suite("searchForPackages", () => {
const packageFolder = testAssetUri("ModularPackage");
const firstModuleFolder = vscode.Uri.joinPath(packageFolder, "Module1");
const secondModuleFolder = vscode.Uri.joinPath(packageFolder, "Module2");

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

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

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

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