@@ -353,8 +353,6 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
353353 }
354354 }
355355
356-
357-
358356 /// Intended to be called during task dependency setup.
359357 /// If remote caching is enabled along with integrated cache queries, it will request
360358 /// a `ClangCachingMaterializeKeyTaskAction` as task dependency.
@@ -485,35 +483,51 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
485483 outputDelegate: any TaskOutputDelegate
486484 ) throws {
487485 let payload : DependencyValidationInfo . Payload
488- if let traceFilePath {
489- let traceFileContent = try fileSystem. read ( traceFilePath)
490-
491- let traceData : Array < TraceData >
492- // clang will emit an empty file instead of an empty array when there's nothing to trace
493- if traceFileContent. isEmpty {
494- traceData = [ ]
495- } else {
496- do {
497- traceData = try JSONDecoder ( ) . decode ( Array< TraceData> . self , from: Data ( traceFileContent) )
498- } catch {
499- throw StubError . error ( " Failed to decode json trace at \( traceFilePath. str) : \( error) " )
500- }
501- }
502-
503- var allFiles = Set < Path > ( )
504- traceData. forEach { allFiles. formUnion ( Set ( $0. includes) ) }
486+ if let traceFilePath,
487+ let traceData = try parseTraceData ( Data ( fileSystem. read ( traceFilePath) ) ) {
505488
506489 outputDelegate. incrementTaskCounter ( . headerDependenciesValidatedTasks)
507-
508490 if isModular {
509- let ( imports, includes) = separateImportsFromIncludes ( allFiles)
510491 outputDelegate. incrementTaskCounter ( . moduleDependenciesValidatedTasks)
511- outputDelegate. incrementTaskCounter ( . moduleDependenciesScanned, by: imports. count)
492+ }
493+
494+ switch traceData {
495+ case . empty:
496+ payload = . clangDependencies( imports: [ ] , includes: [ ] )
497+ case let . v1( traceDataV1) :
498+ // mapping each header path to the set of locations that include it
499+ var allFiles = [ Path : Set < SWBUtil . Diagnostic . Location > ] ( ) ;
500+
501+ for entry in traceDataV1 {
502+ entry. includes. forEach { allFiles [ $0, default: [ ] ] . insert ( . path( entry. source, fileLocation: nil ) ) }
503+ }
504+
505+ if isModular {
506+ let ( imports, includes) = separateImportsFromIncludes ( allFiles)
507+ outputDelegate. incrementTaskCounter ( . moduleDependenciesScanned, by: imports. count)
508+ outputDelegate. incrementTaskCounter ( . headerDependenciesScanned, by: includes. count)
509+ payload = . clangDependencies( imports: imports, includes: includes)
510+ } else {
511+ let includes = allFiles. map { file, locations in DependencyValidationInfo . Include ( path: file, includeLocations: Array ( locations) ) }
512+ outputDelegate. incrementTaskCounter ( . headerDependenciesScanned, by: includes. count)
513+ payload = . clangDependencies( imports: [ ] , includes: includes)
514+ }
515+ case let . v2( traceDataV2) :
516+ var allIncludes = [ Path : Set < SWBUtil . Diagnostic . Location > ] ( ) ;
517+ var allImports = [ String : Set < SWBUtil . Diagnostic . Location > ] ( ) ;
518+
519+ for entry in traceDataV2. dependencies {
520+ entry. includes. forEach { allIncludes [ $0. file, default: [ ] ] . insert ( parseTraceSourceLocation ( $0. location) ) }
521+ if isModular {
522+ entry. imports. forEach { allImports [ $0. module, default: [ ] ] . insert ( parseTraceSourceLocation ( $0. location) ) }
523+ }
524+ }
525+
526+ let imports = allImports. map { name, locations in DependencyValidationInfo . Import ( dependency: ModuleDependency ( name: name, accessLevel: . Private, optional: false ) , importLocations: Array ( locations) ) }
527+ let includes = allIncludes. map { file, locations in DependencyValidationInfo . Include ( path: file, includeLocations: Array ( locations) ) }
512528 outputDelegate. incrementTaskCounter ( . headerDependenciesScanned, by: includes. count)
529+ outputDelegate. incrementTaskCounter ( . moduleDependenciesScanned, by: imports. count)
513530 payload = . clangDependencies( imports: imports, includes: includes)
514- } else {
515- outputDelegate. incrementTaskCounter ( . headerDependenciesScanned, by: allFiles. count)
516- payload = . clangDependencies( imports: [ ] , includes: Array ( allFiles) )
517531 }
518532 } else {
519533 outputDelegate. incrementTaskCounter ( . headerDependenciesNotValidatedTasks)
@@ -534,29 +548,28 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
534548 }
535549 }
536550
537- // Clang's dependency tracing does not currently clearly distinguish modular imports from non-modular includes.
538- // Until that gets fixed , just guess that if the file is contained in a framework, it comes from a module with
551+ // Clang's dependency tracing V1 did not clearly distinguish modular imports from non-modular includes.
552+ // To keep supporting those trace files , just guess that if the file is contained in a framework, it comes from a module with
539553 // the same name. That is obviously not going to be reliable but it unblocks us from continuing experiments with
540554 // dependency specifications.
541- private static func separateImportsFromIncludes( _ files: Set < Path > ) -> ( [ DependencyValidationInfo . Import ] , [ Path ] ) {
555+ private static func separateImportsFromIncludes( _ files: [ Path : Set < SWBUtil . Diagnostic . Location > ] ) -> ( [ DependencyValidationInfo . Import ] , [ DependencyValidationInfo . Include ] ) {
542556 func findFrameworkName( _ file: Path ) -> String ? {
543557 if file. fileExtension == " framework " {
544558 return file. basenameWithoutSuffix
545559 }
546560 return file. dirname. isEmpty || file. dirname. isRoot ? nil : findFrameworkName ( file. dirname)
547561 }
548- var moduleNames : [ String ] = [ ]
549- var includeFiles : [ Path ] = [ ]
550- for file in files {
562+ var moduleImportsByName = [ String : Set < SWBUtil . Diagnostic . Location > ] ( )
563+ var headerIncludes : [ DependencyValidationInfo . Include ] = [ ]
564+ for ( file, includeLocations ) in files {
551565 if let frameworkName = findFrameworkName ( file) {
552- moduleNames . append ( frameworkName )
566+ moduleImportsByName [ frameworkName , default : [ ] ] . formUnion ( includeLocations )
553567 } else {
554- includeFiles . append ( file)
568+ headerIncludes . append ( DependencyValidationInfo . Include ( path : file, includeLocations : Array ( includeLocations ) ) )
555569 }
556570 }
557- let moduleDependencies = moduleNames. map { ModuleDependency ( name: $0, accessLevel: . Private, optional: false ) }
558- let moduleImports = moduleDependencies. map { DependencyValidationInfo . Import ( dependency: $0, importLocations: [ ] ) }
559- return ( moduleImports, includeFiles)
571+ let moduleImports = moduleImportsByName. map { name, locations in DependencyValidationInfo . Import ( dependency: ModuleDependency ( name: name, accessLevel: . Private, optional: false ) , importLocations: Array ( locations) ) }
572+ return ( moduleImports, headerIncludes)
560573 }
561574}
562575
@@ -623,9 +636,71 @@ public final class ClangNonModularCompileTaskAction: TaskAction {
623636 }
624637}
625638
639+ fileprivate func parseTraceData( _ data: Data ) throws -> TraceData ? {
640+ // clang will emit an empty file instead of an empty array when there's nothing to trace
641+ if data. isEmpty {
642+ return . empty
643+ }
644+
645+ let jsonObject = try PropertyList . fromJSONData ( data)
646+ if let version = jsonObject. dictValue ? [ " version " ] ? . stringValue {
647+ if version == " 2.0.0 " {
648+ return . v2( try JSONDecoder ( ) . decode ( TraceData . TraceFileV2. self, from: data) )
649+ }
650+ return nil
651+ } else {
652+ // The initial unversioned format (v1) of the trace file generated from clang
653+ // is a JSON array so deserializing it as a dictionary will fail.
654+ return . v1( try JSONDecoder ( ) . decode ( TraceData . TraceFileV1. self, from: data) )
655+ }
656+ }
657+
658+ fileprivate func parseTraceSourceLocation( _ locationStr: String ) -> SWBUtil . Diagnostic . Location {
659+ guard let match = locationStr. wholeMatch ( of: #/(?<filename>.+):(?<line>\d+):(?<column>\d+)/# ) else {
660+ return . unknown
661+ }
662+ let filename = Path ( match. filename)
663+ let line = Int ( match. line)
664+ let column = Int ( match. column)
665+ if let line {
666+ return . path( filename, fileLocation: . textual( line: line, column: column) )
667+ }
668+ return . unknown
669+ }
670+
626671// Results from tracing header includes with "direct-per-file" filtering.
627672// This is used to validate dependencies.
628- fileprivate struct TraceData : Decodable {
629- let source : Path
630- let includes : [ Path ]
673+ fileprivate enum TraceData : Decodable {
674+ fileprivate struct Include : Decodable {
675+ let location : String
676+ let file : Path
677+ }
678+
679+ fileprivate struct Import : Decodable {
680+ let location : String
681+ let module : String
682+ let file : Path
683+ }
684+
685+ fileprivate struct TraceDataObjectV1 : Decodable {
686+ let source : Path
687+ let includes : [ Path ]
688+ }
689+
690+ fileprivate struct TraceDataObjectV2 : Decodable {
691+ let source : Path
692+ let includes : [ Include ]
693+ let imports : [ Import ]
694+ }
695+
696+ fileprivate typealias TraceFileV1 = [ TraceDataObjectV1 ]
697+
698+ fileprivate struct TraceFileV2 : Decodable {
699+ let version : String
700+ let dependencies : [ TraceDataObjectV2 ]
701+ }
702+
703+ case empty
704+ case v1( TraceFileV1 )
705+ case v2( TraceFileV2 )
631706}
0 commit comments