Skip to content

Commit 09ad77b

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 b9c7438 commit 09ad77b

22 files changed

+226
-174
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/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ add_library(SKCore STATIC
1010
Debouncer.swift
1111
FallbackBuildSystem.swift
1212
FileBuildSettings.swift
13-
IndexProcessResult.swift
13+
IndexTaskID.swift
1414
MainFilesProvider.swift
1515
PathPrefixMapping.swift
1616
SplitShellCommand.swift

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: 0 additions & 71 deletions
This file was deleted.

Sources/SKCore/IndexTaskID.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
/// 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+
}

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 the pipe.
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
@@ -535,20 +540,20 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
535540

536541
public func prepare(
537542
targets: [ConfiguredTarget],
538-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
543+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
539544
) async throws {
540545
// TODO (indexing): Support preparation of multiple targets at once.
541546
// https://github.com/apple/sourcekit-lsp/issues/1262
542547
for target in targets {
543-
try await prepare(singleTarget: target, indexProcessDidProduceResult: indexProcessDidProduceResult)
548+
try await prepare(singleTarget: target, logMessageToIndexLog: logMessageToIndexLog)
544549
}
545550
let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] }
546551
await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init)))
547552
}
548553

549554
private func prepare(
550555
singleTarget target: ConfiguredTarget,
551-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
556+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
552557
) async throws {
553558
if target == .forPackageManifest {
554559
// Nothing to prepare for package manifests.
@@ -581,15 +586,28 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
581586
return
582587
}
583588
let start = ContinuousClock.now
584-
let process = try Process.launch(arguments: arguments, workingDirectory: nil)
585-
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
586-
indexProcessDidProduceResult(
587-
IndexProcessResult(
588-
taskDescription: "Preparing \(target.targetID) for \(target.runDestinationID)",
589-
processResult: result,
590-
start: start
589+
590+
let logID = IndexTaskID.preparation(id: preparationTaskID.fetchAndIncrement())
591+
logMessageToIndexLog(
592+
logID,
593+
"""
594+
Preparing \(target.targetID) for \(target.runDestinationID)
595+
\(arguments.joined(separator: " "))
596+
"""
597+
)
598+
let stdoutHandler = PipeAsStringHandler { logMessageToIndexLog(logID, $0) }
599+
let stderrHandler = PipeAsStringHandler { logMessageToIndexLog(logID, $0) }
600+
601+
let process = try Process.launch(
602+
arguments: arguments,
603+
workingDirectory: nil,
604+
outputRedirection: .stream(
605+
stdout: { stdoutHandler.handleDataFromPipe(Data($0)) },
606+
stderr: { stderrHandler.handleDataFromPipe(Data($0)) }
591607
)
592608
)
609+
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
610+
logMessageToIndexLog(logID, "Finished in \(start.duration(to: .now))")
593611
switch result.exitStatus.exhaustivelySwitchable {
594612
case .terminated(code: 0):
595613
break

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,12 @@ public final class TestSourceKitLSPClient: MessageHandler {
273273
/// Ignores any notifications that are of a different type or that don't satisfy the predicate.
274274
public func nextNotification<ExpectedNotificationType: NotificationType>(
275275
ofType: ExpectedNotificationType.Type,
276-
satisfying predicate: (ExpectedNotificationType) -> Bool = { _ in true },
276+
satisfying predicate: (ExpectedNotificationType) throws -> Bool = { _ in true },
277277
timeout: Duration = .seconds(defaultTimeout)
278278
) async throws -> ExpectedNotificationType {
279279
while true {
280280
let nextNotification = try await nextNotification(timeout: timeout)
281-
if let notification = nextNotification as? ExpectedNotificationType, predicate(notification) {
281+
if let notification = nextNotification as? ExpectedNotificationType, try predicate(notification) {
282282
return notification
283283
}
284284
}

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(

0 commit comments

Comments
 (0)