diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift index 6235e895f..7108a83b5 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift @@ -89,6 +89,19 @@ fileprivate extension ConfiguredTarget { fileprivate let preparationTaskID: AtomicUInt32 = AtomicUInt32(initialValue: 0) +public struct SwiftPMTestHooks: Sendable { + public var reloadPackageDidStart: (@Sendable () async -> Void)? + public var reloadPackageDidFinish: (@Sendable () async -> Void)? + + public init( + reloadPackageDidStart: (@Sendable () async -> Void)? = nil, + reloadPackageDidFinish: (@Sendable () async -> Void)? = nil + ) { + self.reloadPackageDidStart = reloadPackageDidStart + self.reloadPackageDidFinish = reloadPackageDidFinish + } +} + /// Swift Package Manager build system and workspace support. /// /// This class implements the `BuildSystem` interface to provide the build settings for a Swift @@ -124,6 +137,7 @@ public actor SwiftPMBuildSystem { @_spi(Testing) public var projectRoot: TSCAbsolutePath + private var buildDescription: SourceKitLSPAPI.BuildDescription? private var modulesGraph: ModulesGraph private let workspace: Workspace @_spi(Testing) public let toolsBuildParameters: BuildParameters @@ -168,6 +182,8 @@ public actor SwiftPMBuildSystem { /// user's build. private var isForIndexBuild: Bool { experimentalFeatures.contains(.backgroundIndexing) } + private let testHooks: SwiftPMTestHooks + /// Creates a build system using the Swift Package Manager, if this workspace is a package. /// /// - Parameters: @@ -182,7 +198,8 @@ public actor SwiftPMBuildSystem { fileSystem: FileSystem = localFileSystem, buildSetup: BuildSetup, experimentalFeatures: Set, - reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void = { _ in } + reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void = { _ in }, + testHooks: SwiftPMTestHooks ) async throws { self.workspacePath = workspacePath self.buildSetup = buildSetup @@ -193,6 +210,7 @@ public actor SwiftPMBuildSystem { self.toolchain = toolchain self.experimentalFeatures = experimentalFeatures + self.testHooks = testHooks guard let packageRoot = findPackageDirectory(containing: workspacePath, fileSystem) else { throw Error.noManifest(workspacePath: workspacePath) @@ -287,7 +305,8 @@ public actor SwiftPMBuildSystem { toolchainRegistry: ToolchainRegistry, buildSetup: BuildSetup, experimentalFeatures: Set, - reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void + reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void, + testHooks: SwiftPMTestHooks ) async { guard let fileURL = uri.fileURL else { return nil @@ -299,7 +318,8 @@ public actor SwiftPMBuildSystem { fileSystem: localFileSystem, buildSetup: buildSetup, experimentalFeatures: experimentalFeatures, - reloadPackageStatusCallback: reloadPackageStatusCallback + reloadPackageStatusCallback: reloadPackageStatusCallback, + testHooks: testHooks ) } catch Error.noManifest { return nil @@ -315,8 +335,10 @@ extension SwiftPMBuildSystem { /// dependencies. public func reloadPackage(forceResolvedVersions: Bool) async throws { await reloadPackageStatusCallback(.start) + await testHooks.reloadPackageDidStart?() defer { Task { + await testHooks.reloadPackageDidFinish?() await reloadPackageStatusCallback(.end) } } @@ -336,6 +358,7 @@ extension SwiftPMBuildSystem { observabilityScope: observabilitySystem.topScope ) let buildDescription = BuildDescription(buildPlan: plan) + self.buildDescription = buildDescription /// Make sure to execute any throwing statements before setting any /// properties because otherwise we might end up in an inconsistent state @@ -683,14 +706,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { } switch event.type { case .created, .deleted: - guard let path = try? AbsolutePath(validating: fileURL.path) else { + guard let buildDescription else { return false } - return self.workspace.fileAffectsSwiftOrClangBuildSettings( - filePath: path, - packageGraph: self.modulesGraph - ) + return buildDescription.fileAffectsSwiftOrClangBuildSettings(fileURL) case .changed: return fileURL.lastPathComponent == "Package.swift" || fileURL.lastPathComponent == "Package.resolved" default: // Unknown file change type diff --git a/Sources/SemanticIndex/CMakeLists.txt b/Sources/SemanticIndex/CMakeLists.txt index 83ac62fb4..b105a6a0e 100644 --- a/Sources/SemanticIndex/CMakeLists.txt +++ b/Sources/SemanticIndex/CMakeLists.txt @@ -3,9 +3,9 @@ add_library(SemanticIndex STATIC CheckedIndex.swift CompilerCommandLineOption.swift IndexTaskDescription.swift + IndexTestHooks.swift PreparationTaskDescription.swift SemanticIndexManager.swift - TestHooks.swift UpdateIndexStoreTaskDescription.swift UpToDateTracker.swift ) diff --git a/Sources/SemanticIndex/TestHooks.swift b/Sources/SemanticIndex/IndexTestHooks.swift similarity index 100% rename from Sources/SemanticIndex/TestHooks.swift rename to Sources/SemanticIndex/IndexTestHooks.swift diff --git a/Sources/SourceKitLSP/CreateBuildSystem.swift b/Sources/SourceKitLSP/CreateBuildSystem.swift index 09e514d42..a9b6d8cb4 100644 --- a/Sources/SourceKitLSP/CreateBuildSystem.swift +++ b/Sources/SourceKitLSP/CreateBuildSystem.swift @@ -39,7 +39,8 @@ func createBuildSystem( toolchainRegistry: toolchainRegistry, buildSetup: options.buildSetup, experimentalFeatures: options.experimentalFeatures, - reloadPackageStatusCallback: reloadPackageStatusCallback + reloadPackageStatusCallback: reloadPackageStatusCallback, + testHooks: options.swiftpmTestHooks ) } diff --git a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift index 01e5cc8c1..dd53a37f4 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift @@ -14,6 +14,7 @@ import Foundation import LanguageServerProtocol import SKCore import SKSupport +import SKSwiftPMWorkspace import SemanticIndex import struct TSCBasic.AbsolutePath @@ -58,6 +59,8 @@ extension SourceKitLSPServer { public var indexTestHooks: IndexTestHooks + public var swiftpmTestHooks: SwiftPMTestHooks + public init( buildSetup: BuildSetup = .default, clangdOptions: [String] = [], @@ -68,7 +71,8 @@ extension SourceKitLSPServer { swiftPublishDiagnosticsDebounceDuration: TimeInterval = 2, /* 2s */ workDoneProgressDebounceDuration: Duration = .seconds(0), experimentalFeatures: Set = [], - indexTestHooks: IndexTestHooks = IndexTestHooks() + indexTestHooks: IndexTestHooks = IndexTestHooks(), + swiftpmTestHooks: SwiftPMTestHooks = SwiftPMTestHooks() ) { self.buildSetup = buildSetup self.clangdOptions = clangdOptions @@ -80,6 +84,7 @@ extension SourceKitLSPServer { self.experimentalFeatures = experimentalFeatures self.workDoneProgressDebounceDuration = workDoneProgressDebounceDuration self.indexTestHooks = indexTestHooks + self.swiftpmTestHooks = swiftpmTestHooks } } } diff --git a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift index f0b8b0cb7..1f6ced5a6 100644 --- a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift +++ b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift @@ -54,7 +54,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) ) } @@ -81,7 +82,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) await assertThrowsError(try await buildSystem.generateBuildGraph(allowFileSystemWrites: false)) } @@ -111,7 +113,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: ToolchainRegistry(toolchains: []), fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) ) } @@ -142,7 +145,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -207,7 +211,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: localFileSystem, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -270,7 +275,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: config, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -312,7 +318,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -349,7 +356,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -398,7 +406,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -463,7 +472,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -507,7 +517,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -588,7 +599,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: ToolchainRegistry.forTesting, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -640,7 +652,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -708,7 +721,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: ToolchainRegistry.forTesting, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -748,7 +762,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) @@ -789,7 +804,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) assertEqual(await swiftpmBuildSystem.projectRoot, try resolveSymlinks(tempDir.appending(component: "pkg"))) @@ -824,7 +840,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { toolchainRegistry: tr, fileSystem: fs, buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - experimentalFeatures: [] + experimentalFeatures: [], + testHooks: SwiftPMTestHooks() ) try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 255f60dd6..58c06073d 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -13,6 +13,7 @@ import LSPTestSupport import LanguageServerProtocol @_spi(Testing) import SKCore +import SKSupport import SKTestSupport import SemanticIndex import SourceKitLSP @@ -1210,6 +1211,29 @@ final class BackgroundIndexingTests: XCTestCase { return hoverAfterPackageUpdate?.contents.markupContent?.value.contains("Do something v1.1.0") ?? false } } + + func testAddingRandomSwiftFileDoesNotTriggerPackageReload() async throws { + let packageInitialized = AtomicBool(initialValue: false) + + var serverOptions = SourceKitLSPServer.Options.testDefault + serverOptions.swiftpmTestHooks.reloadPackageDidStart = { + if packageInitialized.value { + XCTFail("Build graph should not get reloaded when random file gets added") + } + } + let project = try await SwiftPMTestProject( + files: ["Test.swift": ""], + serverOptions: serverOptions, + enableBackgroundIndexing: true + ) + packageInitialized.value = true + project.testClient.send( + DidChangeWatchedFilesNotification(changes: [ + FileEvent(uri: DocumentURI(project.scratchDirectory.appendingPathComponent("random.swift")), type: .created) + ]) + ) + _ = try await project.testClient.send(PollIndexRequest()) + } } extension HoverResponseContents {