Skip to content

Commit 8da0ff7

Browse files
committed
Allow custom batch size, write test for batch sizes
1 parent b2526c0 commit 8da0ff7

File tree

8 files changed

+109
-99
lines changed

8 files changed

+109
-99
lines changed

Contributor Documentation/BSP Extensions.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ export interface SourceKitInitializeBuildResponseData {
4141
}
4242

4343
export interface MultiTargetPreparationSupport {
44-
/** Whether the build server can prepare multiple targets in parallel. If this value is omitted, it is assumed to be `true`. */
44+
/** Whether the build server can prepare multiple targets in parallel. */
4545
supported: bool;
46+
47+
/** The number of targets to prepare in parallel.
48+
* If not provided, SourceKit-LSP will calculate an appropriate value based on the environment. */
49+
batchSize?: int;
4650
}
4751
```
4852

Sources/BuildServerIntegration/FixedCompilationDatabaseBuildServer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ package actor FixedCompilationDatabaseBuildServer: BuiltInBuildServer {
5959
indexStorePath?.deletingLastPathComponent().appendingPathComponent("IndexDatabase")
6060
}
6161

62-
package nonisolated var supportsMultiTargetPreparation: Bool { true }
62+
package nonisolated var supportsMultiTargetPreparation: Bool { false }
6363

6464
package nonisolated var supportsPreparationAndOutputPaths: Bool { false }
6565

Sources/BuildServerIntegration/JSONCompilationDatabaseBuildServer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ package actor JSONCompilationDatabaseBuildServer: BuiltInBuildServer {
9696
indexStorePath?.deletingLastPathComponent().appendingPathComponent("IndexDatabase")
9797
}
9898

99-
package nonisolated var supportsMultiTargetPreparation: Bool { true }
99+
package nonisolated var supportsMultiTargetPreparation: Bool { false }
100100

101101
package nonisolated var supportsPreparationAndOutputPaths: Bool { false }
102102

Sources/BuildServerIntegration/LegacyBuildServer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ actor LegacyBuildServer: MessageHandler, BuiltInBuildServer {
138138
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
139139
}
140140

141-
package nonisolated var supportsMultiTargetPreparation: Bool { true }
141+
package nonisolated var supportsMultiTargetPreparation: Bool { false }
142142

143143
package nonisolated var supportsPreparationAndOutputPaths: Bool { false }
144144

Sources/BuildServerProtocol/Messages/InitializeBuildRequest.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,24 +372,35 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
372372
}
373373

374374
public struct MultiTargetPreparationSupport: LSPAnyCodable, Codable, Sendable {
375-
/// Whether the build server can prepare multiple targets in parallel. If this value is omitted, it is assumed to be `true`.
375+
/// Whether the build server can prepare multiple targets in parallel.
376376
public var supported: Bool?
377377

378-
public init(supported: Bool? = nil) {
378+
/// The number of targets to prepare in parallel.
379+
/// If not provided, SourceKit-LSP will calculate an appropriate value based on the environment.
380+
public var batchSize: Int?
381+
382+
public init(supported: Bool? = nil, batchSize: Int? = nil) {
379383
self.supported = supported
384+
self.batchSize = batchSize
380385
}
381386

382387
public init?(fromLSPDictionary dictionary: [String: LanguageServerProtocol.LSPAny]) {
383388
if case .bool(let supported) = dictionary[CodingKeys.supported.stringValue] {
384389
self.supported = supported
385390
}
391+
if case .int(let batchSize) = dictionary[CodingKeys.batchSize.stringValue] {
392+
self.batchSize = batchSize
393+
}
386394
}
387395

388396
public func encodeToLSPAny() -> LanguageServerProtocol.LSPAny {
389397
var result: [String: LSPAny] = [:]
390398
if let supported {
391399
result[CodingKeys.supported.stringValue] = .bool(supported)
392400
}
401+
if let batchSize {
402+
result[CodingKeys.batchSize.stringValue] = .int(batchSize)
403+
}
393404
return .dictionary(result)
394405
}
395406
}

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+
multiTargetPreparation: MultiTargetPreparationSupport? = 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+
multiTargetPreparation: multiTargetPreparation,
184186
outputPathsProvider: outputPathsProvider,
185187
prepareProvider: true,
186188
sourceKitOptionsProvider: true

Sources/SourceKitLSP/Workspace.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,15 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
162162
if options.backgroundIndexingOrDefault, let uncheckedIndex,
163163
await buildServerManager.initializationData?.prepareProvider ?? false
164164
{
165-
let shouldIndexInParallel = await buildServerManager.initializationData?.multiTargetPreparation?.supported ?? true
165+
let shouldIndexInParallel = await buildServerManager.initializationData?.multiTargetPreparation?.supported ?? false
166166
let batchSize: Int
167167
if shouldIndexInParallel {
168-
let processorCount = ProcessInfo.processInfo.activeProcessorCount
169-
batchSize = max(1, processorCount * 5)
168+
if let customBatchSize = await buildServerManager.initializationData?.multiTargetPreparation?.batchSize {
169+
batchSize = customBatchSize
170+
} else {
171+
let processorCount = ProcessInfo.processInfo.activeProcessorCount
172+
batchSize = max(1, processorCount / 2)
173+
}
170174
} else {
171175
batchSize = 1
172176
}

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 78 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2624,96 +2624,85 @@ final class BackgroundIndexingTests: XCTestCase {
26242624
let symbols = try await project.testClient.send(WorkspaceSymbolsRequest(query: "myTestFu"))
26252625
XCTAssertEqual(symbols?.count, 1)
26262626
}
2627-
}
26282627

2629-
// WIP, not ready for review
2630-
// func testBuildServerUsesCustomTaskBatchSize() async throws {
2631-
// final class BuildServer: CustomBuildServer {
2632-
// let inProgressRequestsTracker = CustomBuildServerInProgressRequestTracker()
2633-
// private let projectRoot: URL
2634-
// private var testFileURL: URL { projectRoot.appendingPathComponent("test.swift").standardized }
2635-
2636-
// nonisolated(unsafe) var preparedTargetBatches = [[BuildTargetIdentifier]]()
2637-
2638-
// required init(projectRoot: URL, connectionToSourceKitLSP: any LanguageServerProtocol.Connection) {
2639-
// self.projectRoot = projectRoot
2640-
// }
2641-
2642-
// func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
2643-
// return try initializationResponseSupportingBackgroundIndexing(
2644-
// projectRoot: projectRoot,
2645-
// outputPathsProvider: false,
2646-
// )
2647-
// }
2648-
2649-
// func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
2650-
// var dummyTargets = [BuildTargetIdentifier]()
2651-
// for i in 0..<10 {
2652-
// dummyTargets.append(BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-\(i)")))
2653-
// }
2654-
// return BuildTargetSourcesResponse(items: dummyTargets.map {
2655-
// SourcesItem(target: $0, sources: [SourceItem(uri: URI(testFileURL), kind: .file, generated: false)])
2656-
// })
2657-
// }
2658-
2659-
// func textDocumentSourceKitOptionsRequest(
2660-
// _ request: TextDocumentSourceKitOptionsRequest
2661-
// ) async throws -> TextDocumentSourceKitOptionsResponse? {
2662-
// return TextDocumentSourceKitOptionsResponse(compilerArguments: [request.textDocument.uri.pseudoPath])
2663-
// }
2664-
2665-
// func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse {
2666-
// preparedTargetBatches.append(request.targets.sorted { $0.uri.stringValue < $1.uri.stringValue })
2667-
// return VoidResponse()
2668-
// }
2669-
// }
2670-
2671-
// let project = try await CustomBuildServerTestProject(
2672-
// files: [
2673-
// "test.swift": """
2674-
// func testFunction() {}
2675-
// """
2676-
// ],
2677-
// buildServer: BuildServer.self,
2678-
// enableBackgroundIndexing: true,
2679-
// )
2680-
2681-
// // Wait for indexing to finish without elevating the priority
2682-
// // Otherwise, task re-scheduling would cause the test to become flaky
2683-
// let semaphore = WrappedSemaphore(name: "Indexing finished")
2684-
// let testClient = project.testClient
2685-
// Task(priority: .low) {
2686-
// await assertNoThrow {
2687-
// try await testClient.send(SynchronizeRequest(index: true))
2688-
// }
2689-
// semaphore.signal()
2690-
// }
2691-
// try semaphore.waitOrThrow()
2692-
2693-
// let buildServer = try project.buildServer()
2694-
// let preparedBatches = buildServer.preparedTargetBatches.sorted { $0[0].uri.stringValue < $1[0].uri.stringValue }
2695-
// XCTAssertEqual(preparedBatches, [
2696-
// [
2697-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-0")),
2698-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-1")),
2699-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-2")),
2700-
// ],
2701-
// [
2702-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-3")),
2703-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-4")),
2704-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-5")),
2705-
// ],
2706-
// [
2707-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-6")),
2708-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-7")),
2709-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-8")),
2710-
// ],
2711-
// [
2712-
// BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-9")),
2713-
// ]
2714-
// ])
2715-
// }
2716-
// }
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+
nonisolated(unsafe) var preparedTargetBatches = [[BuildTargetIdentifier]]()
2635+
2636+
required init(projectRoot: URL, connectionToSourceKitLSP: any LanguageServerProtocol.Connection) {
2637+
self.projectRoot = projectRoot
2638+
}
2639+
2640+
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
2641+
return try initializationResponseSupportingBackgroundIndexing(
2642+
projectRoot: projectRoot,
2643+
outputPathsProvider: false,
2644+
multiTargetPreparation: MultiTargetPreparationSupport(supported: true, batchSize: 3)
2645+
)
2646+
}
2647+
2648+
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
2649+
var dummyTargets = [BuildTargetIdentifier]()
2650+
for i in 0..<10 {
2651+
dummyTargets.append(BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-\(i)")))
2652+
}
2653+
return BuildTargetSourcesResponse(items: dummyTargets.map {
2654+
SourcesItem(target: $0, sources: [SourceItem(uri: URI(testFileURL), kind: .file, generated: false)])
2655+
})
2656+
}
2657+
2658+
func textDocumentSourceKitOptionsRequest(
2659+
_ request: TextDocumentSourceKitOptionsRequest
2660+
) async throws -> TextDocumentSourceKitOptionsResponse? {
2661+
return TextDocumentSourceKitOptionsResponse(compilerArguments: [request.textDocument.uri.pseudoPath])
2662+
}
2663+
2664+
func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse {
2665+
preparedTargetBatches.append(request.targets.sorted { $0.uri.stringValue < $1.uri.stringValue })
2666+
return VoidResponse()
2667+
}
2668+
}
2669+
2670+
let project = try await CustomBuildServerTestProject(
2671+
files: [
2672+
"test.swift": """
2673+
func testFunction() {}
2674+
"""
2675+
],
2676+
buildServer: BuildServer.self,
2677+
enableBackgroundIndexing: true,
2678+
)
2679+
2680+
try await project.testClient.send(SynchronizeRequest(index: true))
2681+
2682+
let buildServer = try project.buildServer()
2683+
let preparedBatches = buildServer.preparedTargetBatches.sorted { $0[0].uri.stringValue < $1[0].uri.stringValue }
2684+
XCTAssertEqual(preparedBatches, [
2685+
[
2686+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-0")),
2687+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-1")),
2688+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-2")),
2689+
],
2690+
[
2691+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-3")),
2692+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-4")),
2693+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-5")),
2694+
],
2695+
[
2696+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-6")),
2697+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-7")),
2698+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-8")),
2699+
],
2700+
[
2701+
BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy-9")),
2702+
]
2703+
])
2704+
}
2705+
}
27172706

27182707
extension HoverResponseContents {
27192708
var markupContent: MarkupContent? {

0 commit comments

Comments
 (0)