Skip to content

Commit 8480b4e

Browse files
committed
Instead of sending a message to the index log when an indexing task finishes, stream results as they come in
This also means that you can use the index log to view which tasks are currently being executed. Since we only have a single log stream we can write to, I decided to prefix every line in the index log with two colored emojis that an easy visual association of every log line to the task that generated them.
1 parent d439d12 commit 8480b4e

20 files changed

+218
-95
lines changed

Sources/CAtomics/include/CAtomics.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ typedef struct {
7070
} AtomicUInt32;
7171

7272
__attribute__((swift_name("AtomicUInt32.init(initialValue:)")))
73-
static inline AtomicUInt32 atomic_int_create(uint8_t initialValue) {
73+
static inline AtomicUInt32 atomic_int_create(uint32_t initialValue) {
7474
AtomicUInt32 atomic;
7575
atomic.value = initialValue;
7676
return atomic;

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ extension BuildServerBuildSystem: BuildSystem {
293293

294294
public func prepare(
295295
targets: [ConfiguredTarget],
296-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
296+
logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
297297
) async throws {
298298
throw PrepareNotSupportedError()
299299
}

Sources/SKCore/BuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public protocol BuildSystem: AnyObject, Sendable {
169169
/// dependencies.
170170
func prepare(
171171
targets: [ConfiguredTarget],
172-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
172+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
173173
) async throws
174174

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

Sources/SKCore/BuildSystemManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ extension BuildSystemManager {
237237

238238
public func prepare(
239239
targets: [ConfiguredTarget],
240-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
240+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
241241
) async throws {
242-
try await buildSystem?.prepare(targets: targets, indexProcessDidProduceResult: indexProcessDidProduceResult)
242+
try await buildSystem?.prepare(targets: targets, logMessageToIndexLog: logMessageToIndexLog)
243243
}
244244

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

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
129129

130130
public func prepare(
131131
targets: [ConfiguredTarget],
132-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
132+
logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
133133
) async throws {
134134
throw PrepareNotSupportedError()
135135
}

Sources/SKCore/IndexProcessResult.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,39 @@
1212

1313
import struct TSCBasic.ProcessResult
1414

15+
/// The ID of a preparation or update indexstore task. This allows us to log messages from multiple concurrently running
16+
/// indexing tasks to the index log while still being able to differentiate them.
17+
public enum IndexTaskID: Sendable {
18+
case preparation(id: UInt32)
19+
case updateIndexStore(id: UInt32)
20+
21+
private static func numberToEmojis(_ number: Int, numEmojis: Int) -> String {
22+
let emojis = ["🟥", "🟩", "🟦", "🟧", "⬜️", "🟪", "⬛️", "🟨", "🟫"]
23+
var number = abs(number)
24+
var result = ""
25+
for _ in 0..<numEmojis {
26+
let (quotient, remainder) = number.quotientAndRemainder(dividingBy: emojis.count)
27+
result += emojis[remainder]
28+
number = quotient
29+
}
30+
return result
31+
}
32+
33+
/// Returns a two-character emoji string that allows easy differentiation between different task IDs.
34+
///
35+
/// This marker is prepended to every line in the index log.
36+
public var emojiRepresentation: String {
37+
// Multiply by 2 and optionally add 1 to make sure preparation and update index store have distinct IDs.
38+
// Run .hashValue to make sure we semi-randomly pick new emoji markers for new tasks
39+
switch self {
40+
case .preparation(id: let id):
41+
return Self.numberToEmojis((id * 2).hashValue, numEmojis: 2)
42+
case .updateIndexStore(id: let id):
43+
return Self.numberToEmojis((id * 2 + 1).hashValue, numEmojis: 2)
44+
}
45+
}
46+
}
47+
1548
/// Result of a process that prepares a target or updates the index store. To be shown in the build log.
1649
///
1750
/// Abstracted over a `ProcessResult` to facilitate build systems that don't spawn a new process to prepare a target but

Sources/SKSupport/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_library(SKSupport STATIC
1111
DocumentURI+CustomLogStringConvertible.swift
1212
FileSystem.swift
1313
LineTable.swift
14+
PipeAsStringHandler.swift
1415
Process+LaunchWithWorkingDirectoryIfPossible.swift
1516
Process+WaitUntilExitWithCancellation.swift
1617
Random.swift
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 Foundation
14+
15+
/// Gathers data from a stdout or stderr pipe. When it has accumulated a full line, calls the handler to handle the
16+
/// string.
17+
public actor PipeAsStringHandler {
18+
/// Queue on which all data from the pipe will be handled. This allows us to have a
19+
/// nonisolated `handle` function but ensure that data gets processed in order.
20+
private let queue = AsyncQueue<Serial>()
21+
private var buffer = Data()
22+
23+
/// The closure that actually handles
24+
private let handler: @Sendable (String) -> Void
25+
26+
public init(handler: @escaping @Sendable (String) -> Void) {
27+
self.handler = handler
28+
}
29+
30+
private func handleDataFromPipeImpl(_ newData: Data) {
31+
self.buffer += newData
32+
while let newlineIndex = self.buffer.firstIndex(of: UInt8(ascii: "\n")) {
33+
// Output a separate log message for every line in clangd's stderr.
34+
// The reason why we don't output multiple lines in a single log message is that
35+
// a) os_log truncates log messages at about 1000 bytes. The assumption is that a single line is usually less
36+
// than 1000 bytes long but if we merge multiple lines into one message, we might easily exceed this limit.
37+
// b) It might be confusing why sometimes a single log message contains one line while sometimes it contains
38+
// multiple.
39+
handler(String(data: self.buffer[...newlineIndex], encoding: .utf8) ?? "<invalid UTF-8>")
40+
buffer = buffer[buffer.index(after: newlineIndex)...]
41+
}
42+
}
43+
44+
public nonisolated func handleDataFromPipe(_ newData: Data) {
45+
queue.async {
46+
await self.handleDataFromPipeImpl(newData)
47+
}
48+
}
49+
}

Sources/SKSupport/Process+LaunchWithWorkingDirectoryIfPossible.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extension Process {
2626
arguments: [String],
2727
environmentBlock: ProcessEnvironmentBlock = ProcessEnv.block,
2828
workingDirectory: AbsolutePath?,
29+
outputRedirection: OutputRedirection = .collect,
2930
startNewProcessGroup: Bool = true,
3031
loggingHandler: LoggingHandler? = .none
3132
) throws -> Process {
@@ -35,13 +36,15 @@ extension Process {
3536
arguments: arguments,
3637
environmentBlock: environmentBlock,
3738
workingDirectory: workingDirectory,
39+
outputRedirection: outputRedirection,
3840
startNewProcessGroup: startNewProcessGroup,
3941
loggingHandler: loggingHandler
4042
)
4143
} else {
4244
Process(
4345
arguments: arguments,
4446
environmentBlock: environmentBlock,
47+
outputRedirection: outputRedirection,
4548
startNewProcessGroup: startNewProcessGroup,
4649
loggingHandler: loggingHandler
4750
)
@@ -57,6 +60,7 @@ extension Process {
5760
arguments: arguments,
5861
environmentBlock: environmentBlock,
5962
workingDirectory: nil,
63+
outputRedirection: outputRedirection,
6064
startNewProcessGroup: startNewProcessGroup,
6165
loggingHandler: loggingHandler
6266
)

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import Basics
1414
import Build
1515
import BuildServerProtocol
16+
import CAtomics
1617
import Dispatch
18+
import Foundation
1719
import LSPLogging
1820
import LanguageServerProtocol
1921
import PackageGraph
@@ -71,6 +73,9 @@ fileprivate extension ConfiguredTarget {
7173
static let forPackageManifest = ConfiguredTarget(targetID: "", runDestinationID: "")
7274
}
7375

76+
/// `nonisolated(unsafe)` is fine because `preparationTaskID` is atomic.
77+
fileprivate nonisolated(unsafe) var preparationTaskID: AtomicUInt32 = AtomicUInt32(initialValue: 0)
78+
7479
/// Swift Package Manager build system and workspace support.
7580
///
7681
/// This class implements the `BuildSystem` interface to provide the build settings for a Swift
@@ -493,20 +498,20 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
493498

494499
public func prepare(
495500
targets: [ConfiguredTarget],
496-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
501+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
497502
) async throws {
498503
// TODO (indexing): Support preparation of multiple targets at once.
499504
// https://github.com/apple/sourcekit-lsp/issues/1262
500505
for target in targets {
501-
try await prepare(singleTarget: target, indexProcessDidProduceResult: indexProcessDidProduceResult)
506+
try await prepare(singleTarget: target, logMessageToIndexLog: logMessageToIndexLog)
502507
}
503508
let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] }
504509
await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init)))
505510
}
506511

507512
private func prepare(
508513
singleTarget target: ConfiguredTarget,
509-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
514+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
510515
) async throws {
511516
// TODO (indexing): Add a proper 'prepare' job in SwiftPM instead of building the target.
512517
// https://github.com/apple/sourcekit-lsp/issues/1254
@@ -531,15 +536,28 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
531536
return
532537
}
533538
let start = ContinuousClock.now
534-
let process = try Process.launch(arguments: arguments, workingDirectory: nil)
535-
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
536-
indexProcessDidProduceResult(
537-
IndexProcessResult(
538-
taskDescription: "Preparing \(target.targetID) for \(target.runDestinationID)",
539-
processResult: result,
540-
start: start
539+
540+
let logID = IndexTaskID.preparation(id: preparationTaskID.fetchAndIncrement())
541+
logMessageToIndexLog(
542+
logID,
543+
"""
544+
Preparing \(target.targetID) for \(target.runDestinationID)
545+
\(arguments.joined(separator: " "))
546+
"""
547+
)
548+
let stdoutHandler = PipeAsStringHandler { logMessageToIndexLog(logID, $0) }
549+
let stderrHandler = PipeAsStringHandler { logMessageToIndexLog(logID, $0) }
550+
551+
let process = try Process.launch(
552+
arguments: arguments,
553+
workingDirectory: nil,
554+
outputRedirection: .stream(
555+
stdout: { stdoutHandler.handleDataFromPipe(Data($0)) },
556+
stderr: { stderrHandler.handleDataFromPipe(Data($0)) }
541557
)
542558
)
559+
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
560+
logMessageToIndexLog(logID, "Finished in \(start.duration(to: .now))")
543561
switch result.exitStatus.exhaustivelySwitchable {
544562
case .terminated(code: 0):
545563
break

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,12 @@ public final class TestSourceKitLSPClient: MessageHandler {
265265
/// Ignores any notifications that are of a different type or that don't satisfy the predicate.
266266
public func nextNotification<ExpectedNotificationType: NotificationType>(
267267
ofType: ExpectedNotificationType.Type,
268-
satisfying predicate: (ExpectedNotificationType) -> Bool = { _ in true },
268+
satisfying predicate: (ExpectedNotificationType) throws -> Bool = { _ in true },
269269
timeout: TimeInterval = defaultTimeout
270270
) async throws -> ExpectedNotificationType {
271271
while true {
272272
let nextNotification = try await nextNotification(timeout: timeout)
273-
if let notification = nextNotification as? ExpectedNotificationType, predicate(notification) {
273+
if let notification = nextNotification as? ExpectedNotificationType, try predicate(notification) {
274274
return notification
275275
}
276276
}

Sources/SemanticIndex/PreparationTaskDescription.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public struct PreparationTaskDescription: IndexTaskDescription {
3737

3838
private let preparationUpToDateTracker: UpToDateTracker<ConfiguredTarget>
3939

40-
/// See `SemanticIndexManager.indexProcessDidProduceResult`
41-
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
40+
/// See `SemanticIndexManager.logMessageToIndexLog`.
41+
private let logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
4242

4343
/// Test hooks that should be called when the preparation task finishes.
4444
private let testHooks: IndexTestHooks
@@ -60,13 +60,13 @@ public struct PreparationTaskDescription: IndexTaskDescription {
6060
targetsToPrepare: [ConfiguredTarget],
6161
buildSystemManager: BuildSystemManager,
6262
preparationUpToDateTracker: UpToDateTracker<ConfiguredTarget>,
63-
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
63+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
6464
testHooks: IndexTestHooks
6565
) {
6666
self.targetsToPrepare = targetsToPrepare
6767
self.buildSystemManager = buildSystemManager
6868
self.preparationUpToDateTracker = preparationUpToDateTracker
69-
self.indexProcessDidProduceResult = indexProcessDidProduceResult
69+
self.logMessageToIndexLog = logMessageToIndexLog
7070
self.testHooks = testHooks
7171
}
7272

@@ -105,7 +105,7 @@ public struct PreparationTaskDescription: IndexTaskDescription {
105105
do {
106106
try await buildSystemManager.prepare(
107107
targets: targetsToPrepare,
108-
indexProcessDidProduceResult: indexProcessDidProduceResult
108+
logMessageToIndexLog: logMessageToIndexLog
109109
)
110110
} catch {
111111
logger.error(

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,8 @@ public final actor SemanticIndexManager {
143143
/// workspaces.
144144
private let indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>
145145

146-
/// Callback to be called when the process to prepare a target finishes.
147-
///
148-
/// Allows an index log to be displayed to the user that includes the command line invocations of all index-related
149-
/// process launches, as well as their output.
150-
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
146+
/// Callback that is called when an indexing task produces output it wants to log to the index log.
147+
private let logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
151148

152149
/// Called when files are scheduled to be indexed.
153150
///
@@ -189,15 +186,15 @@ public final actor SemanticIndexManager {
189186
buildSystemManager: BuildSystemManager,
190187
testHooks: IndexTestHooks,
191188
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
192-
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
189+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
193190
indexTasksWereScheduled: @escaping @Sendable (Int) -> Void,
194191
indexProgressStatusDidChange: @escaping @Sendable () -> Void
195192
) {
196193
self.index = index
197194
self.buildSystemManager = buildSystemManager
198195
self.testHooks = testHooks
199196
self.indexTaskScheduler = indexTaskScheduler
200-
self.indexProcessDidProduceResult = indexProcessDidProduceResult
197+
self.logMessageToIndexLog = logMessageToIndexLog
201198
self.indexTasksWereScheduled = indexTasksWereScheduled
202199
self.indexProgressStatusDidChange = indexProgressStatusDidChange
203200
}
@@ -418,7 +415,7 @@ public final actor SemanticIndexManager {
418415
targetsToPrepare: targetsToPrepare,
419416
buildSystemManager: self.buildSystemManager,
420417
preparationUpToDateTracker: preparationUpToDateTracker,
421-
indexProcessDidProduceResult: indexProcessDidProduceResult,
418+
logMessageToIndexLog: logMessageToIndexLog,
422419
testHooks: testHooks
423420
)
424421
)
@@ -465,7 +462,7 @@ public final actor SemanticIndexManager {
465462
buildSystemManager: self.buildSystemManager,
466463
index: index,
467464
indexStoreUpToDateTracker: indexStoreUpToDateTracker,
468-
indexProcessDidProduceResult: indexProcessDidProduceResult,
465+
logMessageToIndexLog: logMessageToIndexLog,
469466
testHooks: testHooks
470467
)
471468
)

0 commit comments

Comments
 (0)