Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Contributor Documentation/BSP Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,51 @@ export interface SourceKitInitializeBuildResponseData {
}
```

## `build/logMessage`

Added fields:

```ts
/**
* Extends BSPs log message grouping by explicitly starting and ending the log for a specific task ID.
*/
structure?: StructuredLogBegin | StructuredLogReport | StructuredLogEnd;
```

With

```ts
/**
* Indicates the beginning of a new task that may receive updates with `StructuredLogReport` or `StructuredLogEnd`
* payloads.
*/
export interface StructuredLogBegin {
kind: 'begin'

/**
* A succinct title that can be used to describe the task that started this structured.
*/
title: string;
}


/**
* Adds a new log message to a structured log without ending it.
*/
export interface StructuredLogReport {
kind: 'report';
}

/**
* Ends a structured log. No more `StructuredLogReport` updates should be sent for this task ID.
*
* The task ID may be re-used for new structured logs by beginning a new structured log for that task.
*/
export interface StructuredLogEnd {
kind: 'end';
}
```

## `build/taskStart`

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.
Expand Down
12 changes: 3 additions & 9 deletions Contributor Documentation/LSP Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ With
* payloads.
*/
export interface StructuredLogBegin {
kind: 'begin'
kind: 'begin';

/**
* A succinct title that can be used to describe the task that started this structured.
Expand All @@ -535,10 +535,7 @@ export interface StructuredLogBegin {
* Adds a new log message to a structured log without ending it.
*/
export interface StructuredLogReport {
/*
* A unique identifier, identifying the task this structured log message belongs to.
*/
taskID: string;
kind: 'report';
}

/**
Expand All @@ -547,10 +544,7 @@ export interface StructuredLogReport {
* The task ID may be re-used for new structured logs by beginning a new structured log for that task.
*/
export interface StructuredLogEnd {
/*
* A unique identifier, identifying the task this structured log message belongs to.
*/
taskID: string;
kind: 'end';
}
```

Expand Down
22 changes: 20 additions & 2 deletions Sources/BuildServerIntegration/BuildServerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
await filesBuildSettingsChangedDebouncer.scheduleCall(Set(watchedFiles.keys))
}

private func logMessage(notification: BuildServerProtocol.OnBuildLogMessageNotification) async {
private func logMessage(notification: OnBuildLogMessageNotification) async {
await connectionToClient.waitUntilInitialized()
let type: WindowMessageType =
switch notification.type {
Expand All @@ -710,7 +710,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
connectionToClient.logMessageToIndexLog(
message: notification.message,
type: type,
structure: notification.structure
structure: notification.lspStructure
)
}

Expand Down Expand Up @@ -1739,3 +1739,21 @@ private let supplementalClangIndexingArgs: [String] = [
"-Wno-non-modular-include-in-framework-module",
"-Wno-incomplete-umbrella",
]

private extension OnBuildLogMessageNotification {
var lspStructure: LanguageServerProtocol.StructuredLogKind? {
guard let taskId = self.task?.id else {
return nil
}
switch structure {
case .begin(let info):
return .begin(StructuredLogBegin(title: info.title, taskID: taskId))
case .report:
return .report(StructuredLogReport(taskID: taskId))
case .end:
return .end(StructuredLogEnd(taskID: taskId))
case nil:
return nil
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ package protocol BuildServerManagerConnectionToClient: Sendable, Connection {
func watchFiles(_ fileWatchers: [FileSystemWatcher]) async

/// Log a message in the client's index log.
func logMessageToIndexLog(message: String, type: WindowMessageType, structure: StructuredLogKind?)
func logMessageToIndexLog(
message: String,
type: WindowMessageType,
structure: LanguageServerProtocol.StructuredLogKind?
)
}
23 changes: 13 additions & 10 deletions Sources/BuildServerIntegration/SwiftPMBuildServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,21 +184,23 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
self.connectionToSourceKitLSP = connectionToSourceKitLSP

// Start an open-ended log for messages that we receive during package loading. We never end this log.
let logTaskID = "swiftpm-log-\(UUID())"
let logTaskID = TaskId(id: "swiftpm-log-\(UUID())")
connectionToSourceKitLSP.send(
OnBuildLogMessageNotification(
type: .info,
task: logTaskID,
message: "",
structure: .begin(StructuredLogBegin(title: "SwiftPM log for \(projectRoot.path)", taskID: logTaskID))
structure: .begin(StructuredLogBegin(title: "SwiftPM log for \(projectRoot.path)"))
)
)

self.observabilitySystem = ObservabilitySystem({ scope, diagnostic in
connectionToSourceKitLSP.send(
OnBuildLogMessageNotification(
type: .info,
task: logTaskID,
message: diagnostic.description,
structure: .report(StructuredLogReport(taskID: logTaskID))
structure: .report(StructuredLogReport())
)
)
logger.log(level: diagnostic.severity.asLogLevel, "SwiftPM log: \(diagnostic.description)")
Expand Down Expand Up @@ -750,30 +752,30 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
connectionToSourceKitLSP.send(
BuildServerProtocol.OnBuildLogMessageNotification(
type: .info,
task: taskID,
message: "\(arguments.joined(separator: " "))",
structure: .begin(
StructuredLogBegin(
title: "Preparing \(self.swiftPMTargets[target]?.name ?? target.uri.stringValue)",
taskID: taskID.id
)
StructuredLogBegin(title: "Preparing \(self.swiftPMTargets[target]?.name ?? target.uri.stringValue)")
)
)
)
let stdoutHandler = PipeAsStringHandler { message in
self.connectionToSourceKitLSP.send(
BuildServerProtocol.OnBuildLogMessageNotification(
type: .info,
task: taskID,
message: message,
structure: .report(StructuredLogReport(taskID: taskID.id))
structure: .report(StructuredLogReport())
)
)
}
let stderrHandler = PipeAsStringHandler { message in
self.connectionToSourceKitLSP.send(
BuildServerProtocol.OnBuildLogMessageNotification(
type: .info,
task: taskID,
message: message,
structure: .report(StructuredLogReport(taskID: taskID.id))
structure: .report(StructuredLogReport())
)
)
}
Expand All @@ -790,8 +792,9 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
self.connectionToSourceKitLSP.send(
BuildServerProtocol.OnBuildLogMessageNotification(
type: exitStatus.isSuccess ? .info : .error,
task: taskID,
message: "Finished with \(exitStatus.description) in \(start.duration(to: .now))",
structure: .end(StructuredLogEnd(taskID: taskID.id))
structure: .end(StructuredLogEnd())
)
)
switch exitStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,161 @@ public import LanguageServerProtocol

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

/// The message type.
public var type: MessageType

/// The task id if any.
public var task: TaskId?

/// The request id that originated this notification.
/// The originId field helps clients know which request originated a notification in case several requests are handled by the
/// client at the same time. It will only be populated if the client defined it in the request that triggered this notification.
public var originId: OriginId?

/// The actual message.
public var message: String

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

public init(type: MessageType, message: String, structure: StructuredLogKind?) {
public init(
type: MessageType,
task: TaskId? = nil,
originId: OriginId? = nil,
message: String,
structure: StructuredLogKind? = nil
) {
self.type = type
self.task = task
self.originId = originId
self.message = message
self.structure = structure
}
}

public enum StructuredLogKind: Codable, Hashable, Sendable {
case begin(StructuredLogBegin)
case report(StructuredLogReport)
case end(StructuredLogEnd)

public init(from decoder: Decoder) throws {
if let begin = try? StructuredLogBegin(from: decoder) {
self = .begin(begin)
} else if let report = try? StructuredLogReport(from: decoder) {
self = .report(report)
} else if let end = try? StructuredLogEnd(from: decoder) {
self = .end(end)
} else {
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected StructuredLogBegin, StructuredLogReport, or StructuredLogEnd"
)
throw DecodingError.dataCorrupted(context)
}
}

public func encode(to encoder: Encoder) throws {
switch self {
case .begin(let begin):
try begin.encode(to: encoder)
case .report(let report):
try report.encode(to: encoder)
case .end(let end):
try end.encode(to: encoder)
}
}
}

/// Indicates the beginning of a new task that may receive updates with `StructuredLogReport` or `StructuredLogEnd`
/// payloads.
public struct StructuredLogBegin: Codable, Hashable, Sendable {
/// A succinct title that can be used to describe the task that started this structured.
public var title: String

private enum CodingKeys: CodingKey {
case kind
case title
}

public init(title: String) {
self.title = title
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard try container.decode(String.self, forKey: .kind) == "begin" else {
throw DecodingError.dataCorruptedError(
forKey: .kind,
in: container,
debugDescription: "Kind of StructuredLogBegin is not 'begin'"
)
}

self.title = try container.decode(String.self, forKey: .title)

}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode("begin", forKey: .kind)
try container.encode(self.title, forKey: .title)
}
}

/// Adds a new log message to a structured log without ending it.
public struct StructuredLogReport: Codable, Hashable, Sendable {
private enum CodingKeys: CodingKey {
case kind
}

public init() {}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard try container.decode(String.self, forKey: .kind) == "report" else {
throw DecodingError.dataCorruptedError(
forKey: .kind,
in: container,
debugDescription: "Kind of StructuredLogReport is not 'report'"
)
}
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode("report", forKey: .kind)
}
}

/// Ends a structured log. No more `StructuredLogReport` updates should be sent for this task ID.
///
/// The task ID may be re-used for new structured logs by beginning a new structured log for that task.
public struct StructuredLogEnd: Codable, Hashable, Sendable {
private enum CodingKeys: CodingKey {
case kind
}

public init() {}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard try container.decode(String.self, forKey: .kind) == "end" else {
throw DecodingError.dataCorruptedError(
forKey: .kind,
in: container,
debugDescription: "Kind of StructuredLogEnd is not 'end'"
)
}
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode("end", forKey: .kind)
}
}
6 changes: 4 additions & 2 deletions Sources/SemanticIndex/PreparationTaskDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ package struct PreparationTaskDescription: IndexTaskDescription {

/// See `SemanticIndexManager.logMessageToIndexLog`.
private let logMessageToIndexLog:
@Sendable (_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind) -> Void
@Sendable (
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
) -> Void

/// Hooks that should be called when the preparation task finishes.
private let hooks: IndexHooks
Expand All @@ -65,7 +67,7 @@ package struct PreparationTaskDescription: IndexTaskDescription {
preparationUpToDateTracker: UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>,
logMessageToIndexLog:
@escaping @Sendable (
_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
) -> Void,
hooks: IndexHooks
) {
Expand Down
Loading