Skip to content

Commit e3cacd8

Browse files
authored
Reply faster to BuildTargets (#126)
1 parent e8a5788 commit e3cacd8

File tree

6 files changed

+125
-168
lines changed

6 files changed

+125
-168
lines changed

Example/.bazelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ build --spawn_strategy=remote,worker,local
3131
# Only for BSP builds
3232
common:index_build --experimental_convenience_symlinks=ignore
3333
common:index_build --bes_backend= --bes_results_url=
34-
common:index_build --show_result=0
34+
common:index_build --show_result=0 --noshow_loading_progress --noshow_progress

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ private let logger = makeFileLevelBSPLogger()
3030
// the project's dependency graph and its files.
3131
protocol BazelTargetStore: AnyObject {
3232
var stateLock: OSAllocatedUnfairLock<Void> { get }
33+
var isInitialized: Bool { get }
3334
func fetchTargets() throws -> [BuildTarget]
3435
func bazelTargetLabel(forBSPURI uri: URI) throws -> String
3536
func bazelTargetSrcs(forBSPURI uri: URI) throws -> [URI]
@@ -100,6 +101,11 @@ final class BazelTargetStoreImpl: BazelTargetStore, @unchecked Sendable {
100101
self.bazelTargetQuerier = bazelTargetQuerier
101102
}
102103

104+
/// Returns true if the store has actually processed something.
105+
var isInitialized: Bool {
106+
return cachedTargets != nil
107+
}
108+
103109
/// Converts a BSP BuildTarget URI to its underlying Bazel target label.
104110
func bazelTargetLabel(forBSPURI uri: URI) throws -> String {
105111
guard let label = cqueryResult?.bspURIsToBazelLabelsMap[uri] else {

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BuildTargetsHandler.swift

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,41 @@ final class BuildTargetsHandler {
4646
let taskId = TaskId(id: "buildTargets-\(id.description)")
4747
connection?.startWorkTask(id: taskId, title: "sourcekit-bazel-bsp: Processing the build graph...")
4848
do {
49-
nonisolated(unsafe) var shouldDispatchNotification = false
49+
nonisolated(unsafe) var shouldReplyEmpty = false
5050
let result = try targetStore.stateLock.withLockUnchecked {
51-
shouldDispatchNotification = isFirstTime
51+
shouldReplyEmpty = isFirstTime
5252
isFirstTime = false
53-
return try targetStore.fetchTargets()
53+
// If this is the first time we're responding to buildTargets, sourcekit-lsp will expect us to return an empty list
54+
// and then later send a notification containing the changes for performance reasons.
55+
// See https://github.com/spotify/sourcekit-bazel-bsp/issues/102
56+
if shouldReplyEmpty {
57+
reply(.success(WorkspaceBuildTargetsResponse(targets: [])))
58+
}
59+
let result = try targetStore.fetchTargets()
60+
logger.debug("Found \(result.count, privacy: .public) targets")
61+
logger.logFullObjectInMultipleLogMessages(
62+
level: .debug,
63+
header: "Target list",
64+
result.map { $0.id.uri.stringValue }.joined(separator: ", "),
65+
)
66+
return result
5467
}
55-
logger.debug("Found \(result.count, privacy: .public) targets")
56-
logger.logFullObjectInMultipleLogMessages(
57-
level: .debug,
58-
header: "Target list",
59-
result.map { $0.id.uri.stringValue }.joined(separator: ", "),
60-
)
6168
connection?.finishTask(id: taskId, status: .ok)
62-
reply(.success(WorkspaceBuildTargetsResponse(targets: result)))
63-
// If this is the first time we're responding to buildTargets, send an empty notification.
64-
// This triggers sourcekit-lsp to calculate the file mappings which enables jump-to-definition to work.
65-
// We only need to do this because we're replying to this request incorrectly.
66-
// We should also be able to drop this if we figure out how to make the actual LSP --index-prefix-map flag work.
67-
// See https://github.com/spotify/sourcekit-bazel-bsp/issues/102
68-
if shouldDispatchNotification {
69-
let notification = OnBuildTargetDidChangeNotification(changes: [])
69+
if shouldReplyEmpty {
70+
let targetEvents = result.map { target in
71+
BuildTargetEvent(
72+
target: target.id,
73+
kind: .created,
74+
dataKind: nil,
75+
data: nil
76+
)
77+
}
78+
let notification = OnBuildTargetDidChangeNotification(
79+
changes: targetEvents
80+
)
7081
connection?.send(notification)
82+
} else {
83+
reply(.success(WorkspaceBuildTargetsResponse(targets: result)))
7184
}
7285
} catch {
7386
connection?.finishTask(id: taskId, status: .error)

Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ final class WatchedFileChangeHandler {
7878
// In this case, we keep the lock until the very end of the notification to avoid race conditions
7979
// with how the LSP follows up with this by calling waitForBuildSystemUpdates and buildTargets again.
8080
// Also because we need the targetStore at multiple points of this function.
81-
let invalidatedTargets = targetStore.stateLock.withLockUnchecked {
81+
let invalidatedTargets: [InvalidatedTarget] = targetStore.stateLock.withLockUnchecked {
82+
83+
// If we received this notification before the build graph was calculated, we should stop.
84+
guard targetStore.isInitialized else {
85+
logger.info("Received file changes before the build graph was calculated. Ignoring.")
86+
return []
87+
}
8288

8389
logger.info("Received \(changes.count, privacy: .public) file changes")
8490

Tests/SourceKitBazelBSPTests/Fakes/BazelTargetStoreFake.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ final class BazelTargetStoreFake: BazelTargetStore {
3232
var fetchTargetsError: Error?
3333
var mockSrcToBspURIs: [DocumentURI: [DocumentURI]] = [:]
3434

35+
var isInitialized: Bool {
36+
return fetchTargetsCalled || !mockSrcToBspURIs.isEmpty
37+
}
38+
3539
func fetchTargets() throws -> [BuildTarget] {
3640
fetchTargetsCalled = true
3741
if let error = fetchTargetsError {

0 commit comments

Comments
 (0)