Skip to content

Commit f07a30e

Browse files
authored
Merge pull request #1665 from ahoppen/another-asp
Migrate the remaining methods in `BuiltInBuildSystem` to be BSP-based
2 parents f5245bf + a96c091 commit f07a30e

17 files changed

+197
-103
lines changed

Sources/BuildServerProtocol/BuildTargetsRequest.swift

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ public struct BuildTarget: Codable, Hashable, Sendable {
8181
tags: [BuildTargetTag],
8282
capabilities: BuildTargetCapabilities,
8383
languageIds: [Language],
84-
dependencies: [BuildTargetIdentifier]
84+
dependencies: [BuildTargetIdentifier],
85+
dataKind: BuildTargetDataKind? = nil,
86+
data: LSPAny? = nil
8587
) {
8688
self.id = id
8789
self.displayName = displayName
@@ -90,6 +92,8 @@ public struct BuildTarget: Codable, Hashable, Sendable {
9092
self.capabilities = capabilities
9193
self.languageIds = languageIds
9294
self.dependencies = dependencies
95+
self.dataKind = dataKind
96+
self.data = data
9397
}
9498
}
9599

@@ -177,22 +181,52 @@ public struct BuildTargetDataKind: RawRepresentable, Codable, Hashable, Sendable
177181
}
178182

179183
/// `data` field must contain a CargoBuildTarget object.
180-
public static let cargo = "cargo"
184+
public static let cargo = BuildTargetDataKind(rawValue: "cargo")
181185

182186
/// `data` field must contain a CppBuildTarget object.
183-
public static let cpp = "cpp"
187+
public static let cpp = BuildTargetDataKind(rawValue: "cpp")
184188

185189
/// `data` field must contain a JvmBuildTarget object.
186-
public static let jvm = "jvm"
190+
public static let jvm = BuildTargetDataKind(rawValue: "jvm")
187191

188192
/// `data` field must contain a PythonBuildTarget object.
189-
public static let python = "python"
193+
public static let python = BuildTargetDataKind(rawValue: "python")
190194

191195
/// `data` field must contain a SbtBuildTarget object.
192-
public static let sbt = "sbt"
196+
public static let sbt = BuildTargetDataKind(rawValue: "sbt")
193197

194198
/// `data` field must contain a ScalaBuildTarget object.
195-
public static let scala = "scala"
199+
public static let scala = BuildTargetDataKind(rawValue: "scala")
200+
201+
/// `data` field must contain a SourceKitBuildTarget object.
202+
public static let sourceKit = BuildTargetDataKind(rawValue: "sourceKit")
203+
}
204+
205+
public struct SourceKitBuildTarget: LSPAnyCodable, Codable {
206+
/// The toolchain that should be used to build this target. The URI should point to the directory that contains the
207+
/// `usr` directory. On macOS, this is typically a bundle ending in `.xctoolchain`. If the toolchain is installed to
208+
/// `/` on Linux, the toolchain URI would point to `/`.
209+
///
210+
/// If no toolchain is given, SourceKit-LSP will pick a toolchain to use for this target.
211+
public var toolchain: URI?
212+
213+
public init(toolchain: URI? = nil) {
214+
self.toolchain = toolchain
215+
}
216+
217+
public init(fromLSPDictionary dictionary: [String: LanguageServerProtocol.LSPAny]) {
218+
if case .string(let toolchain) = dictionary[CodingKeys.toolchain.stringValue] {
219+
self.toolchain = try? URI(string: toolchain)
220+
}
221+
}
222+
223+
public func encodeToLSPAny() -> LanguageServerProtocol.LSPAny {
224+
var result: [String: LSPAny] = [:]
225+
if let toolchain {
226+
result[CodingKeys.toolchain.stringValue] = .string(toolchain.stringValue)
227+
}
228+
return .dictionary(result)
229+
}
196230
}
197231

198232
/// The build target output paths request is sent from the client to the server

Sources/BuildServerProtocol/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ add_library(BuildServerProtocol STATIC
1010
PrepareTargetsRequest.swift
1111
RegisterForChangeNotifications.swift
1212
ShutdownBuild.swift
13-
SourceKitOptionsRequest.swift)
13+
SourceKitOptionsRequest.swift
14+
WaitForBuildSystemUpdates.swift)
1415
set_target_properties(BuildServerProtocol PROPERTIES
1516
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
1617
target_link_libraries(BuildServerProtocol PRIVATE

Sources/BuildServerProtocol/InitializeBuild.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public struct InitializeBuildResponseDataKind: RawRepresentable, Hashable, Codab
104104
}
105105

106106
/// `data` field must contain a `SourceKitInitializeBuildResponseData` object.
107-
public static let sourceKit = InitializeBuildResponseDataKind(rawValue: "sourcekit")
107+
public static let sourceKit = InitializeBuildResponseDataKind(rawValue: "sourceKit")
108108
}
109109

110110
public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Sendable {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LanguageServerProtocol
14+
15+
/// This request is a no-op and doesn't have any effects.
16+
///
17+
/// If the build system is currently updating the build graph, this request should return after those updates have
18+
/// finished processing.
19+
public struct WaitForBuildSystemUpdatesRequest: RequestType, Hashable {
20+
public typealias Response = VoidResponse
21+
22+
public static let method: String = "workspace/waitForBuildSystemUpdates"
23+
24+
public init() {}
25+
}

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,8 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
330330
)
331331
}
332332

333-
package func toolchain(for uri: DocumentURI, _ language: Language) async -> Toolchain? {
334-
return nil
335-
}
336-
337-
package func waitForUpToDateBuildGraph() async {}
338-
339-
package func addSourceFilesDidChangeCallback(_ callback: @escaping () async -> Void) {
340-
// BuildServerBuildSystem does not support syntactic test discovery or background indexing.
341-
// (https://github.com/swiftlang/sourcekit-lsp/issues/1173).
333+
package func waitForUpBuildSystemUpdates(request: WaitForBuildSystemUpdatesRequest) async -> VoidResponse {
334+
return VoidResponse()
342335
}
343336
}
344337

Sources/BuildSystemIntegration/BuildSystemDelegate.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ package protocol BuildSystemManagerDelegate: AnyObject, Sendable {
3232

3333
/// Log the given message to the client's index log.
3434
func logMessageToIndexLog(taskID: IndexTaskID, message: String)
35+
36+
/// Notify the delegate that the list of source files in the build system might have changed.
37+
func sourceFilesDidChange() async
3538
}

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 84 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ import struct TSCBasic.AbsolutePath
2525
import os
2626
#endif
2727

28-
fileprivate class RequestCache<Request: RequestType & Hashable> {
29-
private var storage: [Request: Task<Request.Response, Error>] = [:]
28+
fileprivate class RequestCache<Request: RequestType & Hashable, Result: Sendable> {
29+
private var storage: [Request: Task<Result, Error>] = [:]
3030

3131
func get(
3232
_ key: Request,
3333
isolation: isolated any Actor = #isolation,
34-
compute: @Sendable @escaping (Request) async throws(Error) -> Request.Response
35-
) async throws(Error) -> Request.Response {
36-
let task: Task<Request.Response, Error>
34+
compute: @Sendable @escaping (Request) async throws(Error) -> Result
35+
) async throws(Error) -> Result {
36+
let task: Task<Result, Error>
3737
if let cached = storage[key] {
3838
task = cached
3939
} else {
@@ -110,15 +110,15 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
110110
/// Force-unwrapped optional because initializing it requires access to `self`.
111111
private var filesDependenciesUpdatedDebouncer: Debouncer<Set<DocumentURI>>! = nil
112112

113-
private var cachedTargetsForDocument = RequestCache<InverseSourcesRequest>()
113+
private var cachedTargetsForDocument = RequestCache<InverseSourcesRequest, InverseSourcesResponse>()
114114

115-
private var cachedSourceKitOptions = RequestCache<SourceKitOptionsRequest>()
115+
private var cachedSourceKitOptions = RequestCache<SourceKitOptionsRequest, SourceKitOptionsResponse?>()
116116

117-
private var cachedBuildTargets = RequestCache<BuildTargetsRequest>()
117+
private var cachedBuildTargets = RequestCache<
118+
BuildTargetsRequest, [BuildTargetIdentifier: (target: BuildTarget, depth: Int)]
119+
>()
118120

119-
private var cachedTargetSources = RequestCache<BuildTargetSourcesRequest>()
120-
121-
private var cachedTargetDepths: (buildTargets: [BuildTarget], depths: [BuildTargetIdentifier: Int])? = nil
121+
private var cachedTargetSources = RequestCache<BuildTargetSourcesRequest, BuildTargetSourcesResponse>()
122122

123123
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
124124
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
@@ -176,6 +176,8 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
176176
}
177177
await delegate.filesDependenciesUpdated(changedWatchedFiles)
178178
}
179+
180+
// FIXME: (BSP migration) Forward file watch patterns from this initialize request to the client
179181
initializeResult = Task { () -> InitializeBuildResponse? in
180182
guard let buildSystem else {
181183
return nil
@@ -222,10 +224,9 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
222224
if !options.backgroundIndexingOrDefault,
223225
events.contains(where: { $0.uri.fileURL?.pathExtension == "swiftmodule" })
224226
{
225-
let targets = await orLog("Getting build targets") {
226-
try await self.buildTargets()
227+
await orLog("Getting build targets") {
228+
targetsWithUpdatedDependencies.formUnion(try await self.buildTargets().keys)
227229
}
228-
targetsWithUpdatedDependencies.formUnion(targets?.map(\.id) ?? [])
229230
}
230231

231232
var filesWithUpdatedDependencies: Set<DocumentURI> = []
@@ -271,9 +272,37 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
271272
}
272273

273274
/// Returns the toolchain that should be used to process the given document.
274-
package func toolchain(for uri: DocumentURI, _ language: Language) async -> Toolchain? {
275-
if let toolchain = await buildSystem?.underlyingBuildSystem.toolchain(for: uri, language) {
276-
return toolchain
275+
package func toolchain(
276+
for uri: DocumentURI,
277+
in target: BuildTargetIdentifier?,
278+
language: Language
279+
) async -> Toolchain? {
280+
let toolchainPath = await orLog("Getting toolchain from build targets") { () -> AbsolutePath? in
281+
guard let target else {
282+
return nil
283+
}
284+
let targets = try await self.buildTargets()
285+
guard let target = targets[target]?.target else {
286+
logger.error("Failed to find target \(target.forLogging) to determine toolchain")
287+
return nil
288+
}
289+
guard target.dataKind == .sourceKit, case .dictionary(let data) = target.data else {
290+
return nil
291+
}
292+
guard let toolchain = SourceKitBuildTarget(fromLSPDictionary: data).toolchain else {
293+
return nil
294+
}
295+
guard let toolchainUrl = toolchain.fileURL else {
296+
logger.error("Toolchain is not a file URL")
297+
return nil
298+
}
299+
return try AbsolutePath(validating: toolchainUrl.path)
300+
}
301+
if let toolchainPath {
302+
if let toolchain = await self.toolchainRegistry.toolchain(withPath: toolchainPath) {
303+
return toolchain
304+
}
305+
logger.error("Toolchain at \(toolchainPath) not registered in toolchain registry.")
277306
}
278307

279308
switch language {
@@ -484,15 +513,14 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
484513
}
485514

486515
package func waitForUpToDateBuildGraph() async {
487-
await self.buildSystem?.underlyingBuildSystem.waitForUpToDateBuildGraph()
516+
await orLog("Waiting for build system updates") {
517+
let _: VoidResponse? = try await self.buildSystem?.send(WaitForBuildSystemUpdatesRequest())
518+
}
488519
}
489520

490521
/// The root targets of the project have depth of 0 and all target dependencies have a greater depth than the target
491522
// itself.
492523
private func targetDepths(for buildTargets: [BuildTarget]) -> [BuildTargetIdentifier: Int] {
493-
if let cachedTargetDepths, cachedTargetDepths.buildTargets == buildTargets {
494-
return cachedTargetDepths.depths
495-
}
496524
var nonRoots: Set<BuildTargetIdentifier> = []
497525
for buildTarget in buildTargets {
498526
nonRoots.formUnion(buildTarget.dependencies)
@@ -511,7 +539,6 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
511539
}
512540
}
513541
}
514-
cachedTargetDepths = (buildTargets, depths)
515542
return depths
516543
}
517544

@@ -522,25 +549,25 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
522549
///
523550
/// `nil` if the build system doesn't support topological sorting of targets.
524551
package func topologicalSort(of targets: [BuildTargetIdentifier]) async throws -> [BuildTargetIdentifier]? {
525-
guard let workspaceTargets = await orLog("Getting build targets for topological sort", { try await buildTargets() })
552+
guard let buildTargets = await orLog("Getting build targets for topological sort", { try await buildTargets() })
526553
else {
527554
return nil
528555
}
529556

530-
let depths = targetDepths(for: workspaceTargets)
531557
return targets.sorted { (lhs: BuildTargetIdentifier, rhs: BuildTargetIdentifier) -> Bool in
532-
return depths[lhs, default: 0] > depths[rhs, default: 0]
558+
return (buildTargets[lhs]?.depth ?? 0) > (buildTargets[rhs]?.depth ?? 0)
533559
}
534560
}
535561

536562
/// Returns the list of targets that might depend on the given target and that need to be re-prepared when a file in
537563
/// `target` is modified.
538564
package func targets(dependingOn targetIds: Set<BuildTargetIdentifier>) async -> [BuildTargetIdentifier] {
539-
guard let buildTargets = await orLog("Getting build targets for dependencies", { try await self.buildTargets() })
565+
guard
566+
let buildTargets = await orLog("Getting build targets for dependencies", { try await self.buildTargets().values })
540567
else {
541568
return []
542569
}
543-
return buildTargets.filter { $0.dependencies.contains(anyIn: targetIds) }.map { $0.id }
570+
return buildTargets.filter { $0.target.dependencies.contains(anyIn: targetIds) }.map { $0.target.id }
544571
}
545572

546573
package func prepare(
@@ -564,26 +591,46 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
564591
self.watchedFiles[uri] = nil
565592
}
566593

567-
package func buildTargets() async throws -> [BuildTarget] {
594+
package func buildTargets() async throws -> [BuildTargetIdentifier: (target: BuildTarget, depth: Int)] {
568595
guard let buildSystem else {
569-
return []
596+
return [:]
570597
}
571598

572599
let request = BuildTargetsRequest()
573-
let response = try await cachedBuildTargets.get(request) { request in
574-
try await buildSystem.send(request)
600+
let result = try await cachedBuildTargets.get(request) { request in
601+
let buildTargets = try await buildSystem.send(request).targets
602+
let depths = await self.targetDepths(for: buildTargets)
603+
var result: [BuildTargetIdentifier: (target: BuildTarget, depth: Int)] = [:]
604+
result.reserveCapacity(buildTargets.count)
605+
for buildTarget in buildTargets {
606+
guard result[buildTarget.id] == nil else {
607+
logger.error("Found two targets with the same ID \(buildTarget.id)")
608+
continue
609+
}
610+
let depth: Int
611+
if let d = depths[buildTarget.id] {
612+
depth = d
613+
} else {
614+
logger.fault("Did not compute depth for target \(buildTarget.id)")
615+
depth = 0
616+
}
617+
result[buildTarget.id] = (buildTarget, depth)
618+
}
619+
return result
575620
}
576-
return response.targets
621+
return result
577622
}
578623

579-
package func sourceFiles(in targets: [BuildTargetIdentifier]) async throws -> [SourcesItem] {
624+
package func sourceFiles(in targets: some Sequence<BuildTargetIdentifier>) async throws -> [SourcesItem] {
580625
guard let buildSystem else {
581626
return []
582627
}
583628

584629
// FIXME: (BSP migration) If we have a cached request for a superset of the targets, serve the result from that
585630
// cache entry.
586-
let request = BuildTargetSourcesRequest.init(targets: targets)
631+
// Sort targets to help cache hits if we have two calls to `sourceFiles` with targets in different orders.
632+
let sortedTargets = targets.sorted { $0.uri.stringValue < $1.uri.stringValue }
633+
let request = BuildTargetSourcesRequest(targets: sortedTargets)
587634
let response = try await cachedTargetSources.get(request) { request in
588635
try await buildSystem.send(request)
589636
}
@@ -595,9 +642,8 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
595642
// retrieving the source files for those targets.
596643
// FIXME: (BSP Migration) Handle source files that are in multiple targets
597644
let targets = try await self.buildTargets()
598-
let targetsById = Dictionary(elements: targets, keyedBy: \.id)
599-
let sourceFiles = try await self.sourceFiles(in: targets.map(\.id)).flatMap { sourcesItem in
600-
let target = targetsById[sourcesItem.target]
645+
let sourceFiles = try await self.sourceFiles(in: targets.keys).flatMap { sourcesItem in
646+
let target = targets[sourcesItem.target]?.target
601647
return sourcesItem.sources.map { sourceItem in
602648
SourceFileInfo(
603649
uri: sourceItem.uri,
@@ -663,6 +709,7 @@ extension BuildSystemManager {
663709
// FIXME: (BSP Migration) Communicate that the build target has changed to the `BuildSystemManagerDelegate` and make
664710
// it responsible for figuring out which files are affected.
665711
await delegate?.fileBuildSettingsChanged(Set(watchedFiles.keys))
712+
await self.delegate?.sourceFilesDidChange()
666713
}
667714

668715
private func logMessage(notification: BuildServerProtocol.LogMessageNotification) async {

0 commit comments

Comments
 (0)