Skip to content

Commit 3e6319c

Browse files
committed
Produce an index log for the client
This allows a user of SourceKit-LSP to inspect the result of background indexing. This allows a user of SourceKit-LSP to inspect the result of background indexing. I think this gives useful insights into what SourceKit-LSP is indexing and why/how it fails, if it fails, also for users of SourceKit-LSP. rdar://127474136 Fixes #1265
1 parent a887981 commit 3e6319c

16 files changed

+203
-28
lines changed

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,10 @@ extension BuildServerBuildSystem: BuildSystem {
289289
return nil
290290
}
291291

292-
public func prepare(targets: [ConfiguredTarget]) async throws {
292+
public func prepare(
293+
targets: [ConfiguredTarget],
294+
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
295+
) async throws {
293296
throw PrepareNotSupportedError()
294297
}
295298

Sources/SKCore/BuildSystem.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ public protocol BuildSystem: AnyObject, Sendable {
158158

159159
/// Prepare the given targets for indexing and semantic functionality. This should build all swift modules of target
160160
/// dependencies.
161-
func prepare(targets: [ConfiguredTarget]) async throws
161+
func prepare(
162+
targets: [ConfiguredTarget],
163+
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
164+
) async throws
162165

163166
/// If the build system has knowledge about the language that this document should be compiled in, return it.
164167
///

Sources/SKCore/BuildSystemManager.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,11 @@ extension BuildSystemManager {
231231
return await buildSystem?.targets(dependingOn: targets)
232232
}
233233

234-
public func prepare(targets: [ConfiguredTarget]) async throws {
235-
try await buildSystem?.prepare(targets: targets)
234+
public func prepare(
235+
targets: [ConfiguredTarget],
236+
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
237+
) async throws {
238+
try await buildSystem?.prepare(targets: targets, indexProcessDidProduceResult: indexProcessDidProduceResult)
236239
}
237240

238241
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {

Sources/SKCore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(SKCore STATIC
1010
Debouncer.swift
1111
FallbackBuildSystem.swift
1212
FileBuildSettings.swift
13+
IndexProcessResult.swift
1314
MainFilesProvider.swift
1415
PathPrefixMapping.swift
1516
SplitShellCommand.swift

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
125125
return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")]
126126
}
127127

128-
public func prepare(targets: [ConfiguredTarget]) async throws {
128+
public func prepare(
129+
targets: [ConfiguredTarget],
130+
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
131+
) async throws {
129132
throw PrepareNotSupportedError()
130133
}
131134

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 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 struct TSCBasic.ProcessResult
14+
15+
/// Result of a process that prepares a target or updates the index store. To be shown in the build log.
16+
///
17+
/// Abstracted over a `ProcessResult` to facilitate build systems that don't spawn a new process to prepare a target but
18+
/// prepare it from a build graph they have loaded in-process.
19+
public struct IndexProcessResult {
20+
/// A human-readable description of what the process was trying to achieve, like `Preparing MyTarget`
21+
public let taskDescription: String
22+
23+
/// The command that was run to produce the result.
24+
public let command: String
25+
26+
/// The output that the process produced.
27+
public let output: String
28+
29+
/// Whether the process failed.
30+
public let failed: Bool
31+
32+
/// The duration it took for the process to execute.
33+
public let duration: Duration
34+
35+
public init(taskDescription: String, command: String, output: String, failed: Bool, duration: Duration) {
36+
self.taskDescription = taskDescription
37+
self.command = command
38+
self.output = output
39+
self.failed = failed
40+
self.duration = duration
41+
}
42+
43+
public init(taskDescription: String, processResult: ProcessResult, start: ContinuousClock.Instant) {
44+
let stdout = (try? String(bytes: processResult.output.get(), encoding: .utf8)) ?? "<failed to decode stdout>"
45+
let stderr = (try? String(bytes: processResult.stderrOutput.get(), encoding: .utf8)) ?? "<failed to decode stderr>"
46+
var outputComponents: [String] = []
47+
if !stdout.isEmpty {
48+
outputComponents.append(
49+
"""
50+
Stdout:
51+
\(stdout)
52+
"""
53+
)
54+
}
55+
if !stderr.isEmpty {
56+
outputComponents.append(
57+
"""
58+
Stderr:
59+
\(stderr)
60+
"""
61+
)
62+
}
63+
self.init(
64+
taskDescription: taskDescription,
65+
command: processResult.arguments.joined(separator: " "),
66+
output: outputComponents.joined(separator: "\n\n"),
67+
failed: processResult.exitStatus != .terminated(code: 0),
68+
duration: start.duration(to: .now)
69+
)
70+
}
71+
}

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,17 +459,23 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
459459
}
460460
}
461461

462-
public func prepare(targets: [ConfiguredTarget]) async throws {
462+
public func prepare(
463+
targets: [ConfiguredTarget],
464+
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
465+
) async throws {
463466
// TODO (indexing): Support preparation of multiple targets at once.
464467
// https://github.com/apple/sourcekit-lsp/issues/1262
465468
for target in targets {
466-
try await prepare(singleTarget: target)
469+
try await prepare(singleTarget: target, indexProcessDidProduceResult: indexProcessDidProduceResult)
467470
}
468471
let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] }
469472
await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init)))
470473
}
471474

472-
private func prepare(singleTarget target: ConfiguredTarget) async throws {
475+
private func prepare(
476+
singleTarget target: ConfiguredTarget,
477+
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
478+
) async throws {
473479
// TODO (indexing): Add a proper 'prepare' job in SwiftPM instead of building the target.
474480
// https://github.com/apple/sourcekit-lsp/issues/1254
475481
guard let toolchain = await toolchainRegistry.default else {
@@ -492,8 +498,16 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
492498
if Task.isCancelled {
493499
return
494500
}
501+
let start = ContinuousClock.now
495502
let process = try Process.launch(arguments: arguments, workingDirectory: nil)
496503
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
504+
indexProcessDidProduceResult(
505+
IndexProcessResult(
506+
taskDescription: "Preparing \(target.targetID) for \(target.runDestinationID)",
507+
processResult: result,
508+
start: start
509+
)
510+
)
497511
switch result.exitStatus.exhaustivelySwitchable {
498512
case .terminated(code: 0):
499513
break

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -243,25 +243,18 @@ public final class TestSourceKitLSPClient: MessageHandler {
243243
return try await nextNotification(ofType: PublishDiagnosticsNotification.self, timeout: timeout)
244244
}
245245

246-
private struct CastError: Error, CustomStringConvertible {
247-
let expectedType: any NotificationType.Type
248-
let actualType: any NotificationType.Type
249-
250-
var description: String { "Expected a \(expectedType) but got '\(actualType)'" }
251-
}
252-
253-
/// Await the next diagnostic notification sent to the client.
254-
///
255-
/// If the next notification is not of the expected type, this methods throws.
246+
/// Waits for the next notification of the given type to be sent to the client. Ignores any notifications that are of
247+
/// a different type.
256248
public func nextNotification<ExpectedNotificationType: NotificationType>(
257249
ofType: ExpectedNotificationType.Type,
258250
timeout: TimeInterval = defaultTimeout
259251
) async throws -> ExpectedNotificationType {
260-
let nextNotification = try await nextNotification(timeout: timeout)
261-
guard let notification = nextNotification as? ExpectedNotificationType else {
262-
throw CastError(expectedType: ExpectedNotificationType.self, actualType: type(of: nextNotification))
252+
while true {
253+
let nextNotification = try await nextNotification(timeout: timeout)
254+
if let notification = nextNotification as? ExpectedNotificationType {
255+
return notification
256+
}
263257
}
264-
return notification
265258
}
266259

267260
/// Handle the next request that is sent to the client with the given handler.

Sources/SemanticIndex/PreparationTaskDescription.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public struct PreparationTaskDescription: IndexTaskDescription {
3737

3838
private let preparationUpToDateStatus: IndexUpToDateStatusManager<ConfiguredTarget>
3939

40+
/// See `SemanticIndexManager.indexProcessDidProduceResult`
41+
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
42+
4043
/// Test hooks that should be called when the preparation task finishes.
4144
private let testHooks: IndexTestHooks
4245

@@ -57,11 +60,13 @@ public struct PreparationTaskDescription: IndexTaskDescription {
5760
targetsToPrepare: [ConfiguredTarget],
5861
buildSystemManager: BuildSystemManager,
5962
preparationUpToDateStatus: IndexUpToDateStatusManager<ConfiguredTarget>,
63+
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
6064
testHooks: IndexTestHooks
6165
) {
6266
self.targetsToPrepare = targetsToPrepare
6367
self.buildSystemManager = buildSystemManager
6468
self.preparationUpToDateStatus = preparationUpToDateStatus
69+
self.indexProcessDidProduceResult = indexProcessDidProduceResult
6570
self.testHooks = testHooks
6671
}
6772

@@ -89,7 +94,10 @@ public struct PreparationTaskDescription: IndexTaskDescription {
8994
)
9095
let startDate = Date()
9196
do {
92-
try await buildSystemManager.prepare(targets: targetsToPrepare)
97+
try await buildSystemManager.prepare(
98+
targets: targetsToPrepare,
99+
indexProcessDidProduceResult: indexProcessDidProduceResult
100+
)
93101
} catch {
94102
logger.error(
95103
"Preparation failed: \(error.forLogging)"

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ public final actor SemanticIndexManager {
108108
/// workspaces.
109109
private let indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>
110110

111+
/// Callback to be called when the process to prepare a target finishes.
112+
///
113+
/// Allows an index log to be displayed to the user that includes the command line invocations of all index-related
114+
/// process launches, as well as their output.
115+
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
116+
111117
/// Called when files are scheduled to be indexed.
112118
///
113119
/// The parameter is the number of files that were scheduled to be indexed.
@@ -150,13 +156,15 @@ public final actor SemanticIndexManager {
150156
buildSystemManager: BuildSystemManager,
151157
testHooks: IndexTestHooks,
152158
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
159+
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
153160
indexTasksWereScheduled: @escaping @Sendable (Int) -> Void,
154161
indexStatusDidChange: @escaping @Sendable () -> Void
155162
) {
156163
self.index = index
157164
self.buildSystemManager = buildSystemManager
158165
self.testHooks = testHooks
159166
self.indexTaskScheduler = indexTaskScheduler
167+
self.indexProcessDidProduceResult = indexProcessDidProduceResult
160168
self.indexTasksWereScheduled = indexTasksWereScheduled
161169
self.indexStatusDidChange = indexStatusDidChange
162170
}
@@ -350,6 +358,7 @@ public final actor SemanticIndexManager {
350358
targetsToPrepare: targetsToPrepare,
351359
buildSystemManager: self.buildSystemManager,
352360
preparationUpToDateStatus: preparationUpToDateStatus,
361+
indexProcessDidProduceResult: indexProcessDidProduceResult,
353362
testHooks: testHooks
354363
)
355364
)
@@ -396,6 +405,7 @@ public final actor SemanticIndexManager {
396405
buildSystemManager: self.buildSystemManager,
397406
index: index,
398407
indexStoreUpToDateStatus: indexStoreUpToDateStatus,
408+
indexProcessDidProduceResult: indexProcessDidProduceResult,
399409
testHooks: testHooks
400410
)
401411
)

Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
9595
/// case we don't need to index it again.
9696
private let index: UncheckedIndex
9797

98+
/// See `SemanticIndexManager.indexProcessDidProduceResult`
99+
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
100+
98101
/// Test hooks that should be called when the index task finishes.
99102
private let testHooks: IndexTestHooks
100103

@@ -116,12 +119,14 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
116119
buildSystemManager: BuildSystemManager,
117120
index: UncheckedIndex,
118121
indexStoreUpToDateStatus: IndexUpToDateStatusManager<DocumentURI>,
122+
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
119123
testHooks: IndexTestHooks
120124
) {
121125
self.filesToIndex = filesToIndex
122126
self.buildSystemManager = buildSystemManager
123127
self.index = index
124128
self.indexStoreUpToDateStatus = indexStoreUpToDateStatus
129+
self.indexProcessDidProduceResult = indexProcessDidProduceResult
125130
self.testHooks = testHooks
126131
}
127132

@@ -304,18 +309,28 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
304309
if Task.isCancelled {
305310
return
306311
}
312+
let start = ContinuousClock.now
307313
let process = try Process.launch(
308314
arguments: processArguments,
309315
workingDirectory: workingDirectory
310316
)
311317
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
318+
319+
indexProcessDidProduceResult(
320+
IndexProcessResult(
321+
taskDescription: "Indexing \(indexFile.pseudoPath)",
322+
processResult: result,
323+
start: start
324+
)
325+
)
326+
312327
switch result.exitStatus.exhaustivelySwitchable {
313328
case .terminated(code: 0):
314329
break
315330
case .terminated(code: let code):
316331
// This most likely happens if there are compilation errors in the source file. This is nothing to worry about.
317-
let stdout = (try? String(bytes: result.output.get(), encoding: .utf8)) ?? "<no stderr>"
318-
let stderr = (try? String(bytes: result.stderrOutput.get(), encoding: .utf8)) ?? "<no stderr>"
332+
let stdout = (try? String(bytes: result.output.get(), encoding: .utf8)) ?? "<failed to decode stdout>"
333+
let stderr = (try? String(bytes: result.stderrOutput.get(), encoding: .utf8)) ?? "<failed to decode stderr>"
319334
// Indexing will frequently fail if the source code is in an invalid state. Thus, log the failure at a low level.
320335
logger.debug(
321336
"""

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ public actor SourceKitLSPServer {
698698
}
699699

700700
/// Send the given notification to the editor.
701-
public func sendNotificationToClient(_ notification: some NotificationType) {
701+
public nonisolated func sendNotificationToClient(_ notification: some NotificationType) {
702702
client.send(notification)
703703
}
704704

@@ -1177,6 +1177,21 @@ private extension LanguageServerProtocol.WorkspaceType {
11771177
}
11781178
}
11791179

1180+
extension SourceKitLSPServer {
1181+
nonisolated func indexTaskDidProduceResult(_ result: IndexProcessResult) {
1182+
self.sendNotificationToClient(
1183+
LogMessageNotification(
1184+
type: result.failed ? .info : .warning,
1185+
message: """
1186+
\(result.taskDescription) finished in \(result.duration)
1187+
\(result.command)
1188+
\(result.output)
1189+
"""
1190+
)
1191+
)
1192+
}
1193+
}
1194+
11801195
// MARK: - Request and notification handling
11811196

11821197
extension SourceKitLSPServer {
@@ -1223,6 +1238,9 @@ extension SourceKitLSPServer {
12231238
compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths,
12241239
indexOptions: self.options.indexOptions,
12251240
indexTaskScheduler: indexTaskScheduler,
1241+
indexProcessDidProduceResult: { [weak self] in
1242+
self?.indexTaskDidProduceResult($0)
1243+
},
12261244
reloadPackageStatusCallback: { [weak self] status in
12271245
guard let self else { return }
12281246
switch status {
@@ -1295,6 +1313,9 @@ extension SourceKitLSPServer {
12951313
index: nil,
12961314
indexDelegate: nil,
12971315
indexTaskScheduler: self.indexTaskScheduler,
1316+
indexProcessDidProduceResult: { [weak self] in
1317+
self?.indexTaskDidProduceResult($0)
1318+
},
12981319
indexTasksWereScheduled: { [weak self] count in
12991320
self?.indexProgressManager.indexTasksWereScheduled(count: count)
13001321
},

0 commit comments

Comments
 (0)