Skip to content

Commit d24449e

Browse files
authored
preserve TSC Diagnostic location and data (#3792)
motivation: downstream clients still depend on TSC Diagnostics Location changes: * preserve original TSC location in metadata and bridge it back when exposing TSC diagnostics * preserve original TSC data in metadata and bridge it back when exposing TSC diagnostics * adjust and add tests
1 parent 2f781b4 commit d24449e

File tree

12 files changed

+216
-123
lines changed

12 files changed

+216
-123
lines changed

Sources/Basics/Observability.swift

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ extension DiagnosticsEmitterProtocol {
174174
// FIXME: this brings in the TSC API still
175175
if let errorProvidingLocation = error as? DiagnosticLocationProviding, let diagnosticLocation = errorProvidingLocation.diagnosticLocation {
176176
metadata = metadata ?? ObservabilityMetadata()
177-
metadata?.legacyLocation = diagnosticLocation.description
177+
metadata?.legacyDiagnosticLocation = .init(diagnosticLocation)
178178
}
179179

180180
let message: String
@@ -422,6 +422,21 @@ public struct ObservabilityMetadata: Equatable, CustomDebugStringConvertible {
422422
}
423423
}
424424

425+
//@available(*, deprecated, message: "temporary for transition DiagnosticsEngine -> DiagnosticsEmitter")
426+
public func droppingLegacyKeys() -> ObservabilityMetadata? {
427+
var metadata = ObservabilityMetadata()
428+
self.forEach { (key, value) in
429+
if key.keyType == LegacyLocationKey.self {
430+
return
431+
}
432+
if key.keyType == LegacyDataKey.self {
433+
return
434+
}
435+
metadata._storage[key] = value
436+
}
437+
return metadata.isEmpty ? .none : metadata
438+
}
439+
425440
/// A type-erased `ObservabilityMetadataKey` used when iterating through the `ObservabilityMetadata` using its `forEach` method.
426441
public struct AnyKey {
427442
/// The key's type represented erased to an `Any.Type`.
@@ -460,13 +475,11 @@ extension ObservabilityScope {
460475
//@available(*, deprecated, message: "temporary for transition DiagnosticsEngine -> DiagnosticsEmitter")
461476
extension Diagnostic {
462477
init?(_ diagnostic: TSCDiagnostic) {
463-
var metadata: ObservabilityMetadata?
464-
if diagnostic.location is UnknownLocation {
465-
metadata = .none
466-
} else {
467-
metadata = ObservabilityMetadata()
468-
metadata?.legacyLocation = diagnostic.location.description
478+
var metadata = ObservabilityMetadata()
479+
if !(diagnostic.location is UnknownLocation) {
480+
metadata.legacyDiagnosticLocation = .init(diagnostic.location)
469481
}
482+
metadata.legacyDiagnosticData = .init(diagnostic.data)
470483

471484
switch diagnostic.behavior {
472485
case .error:
@@ -504,28 +517,35 @@ extension ObservabilitySystem {
504517
extension TSCDiagnostic {
505518
init(_ diagnostic: Diagnostic) {
506519
let location: DiagnosticLocation
507-
if let metadata = diagnostic.metadata {
508-
location = metadata.legacyDiagnosticLocation
520+
if let legacyLocation = diagnostic.metadata?.legacyDiagnosticLocation {
521+
location = legacyLocation.underlying
509522
} else {
510523
location = UnknownLocation.location
511524
}
512525

526+
let data: DiagnosticData
527+
if let legacyData = diagnostic.metadata?.legacyDiagnosticData {
528+
data = legacyData.underlying
529+
} else {
530+
data = StringDiagnostic(diagnostic.message)
531+
}
532+
513533
switch diagnostic.severity {
514534
case .error:
515-
self = .init(message: .error(diagnostic.message), location: location)
535+
self = .init(message: .error(data), location: location)
516536
case .warning:
517-
self = .init(message: .warning(diagnostic.message), location: location)
537+
self = .init(message: .warning(data), location: location)
518538
case .info:
519-
self = .init(message: .note(diagnostic.message), location: location)
539+
self = .init(message: .note(data), location: location)
520540
case .debug:
521-
self = .init(message: .note(diagnostic.message), location: location)
541+
self = .init(message: .note(data), location: location)
522542
}
523543
}
524544
}
525545

526546
//@available(*, deprecated, message: "temporary for transition DiagnosticsEngine -> DiagnosticsEmitter")
527547
extension ObservabilityMetadata {
528-
public var legacyLocation: String? {
548+
public var legacyDiagnosticLocation: DiagnosticLocationWrapper? {
529549
get {
530550
self[LegacyLocationKey.self]
531551
}
@@ -534,25 +554,47 @@ extension ObservabilityMetadata {
534554
}
535555
}
536556

537-
enum LegacyLocationKey: Key {
538-
typealias Value = String
557+
private enum LegacyLocationKey: Key {
558+
typealias Value = DiagnosticLocationWrapper
559+
}
560+
561+
public struct DiagnosticLocationWrapper: CustomStringConvertible {
562+
let underlying: DiagnosticLocation
563+
564+
public init (_ underlying: DiagnosticLocation) {
565+
self.underlying = underlying
566+
}
567+
568+
public var description: String {
569+
self.underlying.description
570+
}
539571
}
540572
}
541573

542-
@available(*, deprecated, message: "temporary for transition DiagnosticsEngine -> DiagnosticsEmitter")
574+
//@available(*, deprecated, message: "temporary for transition DiagnosticsEngine -> DiagnosticsEmitter")
543575
extension ObservabilityMetadata {
544-
fileprivate var legacyDiagnosticLocation: DiagnosticLocation {
545-
if let legacyLocation = self.legacyLocation {
546-
return DiagnosticLocationWrapper(legacyLocation)
576+
var legacyDiagnosticData: DiagnosticDataWrapper? {
577+
get {
578+
self[LegacyDataKey.self]
547579
}
548-
return UnknownLocation.location
580+
set {
581+
self[LegacyDataKey.self] = newValue
582+
}
583+
}
584+
585+
private enum LegacyDataKey: Key {
586+
typealias Value = DiagnosticDataWrapper
549587
}
550588

551-
private struct DiagnosticLocationWrapper: DiagnosticLocation {
552-
var description: String
589+
struct DiagnosticDataWrapper: CustomStringConvertible {
590+
let underlying: DiagnosticData
591+
592+
public init (_ underlying: DiagnosticData) {
593+
self.underlying = underlying
594+
}
553595

554-
init (_ location: String) {
555-
self.description = location
596+
public var description: String {
597+
self.underlying.description
556598
}
557599
}
558600
}

Sources/Commands/SwiftTool.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,10 +1066,10 @@ private final class InteractiveWriter {
10661066
// we should remove this as we make use of the new scope and metadata to provide better contextual information
10671067
extension ObservabilityMetadata {
10681068
fileprivate var diagnosticPrefix: String? {
1069-
if let legacyLocation = self.legacyLocation {
1070-
return legacyLocation
1071-
} else if let packageIdentity = self.packageIdentity, let packageLocation = self.packageLocation {
1069+
if let packageIdentity = self.packageIdentity, let packageLocation = self.packageLocation {
10721070
return "'\(packageIdentity)' \(packageLocation)"
1071+
} else if let legacyLocation = self.legacyDiagnosticLocation {
1072+
return legacyLocation.description
10731073
} else {
10741074
return .none
10751075
}

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ extension PackageGraph {
9797
for node in allNodes {
9898
let nodeObservabilityScope = observabilityScope.makeChildScope(
9999
description: "loading package \(node.identity)",
100-
metadata: .packageMetadata(identity: node.identity, location: node.manifest.packageLocation)
100+
metadata: .packageMetadata(identity: node.identity, location: node.manifest.packageLocation, path: node.manifest.path.parentDirectory)
101101
)
102102

103103
let manifest = node.manifest

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ public final class PackageBuilder {
283283
self.warnAboutImplicitExecutableTargets = warnAboutImplicitExecutableTargets
284284
self.observabilityScope = observabilityScope.makeChildScope(
285285
description: "PackageBuilder",
286-
metadata: .packageMetadata(identity: self.identity, location: self.manifest.packageLocation)
286+
metadata: .packageMetadata(identity: self.identity, location: self.manifest.packageLocation, path: self.packagePath)
287287
)
288288
self.fileSystem = fileSystem
289289
}

Sources/PackageLoading/TargetSourcesBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public struct TargetSourcesBuilder {
8080
self.fileSystem = fileSystem
8181

8282
self.observabilityScope = observabilityScope.makeChildScope(description: "TargetSourcesBuilder") {
83-
var metadata = ObservabilityMetadata.packageMetadata(identity: packageIdentity, location: packageLocation)
83+
var metadata = ObservabilityMetadata.packageMetadata(identity: packageIdentity, location: packageLocation, path: packagePath)
8484
metadata.targetName = target.name
8585
return metadata
8686
}

Sources/PackageModel/Package.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11+
import Basics
12+
import struct Foundation.URL
1113
import TSCBasic
1214
import TSCUtility
13-
1415
// Re-export Version from PackageModel, since it is a key part of the model.
1516
@_exported import struct TSCUtility.Version
16-
import Basics
1717

1818
/// The basic package representation.
1919
///
@@ -115,7 +115,7 @@ extension Package {
115115

116116
extension Package {
117117
public var diagnosticsMetadata: ObservabilityMetadata {
118-
return .packageMetadata(identity: self.identity, location: self.manifest.packageLocation)
118+
return .packageMetadata(identity: self.identity, location: self.manifest.packageLocation, path: self.path)
119119
}
120120
}
121121

@@ -139,12 +139,12 @@ extension Package.Error: CustomStringConvertible {
139139
}
140140

141141
extension ObservabilityMetadata {
142-
public static func packageMetadata(identity: PackageIdentity, location: String) -> Self {
142+
public static func packageMetadata(identity: PackageIdentity, location: String, path: AbsolutePath) -> Self {
143143
var metadata = ObservabilityMetadata()
144144
metadata.packageIdentity = identity
145145
metadata.packageLocation = location
146146
// FIXME: (diagnostics) remove once transition to Observability API is complete
147-
metadata.legacyLocation = "'\(identity)' \(location)"
147+
metadata.legacyDiagnosticLocation = .init(PackageLocation.Local(name: identity.description, packagePath: path))
148148
return metadata
149149
}
150150
}

Sources/SPMTestSupport/Observability.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ public class DiagnosticsTestResult {
126126

127127
XCTAssertMatch(diagnostic.message, message, file: file, line: line)
128128
XCTAssertEqual(diagnostic.severity, severity, file: file, line: line)
129-
XCTAssertEqual(diagnostic.metadata, metadata, file: file, line: line)
129+
// FIXME: (diagnostics) compare complete metadata when legacy bridge is removed
130+
//XCTAssertEqual(diagnostic.metadata, metadata, file: file, line: line)
131+
XCTAssertEqual(diagnostic.metadata?.droppingLegacyKeys(), metadata?.droppingLegacyKeys(), file: file, line: line)
130132
}
131133

132134
public func checkUnordered(
@@ -145,9 +147,13 @@ public class DiagnosticsTestResult {
145147
return XCTFail("No diagnostics match \(diagnosticPattern)", file: file, line: line)
146148
} else if matching.count == 1, let diagnostic = matching.first, let index = self.uncheckedDiagnostics.firstIndex(where: { $0 == diagnostic }) {
147149
XCTAssertEqual(diagnostic.severity, severity, file: file, line: line)
148-
XCTAssertEqual(diagnostic.metadata, metadata, file: file, line: line)
150+
// FIXME: (diagnostics) compare complete metadata when legacy bridge is removed
151+
//XCTAssertEqual(diagnostic.metadata, metadata, file: file, line: line)
152+
XCTAssertEqual(diagnostic.metadata?.droppingLegacyKeys(), metadata?.droppingLegacyKeys(), file: file, line: line)
149153
self.uncheckedDiagnostics.remove(at: index)
150-
} else if let index = self.uncheckedDiagnostics.firstIndex(where: { diagnostic in diagnostic.severity == severity && diagnostic.metadata == metadata}) {
154+
// FIXME: (diagnostics) compare complete metadata when legacy bridge is removed
155+
//} else if let index = self.uncheckedDiagnostics.firstIndex(where: { diagnostic in diagnostic.severity == severity && diagnostic.metadata == metadata}) {
156+
} else if let index = self.uncheckedDiagnostics.firstIndex(where: { diagnostic in diagnostic.severity == severity && diagnostic.metadata?.droppingLegacyKeys() == metadata?.droppingLegacyKeys()}) {
151157
self.uncheckedDiagnostics.remove(at: index)
152158
}
153159
}

Tests/BasicsTests/ObservabilitySystemTests.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,51 @@ final class ObservabilitySystemTest: XCTestCase {
160160
}
161161
}
162162

163+
@available(*, deprecated, message: "temporary for transition DiagnosticsEngine -> DiagnosticsEmitter")
164+
func testBridging() throws {
165+
do {
166+
let collector = Collector()
167+
let observabilitySystem = ObservabilitySystem(collector)
168+
let diagnosticsEngine = observabilitySystem.topScope.makeDiagnosticsEngine()
169+
170+
let data = TestData()
171+
let location = TestLocation()
172+
173+
diagnosticsEngine.emit(.error(data), location: location)
174+
175+
var expectedMetadata = ObservabilityMetadata()
176+
expectedMetadata.legacyDiagnosticLocation = .init(location)
177+
expectedMetadata.legacyDiagnosticData = .init(data)
178+
179+
XCTAssertEqual(collector.diagnostics.count, 1)
180+
XCTAssertEqual(collector.diagnostics.first?.metadata, expectedMetadata)
181+
}
182+
183+
do {
184+
let diagnosticsEngine1 = DiagnosticsEngine()
185+
let observabilitySystem = ObservabilitySystem(diagnosticEngine: diagnosticsEngine1)
186+
let diagnosticsEngine2 = observabilitySystem.topScope.makeDiagnosticsEngine()
187+
188+
let data = TestData()
189+
let location = TestLocation()
190+
191+
diagnosticsEngine2.emit(.error(data), location: location)
192+
193+
XCTAssertEqual(diagnosticsEngine1.diagnostics.count, 1)
194+
XCTAssertEqual(diagnosticsEngine1.diagnostics.first!.message.data as? TestData, data)
195+
XCTAssertEqual(diagnosticsEngine1.diagnostics.first!.location as? TestLocation, location)
196+
}
197+
198+
struct TestData: DiagnosticData, Equatable {
199+
var description: String = UUID().uuidString
200+
}
201+
202+
struct TestLocation: DiagnosticLocation, Equatable {
203+
var description: String = UUID().uuidString
204+
205+
}
206+
}
207+
163208
struct Collector: ObservabilityHandlerProvider, DiagnosticsHandler {
164209
private let _diagnostics = ThreadSafeArrayStore<Diagnostic>()
165210

0 commit comments

Comments
 (0)