Skip to content

Commit 6301397

Browse files
authored
Merge pull request #2279 from ahoppen/log-bsp-compliant
Make the `build/logMessage` conform to the BSP spec
2 parents 36ccc68 + d734331 commit 6301397

File tree

11 files changed

+246
-34
lines changed

11 files changed

+246
-34
lines changed

Contributor Documentation/BSP Extensions.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,51 @@ export interface SourceKitInitializeBuildResponseData {
3838
}
3939
```
4040

41+
## `build/logMessage`
42+
43+
Added fields:
44+
45+
```ts
46+
/**
47+
* Extends BSPs log message grouping by explicitly starting and ending the log for a specific task ID.
48+
*/
49+
structure?: StructuredLogBegin | StructuredLogReport | StructuredLogEnd;
50+
```
51+
52+
With
53+
54+
```ts
55+
/**
56+
* Indicates the beginning of a new task that may receive updates with `StructuredLogReport` or `StructuredLogEnd`
57+
* payloads.
58+
*/
59+
export interface StructuredLogBegin {
60+
kind: 'begin'
61+
62+
/**
63+
* A succinct title that can be used to describe the task that started this structured.
64+
*/
65+
title: string;
66+
}
67+
68+
69+
/**
70+
* Adds a new log message to a structured log without ending it.
71+
*/
72+
export interface StructuredLogReport {
73+
kind: 'report';
74+
}
75+
76+
/**
77+
* Ends a structured log. No more `StructuredLogReport` updates should be sent for this task ID.
78+
*
79+
* The task ID may be re-used for new structured logs by beginning a new structured log for that task.
80+
*/
81+
export interface StructuredLogEnd {
82+
kind: 'end';
83+
}
84+
```
85+
4186
## `build/taskStart`
4287

4388
If `data` contains a string value for the `workDoneProgressTitle` key, then the task's message will be displayed in the client as a work done progress with that title.

Contributor Documentation/LSP Extensions.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ With
517517
* payloads.
518518
*/
519519
export interface StructuredLogBegin {
520-
kind: 'begin'
520+
kind: 'begin';
521521

522522
/**
523523
* A succinct title that can be used to describe the task that started this structured.
@@ -535,10 +535,7 @@ export interface StructuredLogBegin {
535535
* Adds a new log message to a structured log without ending it.
536536
*/
537537
export interface StructuredLogReport {
538-
/*
539-
* A unique identifier, identifying the task this structured log message belongs to.
540-
*/
541-
taskID: string;
538+
kind: 'report';
542539
}
543540

544541
/**
@@ -547,10 +544,7 @@ export interface StructuredLogReport {
547544
* The task ID may be re-used for new structured logs by beginning a new structured log for that task.
548545
*/
549546
export interface StructuredLogEnd {
550-
/*
551-
* A unique identifier, identifying the task this structured log message belongs to.
552-
*/
553-
taskID: string;
547+
kind: 'end';
554548
}
555549
```
556550

Sources/BuildServerIntegration/BuildServerManager.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
698698
await filesBuildSettingsChangedDebouncer.scheduleCall(Set(watchedFiles.keys))
699699
}
700700

701-
private func logMessage(notification: BuildServerProtocol.OnBuildLogMessageNotification) async {
701+
private func logMessage(notification: OnBuildLogMessageNotification) async {
702702
await connectionToClient.waitUntilInitialized()
703703
let type: WindowMessageType =
704704
switch notification.type {
@@ -710,7 +710,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
710710
connectionToClient.logMessageToIndexLog(
711711
message: notification.message,
712712
type: type,
713-
structure: notification.structure
713+
structure: notification.lspStructure
714714
)
715715
}
716716

@@ -1739,3 +1739,21 @@ private let supplementalClangIndexingArgs: [String] = [
17391739
"-Wno-non-modular-include-in-framework-module",
17401740
"-Wno-incomplete-umbrella",
17411741
]
1742+
1743+
private extension OnBuildLogMessageNotification {
1744+
var lspStructure: LanguageServerProtocol.StructuredLogKind? {
1745+
guard let taskId = self.task?.id else {
1746+
return nil
1747+
}
1748+
switch structure {
1749+
case .begin(let info):
1750+
return .begin(StructuredLogBegin(title: info.title, taskID: taskId))
1751+
case .report:
1752+
return .report(StructuredLogReport(taskID: taskId))
1753+
case .end:
1754+
return .end(StructuredLogEnd(taskID: taskId))
1755+
case nil:
1756+
return nil
1757+
}
1758+
}
1759+
}

Sources/BuildServerIntegration/BuildServerManagerDelegate.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,9 @@ package protocol BuildServerManagerConnectionToClient: Sendable, Connection {
5050
func watchFiles(_ fileWatchers: [FileSystemWatcher]) async
5151

5252
/// Log a message in the client's index log.
53-
func logMessageToIndexLog(message: String, type: WindowMessageType, structure: StructuredLogKind?)
53+
func logMessageToIndexLog(
54+
message: String,
55+
type: WindowMessageType,
56+
structure: LanguageServerProtocol.StructuredLogKind?
57+
)
5458
}

Sources/BuildServerIntegration/SwiftPMBuildServer.swift

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -184,21 +184,23 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
184184
self.connectionToSourceKitLSP = connectionToSourceKitLSP
185185

186186
// Start an open-ended log for messages that we receive during package loading. We never end this log.
187-
let logTaskID = "swiftpm-log-\(UUID())"
187+
let logTaskID = TaskId(id: "swiftpm-log-\(UUID())")
188188
connectionToSourceKitLSP.send(
189189
OnBuildLogMessageNotification(
190190
type: .info,
191+
task: logTaskID,
191192
message: "",
192-
structure: .begin(StructuredLogBegin(title: "SwiftPM log for \(projectRoot.path)", taskID: logTaskID))
193+
structure: .begin(StructuredLogBegin(title: "SwiftPM log for \(projectRoot.path)"))
193194
)
194195
)
195196

196197
self.observabilitySystem = ObservabilitySystem({ scope, diagnostic in
197198
connectionToSourceKitLSP.send(
198199
OnBuildLogMessageNotification(
199200
type: .info,
201+
task: logTaskID,
200202
message: diagnostic.description,
201-
structure: .report(StructuredLogReport(taskID: logTaskID))
203+
structure: .report(StructuredLogReport())
202204
)
203205
)
204206
logger.log(level: diagnostic.severity.asLogLevel, "SwiftPM log: \(diagnostic.description)")
@@ -750,30 +752,30 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
750752
connectionToSourceKitLSP.send(
751753
BuildServerProtocol.OnBuildLogMessageNotification(
752754
type: .info,
755+
task: taskID,
753756
message: "\(arguments.joined(separator: " "))",
754757
structure: .begin(
755-
StructuredLogBegin(
756-
title: "Preparing \(self.swiftPMTargets[target]?.name ?? target.uri.stringValue)",
757-
taskID: taskID.id
758-
)
758+
StructuredLogBegin(title: "Preparing \(self.swiftPMTargets[target]?.name ?? target.uri.stringValue)")
759759
)
760760
)
761761
)
762762
let stdoutHandler = PipeAsStringHandler { message in
763763
self.connectionToSourceKitLSP.send(
764764
BuildServerProtocol.OnBuildLogMessageNotification(
765765
type: .info,
766+
task: taskID,
766767
message: message,
767-
structure: .report(StructuredLogReport(taskID: taskID.id))
768+
structure: .report(StructuredLogReport())
768769
)
769770
)
770771
}
771772
let stderrHandler = PipeAsStringHandler { message in
772773
self.connectionToSourceKitLSP.send(
773774
BuildServerProtocol.OnBuildLogMessageNotification(
774775
type: .info,
776+
task: taskID,
775777
message: message,
776-
structure: .report(StructuredLogReport(taskID: taskID.id))
778+
structure: .report(StructuredLogReport())
777779
)
778780
)
779781
}
@@ -790,8 +792,9 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
790792
self.connectionToSourceKitLSP.send(
791793
BuildServerProtocol.OnBuildLogMessageNotification(
792794
type: exitStatus.isSuccess ? .info : .error,
795+
task: taskID,
793796
message: "Finished with \(exitStatus.description) in \(start.duration(to: .now))",
794-
structure: .end(StructuredLogEnd(taskID: taskID.id))
797+
structure: .end(StructuredLogEnd())
795798
)
796799
)
797800
switch exitStatus {

Sources/BuildServerProtocol/Messages/OnBuildLogMessageNotification.swift

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,161 @@ public import LanguageServerProtocol
1414

1515
/// The log message notification is sent from a server to a client to ask the client to log a particular message in its console.
1616
///
17-
/// A `build/logMessage`` notification is similar to LSP's `window/logMessage``.
17+
/// A `build/logMessage`` notification is similar to LSP's `window/logMessage``, except for a few additions like id and originId.
1818
public struct OnBuildLogMessageNotification: NotificationType {
1919
public static let method: String = "build/logMessage"
2020

2121
/// The message type.
2222
public var type: MessageType
2323

24+
/// The task id if any.
25+
public var task: TaskId?
26+
27+
/// The request id that originated this notification.
28+
/// The originId field helps clients know which request originated a notification in case several requests are handled by the
29+
/// client at the same time. It will only be populated if the client defined it in the request that triggered this notification.
30+
public var originId: OriginId?
31+
2432
/// The actual message.
2533
public var message: String
2634

27-
/// If specified, allows grouping log messages that belong to the same originating task together instead of logging
28-
/// them in chronological order in which they were produced.
35+
/// Extends BSPs log message grouping by explicitly starting and ending the log for a specific task ID.
36+
///
37+
/// **(BSP Extension)***
2938
public var structure: StructuredLogKind?
3039

31-
public init(type: MessageType, message: String, structure: StructuredLogKind?) {
40+
public init(
41+
type: MessageType,
42+
task: TaskId? = nil,
43+
originId: OriginId? = nil,
44+
message: String,
45+
structure: StructuredLogKind? = nil
46+
) {
3247
self.type = type
48+
self.task = task
49+
self.originId = originId
3350
self.message = message
3451
self.structure = structure
3552
}
3653
}
54+
55+
public enum StructuredLogKind: Codable, Hashable, Sendable {
56+
case begin(StructuredLogBegin)
57+
case report(StructuredLogReport)
58+
case end(StructuredLogEnd)
59+
60+
public init(from decoder: Decoder) throws {
61+
if let begin = try? StructuredLogBegin(from: decoder) {
62+
self = .begin(begin)
63+
} else if let report = try? StructuredLogReport(from: decoder) {
64+
self = .report(report)
65+
} else if let end = try? StructuredLogEnd(from: decoder) {
66+
self = .end(end)
67+
} else {
68+
let context = DecodingError.Context(
69+
codingPath: decoder.codingPath,
70+
debugDescription: "Expected StructuredLogBegin, StructuredLogReport, or StructuredLogEnd"
71+
)
72+
throw DecodingError.dataCorrupted(context)
73+
}
74+
}
75+
76+
public func encode(to encoder: Encoder) throws {
77+
switch self {
78+
case .begin(let begin):
79+
try begin.encode(to: encoder)
80+
case .report(let report):
81+
try report.encode(to: encoder)
82+
case .end(let end):
83+
try end.encode(to: encoder)
84+
}
85+
}
86+
}
87+
88+
/// Indicates the beginning of a new task that may receive updates with `StructuredLogReport` or `StructuredLogEnd`
89+
/// payloads.
90+
public struct StructuredLogBegin: Codable, Hashable, Sendable {
91+
/// A succinct title that can be used to describe the task that started this structured.
92+
public var title: String
93+
94+
private enum CodingKeys: CodingKey {
95+
case kind
96+
case title
97+
}
98+
99+
public init(title: String) {
100+
self.title = title
101+
}
102+
103+
public init(from decoder: any Decoder) throws {
104+
let container = try decoder.container(keyedBy: CodingKeys.self)
105+
guard try container.decode(String.self, forKey: .kind) == "begin" else {
106+
throw DecodingError.dataCorruptedError(
107+
forKey: .kind,
108+
in: container,
109+
debugDescription: "Kind of StructuredLogBegin is not 'begin'"
110+
)
111+
}
112+
113+
self.title = try container.decode(String.self, forKey: .title)
114+
115+
}
116+
117+
public func encode(to encoder: any Encoder) throws {
118+
var container = encoder.container(keyedBy: CodingKeys.self)
119+
try container.encode("begin", forKey: .kind)
120+
try container.encode(self.title, forKey: .title)
121+
}
122+
}
123+
124+
/// Adds a new log message to a structured log without ending it.
125+
public struct StructuredLogReport: Codable, Hashable, Sendable {
126+
private enum CodingKeys: CodingKey {
127+
case kind
128+
}
129+
130+
public init() {}
131+
132+
public init(from decoder: any Decoder) throws {
133+
let container = try decoder.container(keyedBy: CodingKeys.self)
134+
guard try container.decode(String.self, forKey: .kind) == "report" else {
135+
throw DecodingError.dataCorruptedError(
136+
forKey: .kind,
137+
in: container,
138+
debugDescription: "Kind of StructuredLogReport is not 'report'"
139+
)
140+
}
141+
}
142+
143+
public func encode(to encoder: any Encoder) throws {
144+
var container = encoder.container(keyedBy: CodingKeys.self)
145+
try container.encode("report", forKey: .kind)
146+
}
147+
}
148+
149+
/// Ends a structured log. No more `StructuredLogReport` updates should be sent for this task ID.
150+
///
151+
/// The task ID may be re-used for new structured logs by beginning a new structured log for that task.
152+
public struct StructuredLogEnd: Codable, Hashable, Sendable {
153+
private enum CodingKeys: CodingKey {
154+
case kind
155+
}
156+
157+
public init() {}
158+
159+
public init(from decoder: any Decoder) throws {
160+
let container = try decoder.container(keyedBy: CodingKeys.self)
161+
guard try container.decode(String.self, forKey: .kind) == "end" else {
162+
throw DecodingError.dataCorruptedError(
163+
forKey: .kind,
164+
in: container,
165+
debugDescription: "Kind of StructuredLogEnd is not 'end'"
166+
)
167+
}
168+
}
169+
170+
public func encode(to encoder: any Encoder) throws {
171+
var container = encoder.container(keyedBy: CodingKeys.self)
172+
try container.encode("end", forKey: .kind)
173+
}
174+
}

Sources/SemanticIndex/PreparationTaskDescription.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ package struct PreparationTaskDescription: IndexTaskDescription {
4141

4242
/// See `SemanticIndexManager.logMessageToIndexLog`.
4343
private let logMessageToIndexLog:
44-
@Sendable (_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind) -> Void
44+
@Sendable (
45+
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
46+
) -> Void
4547

4648
/// Hooks that should be called when the preparation task finishes.
4749
private let hooks: IndexHooks
@@ -65,7 +67,7 @@ package struct PreparationTaskDescription: IndexTaskDescription {
6567
preparationUpToDateTracker: UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>,
6668
logMessageToIndexLog:
6769
@escaping @Sendable (
68-
_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind
70+
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
6971
) -> Void,
7072
hooks: IndexHooks
7173
) {

0 commit comments

Comments
 (0)