Skip to content

Create BuiltInBuildSystem in BuildSystemAdapter #1653

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
merged 4 commits into from
Sep 10, 2024
Merged
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
18 changes: 12 additions & 6 deletions Sources/BuildSystemIntegration/BuildServerBuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Foundation
import LanguageServerProtocol
import LanguageServerProtocolJSONRPC
import SKLogging
import SKOptions
import SKSupport
import SwiftExtensions
import ToolchainRegistry
Expand Down Expand Up @@ -76,15 +77,12 @@ package actor BuildServerBuildSystem: MessageHandler {

package weak var messageHandler: BuiltInBuildSystemMessageHandler?

package func setMessageHandler(_ messageHandler: any BuiltInBuildSystemMessageHandler) {
self.messageHandler = messageHandler
}

/// The build settings that have been received from the build server.
private var buildSettings: [DocumentURI: FileBuildSettings] = [:]

package init(
projectRoot: AbsolutePath,
messageHandler: BuiltInBuildSystemMessageHandler?,
fileSystem: FileSystem = localFileSystem
) async throws {
let configPath = projectRoot.appending(component: "buildServer.json")
Expand All @@ -104,17 +102,18 @@ package actor BuildServerBuildSystem: MessageHandler {
#endif
self.projectRoot = projectRoot
self.serverConfig = config
self.messageHandler = messageHandler
try await self.initializeBuildServer()
}

/// Creates a build system using the Build Server Protocol config.
///
/// - Returns: nil if `projectRoot` has no config or there is an error parsing it.
package init?(projectRoot: AbsolutePath?) async {
package init?(projectRoot: AbsolutePath?, messageHandler: BuiltInBuildSystemMessageHandler?) async {
guard let projectRoot else { return nil }

do {
try await self.init(projectRoot: projectRoot)
try await self.init(projectRoot: projectRoot, messageHandler: messageHandler)
} catch is FileSystemError {
// config file was missing, no build server for this workspace
return nil
Expand Down Expand Up @@ -259,6 +258,13 @@ private func readReponseDataKey(data: LSPAny?, key: String) -> String? {
}

extension BuildServerBuildSystem: BuiltInBuildSystem {
static package func projectRoot(for workspaceFolder: AbsolutePath, options: SourceKitLSPOptions) -> AbsolutePath? {
guard localFileSystem.isFile(workspaceFolder.appending(component: "buildServer.json")) else {
return nil
}
return workspaceFolder
}

package nonisolated var supportsPreparation: Bool { false }

/// The build settings for the given file.
Expand Down
14 changes: 9 additions & 5 deletions Sources/BuildSystemIntegration/BuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
import BuildServerProtocol
import LanguageServerProtocol
import SKLogging
import SKOptions
import ToolchainRegistry

import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath

/// Defines how well a `BuildSystem` can handle a file with a given URI.
package enum FileHandlingCapability: Comparable, Sendable {
Expand Down Expand Up @@ -67,6 +69,13 @@ package struct PrepareNotSupportedError: Error, CustomStringConvertible {
/// For example, a SwiftPMWorkspace provides compiler arguments for the files
/// contained in a SwiftPM package root directory.
package protocol BuiltInBuildSystem: AnyObject, Sendable {
/// When opening an LSP workspace at `workspaceFolder`, determine the directory in which a project of this build system
/// starts. For example, a user might open the `Sources` folder of a SwiftPM project, then the project root is the
/// directory containing `Package.swift`.
///
/// Returns `nil` if the build system can't handle the given workspace folder
static func projectRoot(for workspaceFolder: AbsolutePath, options: SourceKitLSPOptions) -> AbsolutePath?

/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
/// was found.
Expand All @@ -90,11 +99,6 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable {
/// context.
func setDelegate(_ delegate: BuildSystemDelegate?) async

/// Set the message handler that is used to send messages from the build system to SourceKit-LSP.
// FIXME: (BSP Migration) This should be set in the initializer but can't right now because BuiltInBuildSystemAdapter is not
// responsible for creating the build system.
func setMessageHandler(_ messageHandler: BuiltInBuildSystemMessageHandler) async

/// Whether the build system is capable of preparing a target for indexing, ie. if the `prepare` methods has been
/// implemented.
var supportsPreparation: Bool { get }
Expand Down
48 changes: 34 additions & 14 deletions Sources/BuildSystemIntegration/BuildSystemManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import BuildServerProtocol
import Dispatch
import LanguageServerProtocol
import SKLogging
import SKOptions
import SwiftExtensions
import ToolchainRegistry

Expand Down Expand Up @@ -76,10 +77,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
/// The underlying primary build system.
///
/// - Important: The only time this should be modified is in the initializer. Afterwards, it must be constant.
private(set) var buildSystem: BuiltInBuildSystemAdapter?

/// Timeout before fallback build settings are used.
let fallbackSettingsTimeout: DispatchTimeInterval
private(set) package var buildSystem: BuiltInBuildSystemAdapter?

/// The fallback build system. If present, used when the `buildSystem` is not
/// set or cannot provide settings.
Expand Down Expand Up @@ -108,27 +106,49 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
}

package var supportsPreparation: Bool {
return buildSystem?.underlyingBuildSystem.supportsPreparation ?? false
get async {
return await buildSystem?.underlyingBuildSystem.supportsPreparation ?? false
}
}

/// Create a BuildSystemManager that wraps the given build system. The new
/// manager will modify the delegate of the underlying build system.
package init(
buildSystem: BuiltInBuildSystem?,
buildSystemKind: (WorkspaceType, projectRoot: AbsolutePath)?,
toolchainRegistry: ToolchainRegistry,
options: SourceKitLSPOptions,
swiftpmTestHooks: SwiftPMTestHooks,
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void
) async {
self.fallbackBuildSystem = FallbackBuildSystem(options: options.fallbackBuildSystemOrDefault)
self.toolchainRegistry = toolchainRegistry
self.buildSystem = await BuiltInBuildSystemAdapter(
buildSystemKind: buildSystemKind,
toolchainRegistry: toolchainRegistry,
options: options,
swiftpmTestHooks: swiftpmTestHooks,
reloadPackageStatusCallback: reloadPackageStatusCallback,
messageHandler: self
)
await self.buildSystem?.underlyingBuildSystem.setDelegate(self)
}

/// Create a BuildSystemManager that wraps the given build system.
/// The new manager will modify the delegate of the underlying build system.
///
/// - Important: For testing purposes only
package init(
testBuildSystem: BuiltInBuildSystem?,
fallbackBuildSystem: FallbackBuildSystem?,
mainFilesProvider: MainFilesProvider?,
toolchainRegistry: ToolchainRegistry,
fallbackSettingsTimeout: DispatchTimeInterval = .seconds(3)
toolchainRegistry: ToolchainRegistry
) async {
let buildSystemHasDelegate = await buildSystem?.delegate != nil
let buildSystemHasDelegate = await testBuildSystem?.delegate != nil
precondition(!buildSystemHasDelegate)
self.fallbackBuildSystem = fallbackBuildSystem
self.mainFilesProvider = mainFilesProvider
self.toolchainRegistry = toolchainRegistry
self.fallbackSettingsTimeout = fallbackSettingsTimeout
self.buildSystem =
if let buildSystem {
await BuiltInBuildSystemAdapter(buildSystem: buildSystem, messageHandler: self)
if let testBuildSystem {
await BuiltInBuildSystemAdapter(testBuildSystem: testBuildSystem, messageHandler: self)
} else {
nil
}
Expand Down
81 changes: 74 additions & 7 deletions Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
import BuildServerProtocol
import LanguageServerProtocol
import SKLogging
import SKOptions
import SKSupport
import ToolchainRegistry

import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath

// FIXME: (BSP Migration) This should be a MessageHandler once we have migrated all build system queries to BSP and can use
// LocalConnection for the communication.
Expand All @@ -29,22 +34,84 @@ package protocol BuiltInBuildSystemMessageHandler: AnyObject, Sendable {
func sendRequestToSourceKitLSP<R: RequestType>(_ request: R) async throws -> R.Response
}

/// Create a build system of the given type.
private func createBuildSystem(
ofType buildSystemType: WorkspaceType,
projectRoot: AbsolutePath,
options: SourceKitLSPOptions,
swiftpmTestHooks: SwiftPMTestHooks,
toolchainRegistry: ToolchainRegistry,
messageHandler: BuiltInBuildSystemMessageHandler,
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void
) async -> BuiltInBuildSystem? {
switch buildSystemType {
case .buildServer:
return await BuildServerBuildSystem(projectRoot: projectRoot, messageHandler: messageHandler)
case .compilationDatabase:
return CompilationDatabaseBuildSystem(
projectRoot: projectRoot,
searchPaths: (options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap {
try? RelativePath(validating: $0)
},
messageHandler: messageHandler
)
case .swiftPM:
return await SwiftPMBuildSystem(
projectRoot: projectRoot,
toolchainRegistry: toolchainRegistry,
options: options,
messageHandler: messageHandler,
reloadPackageStatusCallback: reloadPackageStatusCallback,
testHooks: swiftpmTestHooks
)
}
}

/// A type that outwardly acts as a build server conforming to the Build System Integration Protocol and internally uses
/// a `BuiltInBuildSystem` to satisfy the requests.
actor BuiltInBuildSystemAdapter: BuiltInBuildSystemMessageHandler {
package actor BuiltInBuildSystemAdapter: BuiltInBuildSystemMessageHandler {
/// The underlying build system
// FIXME: (BSP Migration) This should be private, all messages should go through BSP. Only accessible from the outside for transition
// purposes.
package let underlyingBuildSystem: BuiltInBuildSystem
private(set) package var underlyingBuildSystem: BuiltInBuildSystem!
private let messageHandler: any BuiltInBuildSystemAdapterDelegate

init(
buildSystem: BuiltInBuildSystem,
init?(
buildSystemKind: (WorkspaceType, projectRoot: AbsolutePath)?,
toolchainRegistry: ToolchainRegistry,
options: SourceKitLSPOptions,
swiftpmTestHooks: SwiftPMTestHooks,
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void,
messageHandler: any BuiltInBuildSystemAdapterDelegate
) async {
guard let (buildSystemType, projectRoot) = buildSystemKind else {
return nil
}
self.messageHandler = messageHandler

let buildSystem = await createBuildSystem(
ofType: buildSystemType,
projectRoot: projectRoot,
options: options,
swiftpmTestHooks: swiftpmTestHooks,
toolchainRegistry: toolchainRegistry,
messageHandler: self,
reloadPackageStatusCallback: reloadPackageStatusCallback
)
guard let buildSystem else {
return nil
}

self.underlyingBuildSystem = buildSystem
}

/// - Important: For testing purposes only
init(
testBuildSystem: BuiltInBuildSystem,
messageHandler: any BuiltInBuildSystemAdapterDelegate
) async {
self.underlyingBuildSystem = testBuildSystem
self.messageHandler = messageHandler
await buildSystem.setMessageHandler(self)
}

package func send<R: RequestType>(_ request: R) async throws -> R.Response {
Expand Down Expand Up @@ -89,7 +156,7 @@ actor BuiltInBuildSystemAdapter: BuiltInBuildSystemMessageHandler {
}
}

func sendNotificationToSourceKitLSP(_ notification: some LanguageServerProtocol.NotificationType) async {
package func sendNotificationToSourceKitLSP(_ notification: some LanguageServerProtocol.NotificationType) async {
logger.info(
"""
Received notification from build system
Expand All @@ -99,7 +166,7 @@ actor BuiltInBuildSystemAdapter: BuiltInBuildSystemMessageHandler {
await messageHandler.handle(notification)
}

func sendRequestToSourceKitLSP<R: RequestType>(_ request: R) async throws -> R.Response {
package func sendRequestToSourceKitLSP<R: RequestType>(_ request: R) async throws -> R.Response {
logger.info(
"""
Received request from build system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import BuildServerProtocol
import Dispatch
import LanguageServerProtocol
import SKLogging
import SKOptions
import SKSupport
import ToolchainRegistry

Expand Down Expand Up @@ -49,10 +50,6 @@ package actor CompilationDatabaseBuildSystem {

package weak var messageHandler: BuiltInBuildSystemMessageHandler?

package func setMessageHandler(_ messageHandler: any BuiltInBuildSystemMessageHandler) {
self.messageHandler = messageHandler
}

package let projectRoot: AbsolutePath

let searchPaths: [RelativePath]
Expand Down Expand Up @@ -86,11 +83,13 @@ package actor CompilationDatabaseBuildSystem {
package init?(
projectRoot: AbsolutePath,
searchPaths: [RelativePath],
messageHandler: (any BuiltInBuildSystemMessageHandler)?,
fileSystem: FileSystem = localFileSystem
) {
self.fileSystem = fileSystem
self.projectRoot = projectRoot
self.searchPaths = searchPaths
self.messageHandler = messageHandler
if let compdb = tryLoadCompilationDatabase(directory: projectRoot, additionalSearchPaths: searchPaths, fileSystem) {
self.compdb = compdb
} else {
Expand All @@ -100,6 +99,13 @@ package actor CompilationDatabaseBuildSystem {
}

extension CompilationDatabaseBuildSystem: BuiltInBuildSystem {
static package func projectRoot(for workspaceFolder: AbsolutePath, options: SourceKitLSPOptions) -> AbsolutePath? {
if tryLoadCompilationDatabase(directory: workspaceFolder) != nil {
return workspaceFolder
}
return nil
}

package nonisolated var supportsPreparation: Bool { false }

package var indexDatabasePath: AbsolutePath? {
Expand Down
Loading