diff --git a/Sources/SKCore/BuildServerBuildSystem.swift b/Sources/SKCore/BuildServerBuildSystem.swift index e2da82695..2ad9af434 100644 --- a/Sources/SKCore/BuildServerBuildSystem.swift +++ b/Sources/SKCore/BuildServerBuildSystem.swift @@ -279,7 +279,7 @@ extension BuildServerBuildSystem: BuildSystem { return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")] } - public func generateBuildGraph() {} + public func generateBuildGraph(allowFileSystemWrites: Bool) {} public func topologicalSort(of targets: [ConfiguredTarget]) async -> [ConfiguredTarget]? { return nil diff --git a/Sources/SKCore/BuildSystem.swift b/Sources/SKCore/BuildSystem.swift index bc4848e82..718a03bc7 100644 --- a/Sources/SKCore/BuildSystem.swift +++ b/Sources/SKCore/BuildSystem.swift @@ -135,9 +135,14 @@ public protocol BuildSystem: AnyObject, Sendable { /// Return the list of targets and run destinations that the given document can be built for. func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] - /// Re-generate the build graph including all the tasks that are necessary for building the entire build graph, like - /// resolving package versions. - func generateBuildGraph() async throws + /// Re-generate the build graph. + /// + /// If `allowFileSystemWrites` is `true`, this should include all the tasks that are necessary for building the entire + /// build graph, like resolving package versions. + /// + /// If `allowFileSystemWrites` is `false`, no files must be written to disk. This mode is used to determine whether + /// the build system can handle a source file, and decide whether a workspace should be opened with this build system + func generateBuildGraph(allowFileSystemWrites: Bool) async throws /// Sort the targets so that low-level targets occur before high-level targets. /// diff --git a/Sources/SKCore/BuildSystemManager.swift b/Sources/SKCore/BuildSystemManager.swift index acba3e7f0..6b3b8be91 100644 --- a/Sources/SKCore/BuildSystemManager.swift +++ b/Sources/SKCore/BuildSystemManager.swift @@ -219,8 +219,8 @@ extension BuildSystemManager { return settings } - public func generateBuildGraph() async throws { - try await self.buildSystem?.generateBuildGraph() + public func generateBuildGraph(allowFileSystemWrites: Bool) async throws { + try await self.buildSystem?.generateBuildGraph(allowFileSystemWrites: allowFileSystemWrites) } public func topologicalSort(of targets: [ConfiguredTarget]) async throws -> [ConfiguredTarget]? { diff --git a/Sources/SKCore/CompilationDatabaseBuildSystem.swift b/Sources/SKCore/CompilationDatabaseBuildSystem.swift index ac5825f63..3d84a6beb 100644 --- a/Sources/SKCore/CompilationDatabaseBuildSystem.swift +++ b/Sources/SKCore/CompilationDatabaseBuildSystem.swift @@ -132,7 +132,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem { throw PrepareNotSupportedError() } - public func generateBuildGraph() {} + public func generateBuildGraph(allowFileSystemWrites: Bool) {} public func topologicalSort(of targets: [ConfiguredTarget]) -> [ConfiguredTarget]? { return nil diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift index e54cbfe4d..a23dd40aa 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift @@ -178,7 +178,9 @@ public actor SwiftPMBuildSystem { forRootPackage: AbsolutePath(packageRoot), fileSystem: fileSystem ) - if let scratchDirectory = buildSetup.path { + if isForIndexBuild { + location.scratchDirectory = AbsolutePath(packageRoot.appending(component: ".index-build")) + } else if let scratchDirectory = buildSetup.path { location.scratchDirectory = AbsolutePath(scratchDirectory) } @@ -227,7 +229,6 @@ public actor SwiftPMBuildSystem { } await delegate.filesDependenciesUpdated(filesWithUpdatedDependencies) } - try await reloadPackage() } /// Creates a build system using the Swift Package Manager, if this workspace is a package. @@ -261,13 +262,9 @@ public actor SwiftPMBuildSystem { } extension SwiftPMBuildSystem { - public func generateBuildGraph() async throws { - try await self.reloadPackage() - } - /// (Re-)load the package settings by parsing the manifest and resolving all the targets and /// dependencies. - func reloadPackage() async throws { + public func reloadPackage(forceResolvedVersions: Bool) async throws { await reloadPackageStatusCallback(.start) defer { Task { @@ -277,7 +274,7 @@ extension SwiftPMBuildSystem { let modulesGraph = try self.workspace.loadPackageGraph( rootInput: PackageGraphRootInput(packages: [AbsolutePath(projectRoot)]), - forceResolvedVersions: !isForIndexBuild, + forceResolvedVersions: forceResolvedVersions, observabilityScope: observabilitySystem.topScope ) @@ -430,6 +427,10 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { return [] } + public func generateBuildGraph(allowFileSystemWrites: Bool) async throws { + try await self.reloadPackage(forceResolvedVersions: !isForIndexBuild || !allowFileSystemWrites) + } + public func topologicalSort(of targets: [ConfiguredTarget]) -> [ConfiguredTarget]? { return targets.sorted { (lhs: ConfiguredTarget, rhs: ConfiguredTarget) -> Bool in let lhsIndex = self.targets[lhs]?.index ?? self.targets.count @@ -590,7 +591,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { logger.log("Reloading package because of file change") await orLog("Reloading package") { // TODO: It should not be necessary to reload the entire package just to get build settings for one file. - try await self.reloadPackage() + try await self.reloadPackage(forceResolvedVersions: !isForIndexBuild) } } diff --git a/Sources/SemanticIndex/SemanticIndexManager.swift b/Sources/SemanticIndex/SemanticIndexManager.swift index f01e7f218..4bdf9df18 100644 --- a/Sources/SemanticIndex/SemanticIndexManager.swift +++ b/Sources/SemanticIndex/SemanticIndexManager.swift @@ -204,7 +204,9 @@ public final actor SemanticIndexManager { signposter.endInterval("Preparing", state) } await testHooks.buildGraphGenerationDidStart?() - await orLog("Generating build graph") { try await self.buildSystemManager.generateBuildGraph() } + await orLog("Generating build graph") { + try await self.buildSystemManager.generateBuildGraph(allowFileSystemWrites: true) + } // Ensure that we have an up-to-date indexstore-db. Waiting for the indexstore-db to be updated is cheaper than // potentially not knowing about unit files, which causes the corresponding source files to be re-indexed. index.pollForUnitChangesAndWait() diff --git a/Sources/SourceKitLSP/CMakeLists.txt b/Sources/SourceKitLSP/CMakeLists.txt index 0a7c402d6..93a5ffd87 100644 --- a/Sources/SourceKitLSP/CMakeLists.txt +++ b/Sources/SourceKitLSP/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(SourceKitLSP STATIC CapabilityRegistry.swift + CreateBuildSystem.swift DocumentManager.swift DocumentSnapshot+FromFileContents.swift IndexProgressManager.swift diff --git a/Sources/SourceKitLSP/CreateBuildSystem.swift b/Sources/SourceKitLSP/CreateBuildSystem.swift new file mode 100644 index 000000000..9eb963419 --- /dev/null +++ b/Sources/SourceKitLSP/CreateBuildSystem.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import LSPLogging +import LanguageServerProtocol +import SKCore +import SKSwiftPMWorkspace + +import struct TSCBasic.AbsolutePath +import struct TSCBasic.RelativePath + +/// Tries to create a build system for a workspace at the given location, with the given parameters. +func createBuildSystem( + rootUri: DocumentURI, + options: SourceKitLSPServer.Options, + toolchainRegistry: ToolchainRegistry, + reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void +) async -> BuildSystem? { + guard let rootUrl = rootUri.fileURL, let rootPath = try? AbsolutePath(validating: rootUrl.path) else { + // We assume that workspaces are directories. This is only true for URLs not for URIs in general. + // Simply skip setting up the build integration in this case. + logger.error( + "cannot setup build integration at URI \(rootUri.forLogging) because the URI it is not a valid file URL" + ) + return nil + } + func createSwiftPMBuildSystem(rootUrl: URL) async -> SwiftPMBuildSystem? { + return await SwiftPMBuildSystem( + url: rootUrl, + toolchainRegistry: toolchainRegistry, + buildSetup: options.buildSetup, + isForIndexBuild: options.indexOptions.enableBackgroundIndexing, + reloadPackageStatusCallback: reloadPackageStatusCallback + ) + } + + func createCompilationDatabaseBuildSystem(rootPath: AbsolutePath) -> CompilationDatabaseBuildSystem? { + return CompilationDatabaseBuildSystem( + projectRoot: rootPath, + searchPaths: options.compilationDatabaseSearchPaths + ) + } + + func createBuildServerBuildSystem(rootPath: AbsolutePath) async -> BuildServerBuildSystem? { + return await BuildServerBuildSystem(projectRoot: rootPath, buildSetup: options.buildSetup) + } + + let defaultBuildSystem: BuildSystem? = + switch options.buildSetup.defaultWorkspaceType { + case .buildServer: await createBuildServerBuildSystem(rootPath: rootPath) + case .compilationDatabase: createCompilationDatabaseBuildSystem(rootPath: rootPath) + case .swiftPM: await createSwiftPMBuildSystem(rootUrl: rootUrl) + case nil: nil + } + if let defaultBuildSystem { + return defaultBuildSystem + } else if let buildServer = await createBuildServerBuildSystem(rootPath: rootPath) { + return buildServer + } else if let swiftpm = await createSwiftPMBuildSystem(rootUrl: rootUrl) { + return swiftpm + } else if let compdb = createCompilationDatabaseBuildSystem(rootPath: rootPath) { + return compdb + } else { + logger.error("Could not set up a build system at '\(rootUri.forLogging)'") + return nil + } +} diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 1172400ef..2d6a96e55 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -594,11 +594,20 @@ public actor SourceKitLSPServer { // The latter might happen if there is an existing SwiftPM workspace that hasn't been reloaded after a new file // was added to it and thus currently doesn't know that it can handle that file. In that case, we shouldn't open // a new workspace for the same root. Instead, the existing workspace's build system needs to be reloaded. - if let workspace = await self.createWorkspace(WorkspaceFolder(uri: DocumentURI(url))), - await workspace.buildSystemManager.fileHandlingCapability(for: uri) == .handled, - let projectRoot = await workspace.buildSystemManager.projectRoot, - !projectRoots.contains(projectRoot) - { + let workspace = await self.createWorkspace(WorkspaceFolder(uri: DocumentURI(url))) { buildSystem in + guard let buildSystem, !projectRoots.contains(await buildSystem.projectRoot) else { + // If we didn't create a build system, `url` is not capable of handling the document. + // If we already have a workspace at the same project root, don't create another one. + return false + } + do { + try await buildSystem.generateBuildGraph(allowFileSystemWrites: false) + } catch { + return false + } + return await buildSystem.fileHandlingCapability(for: uri) == .handled + } + if let workspace { return workspace } url.deleteLastPathComponent() @@ -1226,25 +1235,22 @@ extension SourceKitLSPServer { } /// Creates a workspace at the given `uri`. - private func createWorkspace(_ workspaceFolder: WorkspaceFolder) async -> Workspace? { + /// + /// If the build system that was determined for the workspace does not satisfy `condition`, `nil` is returned. + private func createWorkspace( + _ workspaceFolder: WorkspaceFolder, + condition: (BuildSystem?) async -> Bool = { _ in true } + ) async -> Workspace? { guard let capabilityRegistry = capabilityRegistry else { logger.log("Cannot open workspace before server is initialized") return nil } var options = self.options options.buildSetup = self.options.buildSetup.merging(buildSetup(for: workspaceFolder)) - return try? await Workspace( - documentManager: self.documentManager, + let buildSystem = await createBuildSystem( rootUri: workspaceFolder.uri, - capabilityRegistry: capabilityRegistry, - toolchainRegistry: self.toolchainRegistry, options: options, - compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths, - indexOptions: self.options.indexOptions, - indexTaskScheduler: indexTaskScheduler, - indexProcessDidProduceResult: { [weak self] in - self?.indexTaskDidProduceResult($0) - }, + toolchainRegistry: toolchainRegistry, reloadPackageStatusCallback: { [weak self] status in guard let self else { return } switch status { @@ -1253,6 +1259,34 @@ extension SourceKitLSPServer { case .end: await self.packageLoadingWorkDoneProgress.endProgress(server: self) } + } + ) + guard await condition(buildSystem) else { + return nil + } + do { + try await buildSystem?.generateBuildGraph(allowFileSystemWrites: true) + } catch { + logger.error("failed to generate build graph at \(workspaceFolder.uri.forLogging): \(error.forLogging)") + return nil + } + + let projectRoot = await buildSystem?.projectRoot.pathString + logger.log( + "Created workspace at \(workspaceFolder.uri.forLogging) as \(type(of: buildSystem)) with project root \(projectRoot ?? "")" + ) + + return try? await Workspace( + documentManager: self.documentManager, + rootUri: workspaceFolder.uri, + capabilityRegistry: capabilityRegistry, + buildSystem: buildSystem, + toolchainRegistry: self.toolchainRegistry, + options: options, + indexOptions: self.options.indexOptions, + indexTaskScheduler: indexTaskScheduler, + indexProcessDidProduceResult: { [weak self] in + self?.indexTaskDidProduceResult($0) }, indexTasksWereScheduled: { [weak self] count in self?.indexProgressManager.indexTasksWereScheduled(count: count) diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index f913520e5..1cd418b69 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -15,7 +15,6 @@ import LSPLogging import LanguageServerProtocol import SKCore import SKSupport -import SKSwiftPMWorkspace import SemanticIndex import struct TSCBasic.AbsolutePath @@ -148,82 +147,15 @@ public final class Workspace: Sendable { documentManager: DocumentManager, rootUri: DocumentURI, capabilityRegistry: CapabilityRegistry, + buildSystem: BuildSystem?, toolchainRegistry: ToolchainRegistry, options: SourceKitLSPServer.Options, - compilationDatabaseSearchPaths: [RelativePath], indexOptions: IndexOptions = IndexOptions(), indexTaskScheduler: TaskScheduler, indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void, - reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void, indexTasksWereScheduled: @Sendable @escaping (Int) -> Void, indexStatusDidChange: @Sendable @escaping () -> Void ) async throws { - var buildSystem: BuildSystem? = nil - - if let rootUrl = rootUri.fileURL, let rootPath = try? AbsolutePath(validating: rootUrl.path) { - var options = options - var isForIndexBuild = false - if options.indexOptions.enableBackgroundIndexing, options.buildSetup.path == nil { - options.buildSetup.path = rootPath.appending(component: ".index-build") - isForIndexBuild = true - } - func createSwiftPMBuildSystem(rootUrl: URL) async -> SwiftPMBuildSystem? { - return await SwiftPMBuildSystem( - url: rootUrl, - toolchainRegistry: toolchainRegistry, - buildSetup: options.buildSetup, - isForIndexBuild: isForIndexBuild, - reloadPackageStatusCallback: reloadPackageStatusCallback - ) - } - - func createCompilationDatabaseBuildSystem(rootPath: AbsolutePath) -> CompilationDatabaseBuildSystem? { - return CompilationDatabaseBuildSystem( - projectRoot: rootPath, - searchPaths: compilationDatabaseSearchPaths - ) - } - - func createBuildServerBuildSystem(rootPath: AbsolutePath) async -> BuildServerBuildSystem? { - return await BuildServerBuildSystem(projectRoot: rootPath, buildSetup: options.buildSetup) - } - - let defaultBuildSystem: BuildSystem? = - switch options.buildSetup.defaultWorkspaceType { - case .buildServer: await createBuildServerBuildSystem(rootPath: rootPath) - case .compilationDatabase: createCompilationDatabaseBuildSystem(rootPath: rootPath) - case .swiftPM: await createSwiftPMBuildSystem(rootUrl: rootUrl) - case nil: nil - } - if let defaultBuildSystem { - buildSystem = defaultBuildSystem - } else if let buildServer = await createBuildServerBuildSystem(rootPath: rootPath) { - buildSystem = buildServer - } else if let swiftpm = await createSwiftPMBuildSystem(rootUrl: rootUrl) { - buildSystem = swiftpm - } else if let compdb = createCompilationDatabaseBuildSystem(rootPath: rootPath) { - buildSystem = compdb - } else { - buildSystem = nil - } - if let buildSystem { - let projectRoot = await buildSystem.projectRoot - logger.log( - "Opening workspace at \(rootUrl) as \(type(of: buildSystem)) with project root \(projectRoot.pathString)" - ) - } else { - logger.error( - "Could not set up a build system for workspace at '\(rootUri.forLogging)'" - ) - } - } else { - // We assume that workspaces are directories. This is only true for URLs not for URIs in general. - // Simply skip setting up the build integration in this case. - logger.error( - "cannot setup build integration for workspace at URI \(rootUri.forLogging) because the URI it is not a valid file URL" - ) - } - var index: IndexStoreDB? = nil var indexDelegate: SourceKitIndexDelegate? = nil diff --git a/Tests/SKCoreTests/BuildSystemManagerTests.swift b/Tests/SKCoreTests/BuildSystemManagerTests.swift index 763991ecd..5eb4fad08 100644 --- a/Tests/SKCoreTests/BuildSystemManagerTests.swift +++ b/Tests/SKCoreTests/BuildSystemManagerTests.swift @@ -474,7 +474,7 @@ class ManualBuildSystem: BuildSystem { throw PrepareNotSupportedError() } - public func generateBuildGraph() {} + public func generateBuildGraph(allowFileSystemWrites: Bool) {} public func topologicalSort(of targets: [ConfiguredTarget]) -> [ConfiguredTarget]? { return nil diff --git a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift index 69cc4b0f2..92ec238dd 100644 --- a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift +++ b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift @@ -76,15 +76,14 @@ final class SwiftPMBuildSystemTests: XCTestCase { ) let packageRoot = tempDir.appending(component: "pkg") let tr = ToolchainRegistry.forTesting - await assertThrowsError( - try await SwiftPMBuildSystem( - workspacePath: packageRoot, - toolchainRegistry: tr, - fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, - isForIndexBuild: false - ) + let buildSystem = try await SwiftPMBuildSystem( + workspacePath: packageRoot, + toolchainRegistry: tr, + fileSystem: fs, + buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + isForIndexBuild: false ) + await assertThrowsError(try await buildSystem.generateBuildGraph(allowFileSystemWrites: false)) } } @@ -140,6 +139,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "lib", "a.swift") let hostTriple = await swiftpmBuildSystem.buildParameters.triple @@ -147,7 +147,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { assertEqual(await swiftpmBuildSystem.buildPath, build) assertNotNil(await swiftpmBuildSystem.indexStorePath) - let arguments = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) + .compilerArguments assertArgumentsContain("-module-name", "lib", arguments: arguments) assertArgumentsContain("-emit-dependencies", arguments: arguments) @@ -209,13 +210,15 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: config, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "lib", "a.swift") let hostTriple = await swiftpmBuildSystem.buildParameters.triple let build = buildPath(root: packageRoot, config: config, platform: hostTriple.platformBuildPathComponent) assertEqual(await swiftpmBuildSystem.buildPath, build) - let arguments = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) + .compilerArguments assertArgumentsContain("-typecheck", arguments: arguments) assertArgumentsContain("-Xcc", "-m32", arguments: arguments) @@ -247,9 +250,11 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let source = try resolveSymlinks(packageRoot.appending(component: "Package.swift")) - let arguments = try await swiftpmBuildSystem.buildSettings(for: source.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: source.asURI, language: .swift)) + .compilerArguments assertArgumentsContain("-swift-version", "4.2", arguments: arguments) assertArgumentsContain(source.pathString, arguments: arguments) @@ -281,15 +286,16 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "lib", "a.swift") let bswift = packageRoot.appending(components: "Sources", "lib", "b.swift") - let argumentsA = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)! + let argumentsA = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) .compilerArguments assertArgumentsContain(aswift.pathString, arguments: argumentsA) assertArgumentsContain(bswift.pathString, arguments: argumentsA) - let argumentsB = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)! + let argumentsB = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) .compilerArguments assertArgumentsContain(aswift.pathString, arguments: argumentsB) assertArgumentsContain(bswift.pathString, arguments: argumentsB) @@ -327,10 +333,12 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "libA", "a.swift") let bswift = packageRoot.appending(components: "Sources", "libB", "b.swift") - let arguments = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) + .compilerArguments assertArgumentsContain(aswift.pathString, arguments: arguments) assertArgumentsDoNotContain(bswift.pathString, arguments: arguments) // Temporary conditional to work around revlock between SourceKit-LSP and SwiftPM @@ -351,7 +359,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { ) } - let argumentsB = try await swiftpmBuildSystem.buildSettings(for: bswift.asURI, language: .swift)! + let argumentsB = try await unwrap(swiftpmBuildSystem.buildSettings(for: bswift.asURI, language: .swift)) .compilerArguments assertArgumentsContain(bswift.pathString, arguments: argumentsB) assertArgumentsDoNotContain(aswift.pathString, arguments: argumentsB) @@ -390,6 +398,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "libA", "a.swift") let bswift = packageRoot.appending(components: "Sources", "libB", "b.swift") @@ -431,6 +440,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let acxx = packageRoot.appending(components: "Sources", "lib", "a.cpp") let bcxx = packageRoot.appending(components: "Sources", "lib", "b.cpp") @@ -442,7 +452,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { assertNotNil(await swiftpmBuildSystem.indexStorePath) for file in [acxx, header] { - let args = try await swiftpmBuildSystem.buildSettings(for: file.asURI, language: .cpp)!.compilerArguments + let args = try await unwrap(swiftpmBuildSystem.buildSettings(for: file.asURI, language: .cpp)).compilerArguments assertArgumentsContain("-std=c++14", arguments: args) @@ -511,9 +521,11 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "lib", "a.swift") - let arguments = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) + .compilerArguments assertArgumentsContain("-target", arguments: arguments) // Only one! let hostTriple = await swiftpmBuildSystem.buildParameters.triple @@ -559,6 +571,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift1 = packageRoot.appending(components: "Sources", "lib", "a.swift") let aswift2 = @@ -624,6 +637,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) for file in [acpp, ah] { let args = try unwrap( @@ -665,9 +679,11 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Sources", "lib", "a.swift") - let arguments = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) + .compilerArguments assertArgumentsContain(aswift.pathString, arguments: arguments) XCTAssertNotNil( arguments.firstIndex(where: { @@ -739,6 +755,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, isForIndexBuild: false ) + try await swiftpmBuildSystem.generateBuildGraph(allowFileSystemWrites: false) let aswift = packageRoot.appending(components: "Plugins", "MyPlugin", "a.swift") let hostTriple = await swiftpmBuildSystem.buildParameters.triple @@ -746,7 +763,8 @@ final class SwiftPMBuildSystemTests: XCTestCase { assertEqual(await swiftpmBuildSystem.buildPath, build) assertNotNil(await swiftpmBuildSystem.indexStorePath) - let arguments = try await swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)!.compilerArguments + let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) + .compilerArguments // Plugins get compiled with the same compiler arguments as the package manifest assertArgumentsContain("-package-description-version", "5.7.0", arguments: arguments) diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 0c42fa06e..805adbfeb 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -821,4 +821,27 @@ final class BackgroundIndexingTests: XCTestCase { ) _ = try await otherClient.send(PollIndexRequest()) } + + func testOpeningFileThatIsNotPartOfThePackageDoesntGenerateABuildFolderThere() async throws { + let project = try await SwiftPMTestProject( + files: [ + "Lib.swift": "", + "OtherLib/OtherLib.swift": "", + ], + serverOptions: backgroundIndexingOptions + ) + _ = try project.openDocument("OtherLib.swift") + // Wait for 1 second to increase the likelihood of this test failing in case we would start scheduling some + // background task that causes a build in the `OtherLib` directory. + try await Task.sleep(for: .seconds(1)) + let nestedIndexBuildURL = try XCTUnwrap( + project.uri(for: "OtherLib.swift").fileURL? + .deletingLastPathComponent() + .appendingPathComponent(".index-build") + ) + XCTAssertFalse( + FileManager.default.fileExists(atPath: nestedIndexBuildURL.path), + "No file should exist at \(nestedIndexBuildURL)" + ) + } } diff --git a/Tests/SourceKitLSPTests/BuildSystemTests.swift b/Tests/SourceKitLSPTests/BuildSystemTests.swift index 378df97f8..ff9b5adb4 100644 --- a/Tests/SourceKitLSPTests/BuildSystemTests.swift +++ b/Tests/SourceKitLSPTests/BuildSystemTests.swift @@ -66,7 +66,7 @@ actor TestBuildSystem: BuildSystem { throw PrepareNotSupportedError() } - public func generateBuildGraph() {} + public func generateBuildGraph(allowFileSystemWrites: Bool) {} public func topologicalSort(of targets: [ConfiguredTarget]) -> [ConfiguredTarget]? { return nil