Skip to content

Commit 9fbdeeb

Browse files
committed
Allow alternate build systems to have larger indexing batch sizes
1 parent 1ab9e2e commit 9fbdeeb

File tree

6 files changed

+108
-6
lines changed

6 files changed

+108
-6
lines changed

Sources/BuildServerIntegration/BuildServerManager.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,13 @@ package actor BuildServerManager: QueueBasedMessageHandler {
322322

323323
private var connectionToClient: BuildServerManagerConnectionToClient
324324

325-
/// The build serer adapter that is used to answer build server queries.
325+
/// The build server adapter that is used to answer build server queries.
326326
private var buildServerAdapter: BuildServerAdapter?
327327

328+
/// The kind of underlying build server adapter that is being managed.
329+
/// `nil` if the `BuildServerManager` does not have an underlying build system.
330+
private var kind: BuildServerSpec.Kind?
331+
328332
/// The build server adapter after initialization finishes. When sending messages to the BSP server, this should be
329333
/// preferred over `buildServerAdapter` because no messages must be sent to the build server before initialization
330334
/// finishes.
@@ -451,6 +455,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
451455
self.toolchainRegistry = toolchainRegistry
452456
self.options = options
453457
self.connectionToClient = connectionToClient
458+
self.kind = buildServerSpec?.kind
454459
self.configPath = buildServerSpec?.configPath
455460
self.buildServerAdapter = await buildServerSpec?.createBuildServerAdapter(
456461
toolchainRegistry: toolchainRegistry,

Sources/BuildServerProtocol/Messages/InitializeBuildRequest.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
278278
/// Whether the server implements the `textDocument/sourceKitOptions` request.
279279
public var sourceKitOptionsProvider: Bool?
280280

281+
/// The number of targets to prepare concurrently, when an index request is scheduled.
282+
public var indexTaskBatchSize: Int?
283+
281284
/// The files to watch for changes.
282285
public var watchers: [FileSystemWatcher]?
283286

@@ -286,12 +289,14 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
286289
public init(
287290
indexDatabasePath: String? = nil,
288291
indexStorePath: String? = nil,
292+
indexTaskBatchSize: Int? = nil,
289293
watchers: [FileSystemWatcher]? = nil,
290294
prepareProvider: Bool? = nil,
291295
sourceKitOptionsProvider: Bool? = nil
292296
) {
293297
self.indexDatabasePath = indexDatabasePath
294298
self.indexStorePath = indexStorePath
299+
self.indexTaskBatchSize = indexTaskBatchSize
295300
self.watchers = watchers
296301
self.prepareProvider = prepareProvider
297302
self.sourceKitOptionsProvider = sourceKitOptionsProvider
@@ -300,13 +305,15 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
300305
public init(
301306
indexDatabasePath: String? = nil,
302307
indexStorePath: String? = nil,
308+
indexTaskBatchSize: Int? = nil,
303309
outputPathsProvider: Bool? = nil,
304310
prepareProvider: Bool? = nil,
305311
sourceKitOptionsProvider: Bool? = nil,
306312
watchers: [FileSystemWatcher]? = nil
307313
) {
308314
self.indexDatabasePath = indexDatabasePath
309315
self.indexStorePath = indexStorePath
316+
self.indexTaskBatchSize = indexTaskBatchSize
310317
self.outputPathsProvider = outputPathsProvider
311318
self.prepareProvider = prepareProvider
312319
self.sourceKitOptionsProvider = sourceKitOptionsProvider
@@ -320,6 +327,9 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
320327
if case .string(let indexStorePath) = dictionary[CodingKeys.indexStorePath.stringValue] {
321328
self.indexStorePath = indexStorePath
322329
}
330+
if case .int(let indexTaskBatchSize) = dictionary[CodingKeys.indexTaskBatchSize.stringValue] {
331+
self.indexTaskBatchSize = indexTaskBatchSize
332+
}
323333
if case .bool(let outputPathsProvider) = dictionary[CodingKeys.outputPathsProvider.stringValue] {
324334
self.outputPathsProvider = outputPathsProvider
325335
}
@@ -342,6 +352,9 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
342352
if let indexStorePath {
343353
result[CodingKeys.indexStorePath.stringValue] = .string(indexStorePath)
344354
}
355+
if let indexTaskBatchSize {
356+
result[CodingKeys.indexTaskBatchSize.stringValue] = .int(indexTaskBatchSize)
357+
}
345358
if let outputPathsProvider {
346359
result[CodingKeys.outputPathsProvider.stringValue] = .bool(outputPathsProvider)
347360
}

Sources/SKTestSupport/CustomBuildServerTestProject.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,14 @@ package extension CustomBuildServer {
175175

176176
func initializationResponseSupportingBackgroundIndexing(
177177
projectRoot: URL,
178-
outputPathsProvider: Bool
178+
outputPathsProvider: Bool,
179+
indexTaskBatchSize: Int? = nil
179180
) throws -> InitializeBuildResponse {
180181
return initializationResponse(
181182
initializeData: SourceKitInitializeBuildResponseData(
182183
indexDatabasePath: try projectRoot.appendingPathComponent("index-db").filePath,
183184
indexStorePath: try projectRoot.appendingPathComponent("index-store").filePath,
185+
indexTaskBatchSize: indexTaskBatchSize,
184186
outputPathsProvider: outputPathsProvider,
185187
prepareProvider: true,
186188
sourceKitOptionsProvider: true

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ package final actor SemanticIndexManager {
222222
/// The parameter is the number of files that were scheduled to be indexed.
223223
private let indexTasksWereScheduled: @Sendable (_ numberOfFileScheduled: Int) -> Void
224224

225+
/// The number of targets to prepare concurrently, whenever a index request is scheduled.
226+
private let indexTaskBatchSize: Int
227+
225228
/// Callback that is called when `progressStatus` might have changed.
226229
private let indexProgressStatusDidChange: @Sendable () -> Void
227230

@@ -261,6 +264,7 @@ package final actor SemanticIndexManager {
261264
updateIndexStoreTimeout: Duration,
262265
hooks: IndexHooks,
263266
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
267+
indexTaskBatchSize: Int,
264268
logMessageToIndexLog:
265269
@escaping @Sendable (
266270
_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind
@@ -273,6 +277,7 @@ package final actor SemanticIndexManager {
273277
self.updateIndexStoreTimeout = updateIndexStoreTimeout
274278
self.hooks = hooks
275279
self.indexTaskScheduler = indexTaskScheduler
280+
self.indexTaskBatchSize = indexTaskBatchSize
276281
self.logMessageToIndexLog = logMessageToIndexLog
277282
self.indexTasksWereScheduled = indexTasksWereScheduled
278283
self.indexProgressStatusDidChange = indexProgressStatusDidChange
@@ -877,10 +882,7 @@ package final actor SemanticIndexManager {
877882

878883
var indexTasks: [Task<Void, Never>] = []
879884

880-
// TODO: When we can index multiple targets concurrently in SwiftPM, increase the batch size to half the
881-
// processor count, so we can get parallelism during preparation.
882-
// (https://github.com/swiftlang/sourcekit-lsp/issues/1262)
883-
for targetsBatch in sortedTargets.partition(intoBatchesOfSize: 1) {
885+
for targetsBatch in sortedTargets.partition(intoBatchesOfSize: indexTaskBatchSize) {
884886
let preparationTaskID = UUID()
885887
let filesToIndex = targetsBatch.flatMap({ filesByTarget[$0]! })
886888

Sources/SourceKitLSP/Workspace.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,17 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
162162
if options.backgroundIndexingOrDefault, let uncheckedIndex,
163163
await buildServerManager.initializationData?.prepareProvider ?? false
164164
{
165+
// TODO: When we can index multiple targets concurrently in SwiftPM, we may want to default
166+
// to something else other than 1.
167+
// (https://github.com/swiftlang/sourcekit-lsp/issues/1262)
168+
let batchSize = await buildServerManager.initializationData?.indexTaskBatchSize ?? 1
165169
self.semanticIndexManager = SemanticIndexManager(
166170
index: uncheckedIndex,
167171
buildServerManager: buildServerManager,
168172
updateIndexStoreTimeout: options.indexOrDefault.updateIndexStoreTimeoutOrDefault,
169173
hooks: hooks.indexHooks,
170174
indexTaskScheduler: indexTaskScheduler,
175+
indexTaskBatchSize: batchSize,
171176
logMessageToIndexLog: { [weak sourceKitLSPServer] in
172177
sourceKitLSPServer?.logMessageToIndexLog(message: $0, type: $1, structure: $2)
173178
},

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2624,6 +2624,81 @@ final class BackgroundIndexingTests: XCTestCase {
26242624
let symbols = try await project.testClient.send(WorkspaceSymbolsRequest(query: "myTestFu"))
26252625
XCTAssertEqual(symbols?.count, 1)
26262626
}
2627+
2628+
func testBuildServerUsesCustomTaskBatchSize() async throws {
2629+
final class BuildServer: CustomBuildServer {
2630+
let inProgressRequestsTracker = CustomBuildServerInProgressRequestTracker()
2631+
private let projectRoot: URL
2632+
private var testFileURL: URL { projectRoot.appendingPathComponent("test.swift").standardized }
2633+
2634+
required init(projectRoot: URL, connectionToSourceKitLSP: any LanguageServerProtocol.Connection) {
2635+
self.projectRoot = projectRoot
2636+
}
2637+
2638+
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
2639+
return try initializationResponseSupportingBackgroundIndexing(
2640+
projectRoot: projectRoot,
2641+
outputPathsProvider: false,
2642+
indexTaskBatchSize: 3
2643+
)
2644+
}
2645+
2646+
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
2647+
var dummyTargets = [BuildTargetIdentifier]()
2648+
for i in 0..<10 {
2649+
dummyTargets.append(BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-\(i)")))
2650+
}
2651+
return BuildTargetSourcesResponse(items: dummyTargets.map {
2652+
SourcesItem(target: $0, sources: [SourceItem(uri: URI(testFileURL), kind: .file, generated: false)])
2653+
})
2654+
}
2655+
2656+
func textDocumentSourceKitOptionsRequest(
2657+
_ request: TextDocumentSourceKitOptionsRequest
2658+
) async throws -> TextDocumentSourceKitOptionsResponse? {
2659+
return TextDocumentSourceKitOptionsResponse(compilerArguments: [request.textDocument.uri.pseudoPath])
2660+
}
2661+
}
2662+
2663+
let preparationTaskSemaphore = WrappedSemaphore(name: "Received a preparation task")
2664+
let preparationTasks = ThreadSafeBox<[PreparationTaskDescription]>(initialValue: [])
2665+
let project = try await CustomBuildServerTestProject(
2666+
files: [
2667+
"test.swift": """
2668+
func testFunction() {}
2669+
"""
2670+
],
2671+
buildServer: BuildServer.self,
2672+
hooks: Hooks(
2673+
indexHooks: IndexHooks(
2674+
preparationTaskDidStart: { task in
2675+
preparationTasks.withLock { preparationTasks in
2676+
// Ignore everything after the 4th batch to avoid flakiness.
2677+
if preparationTasks.count < 4 {
2678+
preparationTasks.append(task)
2679+
}
2680+
if preparationTasks.count == 4 {
2681+
preparationTaskSemaphore.signal()
2682+
}
2683+
}
2684+
}
2685+
)
2686+
),
2687+
enableBackgroundIndexing: true
2688+
)
2689+
2690+
_ = try await project.testClient.send(SynchronizeRequest(index: true))
2691+
2692+
preparationTaskSemaphore.waitOrXCTFail()
2693+
2694+
// The test project has 10 targets, and we should have received them in batches of 3.
2695+
XCTAssertEqual(preparationTasks.value.count, 4)
2696+
let preparedTargetBatches = preparationTasks.value.map(\.targetsToPrepare).sorted { $0.count > $1.count }
2697+
XCTAssertEqual(preparedTargetBatches[0].count, 3)
2698+
XCTAssertEqual(preparedTargetBatches[1].count, 3)
2699+
XCTAssertEqual(preparedTargetBatches[2].count, 3)
2700+
XCTAssertEqual(preparedTargetBatches[3].count, 1)
2701+
}
26272702
}
26282703

26292704
extension HoverResponseContents {

0 commit comments

Comments
 (0)