@@ -196,7 +196,7 @@ extension SwiftBuildMessage.LocationContext {
196196}
197197
198198/// Handler for SwiftBuildMessage events sent by the SWBBuildOperation.
199- final class SwiftBuildSystemMessageHandler {
199+ public final class SwiftBuildSystemMessageHandler {
200200 private let observabilityScope : ObservabilityScope
201201 private let outputStream : OutputByteStream
202202 private let logLevel : Basics . Diagnostic . Severity
@@ -252,7 +252,7 @@ final class SwiftBuildSystemMessageHandler {
252252 targetsByID [ target. targetID] = target
253253 }
254254
255- mutating func target( for task: SwiftBuild . SwiftBuildMessage . TaskStartedInfo ) throws -> SwiftBuild . SwiftBuildMessage . TargetStartedInfo ? {
255+ func target( for task: SwiftBuild . SwiftBuildMessage . TaskStartedInfo ) throws -> SwiftBuild . SwiftBuildMessage . TargetStartedInfo ? {
256256 guard let id = task. targetID else {
257257 return nil
258258 }
@@ -308,91 +308,6 @@ final class SwiftBuildSystemMessageHandler {
308308 }
309309 }
310310
311- /// Represents a parsed diagnostic segment from compiler output
312- private struct ParsedDiagnostic {
313- /// The file path if present
314- let filePath : String ?
315- /// The line number if present
316- let line : Int ?
317- /// The column number if present
318- let column : Int ?
319- /// The severity (error, warning, note, remark)
320- let severity : String
321- /// The diagnostic message text
322- let message : String
323- /// The full text including any multi-line context (code snippets, carets, etc.)
324- let fullText : String
325-
326- /// Parse severity string to Diagnostic.Severity
327- func toDiagnosticSeverity( ) -> Basics . Diagnostic . Severity {
328- switch severity. lowercased ( ) {
329- case " error " : return . error
330- case " warning " : return . warning
331- case " note " : return . info
332- case " remark " : return . debug
333- default : return . info
334- }
335- }
336- }
337-
338- /// Split compiler output into individual diagnostic segments
339- /// Format: /path/to/file.swift:line:column: severity: message
340- private func splitIntoDiagnostics( _ output: String ) -> [ ParsedDiagnostic ] {
341- var diagnostics : [ ParsedDiagnostic ] = [ ]
342-
343- // Regex pattern to match diagnostic lines
344- // Matches: path:line:column: severity: message (path is required)
345- // The path must contain at least one character and line must be present
346- let diagnosticPattern = #"^(.+?):(\d+):(?:(\d+):)?\s*(error|warning|note|remark):\s*(.*)$"#
347- guard let regex = try ? NSRegularExpression ( pattern: diagnosticPattern, options: [ . anchorsMatchLines] ) else {
348- return [ ]
349- }
350-
351- let nsString = output as NSString
352- let matches = regex. matches ( in: output, options: [ ] , range: NSRange ( location: 0 , length: nsString. length) )
353-
354- // Process each match and gather full text including subsequent lines
355- for (index, match) in matches. enumerated ( ) {
356- let matchRange = match. range
357-
358- // Extract components
359- let filePathRange = match. range ( at: 1 )
360- let lineRange = match. range ( at: 2 )
361- let columnRange = match. range ( at: 3 )
362- let severityRange = match. range ( at: 4 )
363- let messageRange = match. range ( at: 5 )
364-
365- let filePath = nsString. substring ( with: filePathRange)
366- let line = Int ( nsString. substring ( with: lineRange) )
367- let column = columnRange. location != NSNotFound ? Int ( nsString. substring ( with: columnRange) ) : nil
368- let severity = nsString. substring ( with: severityRange)
369- let message = nsString. substring ( with: messageRange)
370-
371- // Determine the full text range (from this diagnostic to the next one, or end)
372- let startLocation = matchRange. location
373- let endLocation : Int
374- if index + 1 < matches. count {
375- endLocation = matches [ index + 1 ] . range. location
376- } else {
377- endLocation = nsString. length
378- }
379-
380- let fullTextRange = NSRange ( location: startLocation, length: endLocation - startLocation)
381- let fullText = nsString. substring ( with: fullTextRange) . trimmingCharacters ( in: . whitespacesAndNewlines)
382-
383- diagnostics. append ( ParsedDiagnostic (
384- filePath: filePath,
385- line: line,
386- column: column,
387- severity: severity,
388- message: message,
389- fullText: fullText
390- ) )
391- }
392-
393- return diagnostics
394- }
395-
396311 private func emitDiagnosticCompilerOutput( _ info: SwiftBuildMessage . TaskStartedInfo ) {
397312 // Don't redundantly emit tasks.
398313 guard !self . tasksEmitted. contains ( info. taskSignature) else {
@@ -406,47 +321,8 @@ final class SwiftBuildSystemMessageHandler {
406321 // Decode the buffer to a string
407322 let decodedOutput = String ( decoding: buffer, as: UTF8 . self)
408323
409- // Split the output into individual diagnostic segments
410- let parsedDiagnostics = splitIntoDiagnostics ( decodedOutput)
411-
412- if parsedDiagnostics. isEmpty {
413- // No structured diagnostics found - emit as-is based on task signature matching
414- // Fetch the task signature for a SwiftBuildMessage.DiagnosticInfo
415- func getTaskSignature( from info: SwiftBuildMessage . DiagnosticInfo ) -> String ? {
416- if let taskSignature = info. locationContext2. taskSignature {
417- return taskSignature
418- } else if let taskID = info. locationContext. taskID,
419- let taskSignature = self . buildState. taskSignature ( for: taskID)
420- {
421- return taskSignature
422- }
423- return nil
424- }
425-
426- // Use existing logic as fallback
427- if unprocessedDiagnostics. compactMap ( getTaskSignature) . contains ( where: { $0 == info. taskSignature } ) {
428- self . observabilityScope. emit ( error: decodedOutput)
429- } else {
430- self . observabilityScope. emit ( info: decodedOutput)
431- }
432- } else {
433- // Process each parsed diagnostic derived from the decodedOutput
434- for parsedDiag in parsedDiagnostics {
435- let severity = parsedDiag. toDiagnosticSeverity ( )
436-
437- // Use the appropriate emit method based on severity
438- switch severity {
439- case . error:
440- self . observabilityScope. emit ( error: parsedDiag. fullText)
441- case . warning:
442- self . observabilityScope. emit ( warning: parsedDiag. fullText)
443- case . info:
444- self . observabilityScope. emit ( info: parsedDiag. fullText)
445- case . debug:
446- self . observabilityScope. emit ( severity: . debug, message: parsedDiag. fullText)
447- }
448- }
449- }
324+ // Emit to output stream.
325+ outputStream. send ( decodedOutput)
450326
451327 // Record that we've emitted the output for a given task signature.
452328 self . tasksEmitted. insert ( info. taskSignature)
@@ -515,9 +391,7 @@ final class SwiftBuildSystemMessageHandler {
515391 let startedInfo = try buildState. completed ( task: info)
516392
517393 // If we've captured the compiler output with formatted diagnostics, emit them.
518- // if let buffer = buildState.dataBuffer(for: startedInfo) {
519394 emitDiagnosticCompilerOutput ( startedInfo)
520- // }
521395
522396 if info. result != . success {
523397 self . observabilityScope. emit ( severity: . error, message: " \( startedInfo. ruleInfo) failed with a nonzero exit code. Command line: \( startedInfo. commandLineDisplayString ?? " <no command line> " ) " )
0 commit comments