@@ -353,8 +353,6 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
353
353
}
354
354
}
355
355
356
-
357
-
358
356
/// Intended to be called during task dependency setup.
359
357
/// If remote caching is enabled along with integrated cache queries, it will request
360
358
/// a `ClangCachingMaterializeKeyTaskAction` as task dependency.
@@ -485,35 +483,51 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
485
483
outputDelegate: any TaskOutputDelegate
486
484
) throws {
487
485
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) ) ) {
505
488
506
489
outputDelegate. incrementTaskCounter ( . headerDependenciesValidatedTasks)
507
-
508
490
if isModular {
509
- let ( imports, includes) = separateImportsFromIncludes ( allFiles)
510
491
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) ) }
512
528
outputDelegate. incrementTaskCounter ( . headerDependenciesScanned, by: includes. count)
529
+ outputDelegate. incrementTaskCounter ( . moduleDependenciesScanned, by: imports. count)
513
530
payload = . clangDependencies( imports: imports, includes: includes)
514
- } else {
515
- outputDelegate. incrementTaskCounter ( . headerDependenciesScanned, by: allFiles. count)
516
- payload = . clangDependencies( imports: [ ] , includes: Array ( allFiles) )
517
531
}
518
532
} else {
519
533
outputDelegate. incrementTaskCounter ( . headerDependenciesNotValidatedTasks)
@@ -534,29 +548,28 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
534
548
}
535
549
}
536
550
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
539
553
// the same name. That is obviously not going to be reliable but it unblocks us from continuing experiments with
540
554
// 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 ] ) {
542
556
func findFrameworkName( _ file: Path ) -> String ? {
543
557
if file. fileExtension == " framework " {
544
558
return file. basenameWithoutSuffix
545
559
}
546
560
return file. dirname. isEmpty || file. dirname. isRoot ? nil : findFrameworkName ( file. dirname)
547
561
}
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 {
551
565
if let frameworkName = findFrameworkName ( file) {
552
- moduleNames . append ( frameworkName )
566
+ moduleImportsByName [ frameworkName , default : [ ] ] . formUnion ( includeLocations )
553
567
} else {
554
- includeFiles . append ( file)
568
+ headerIncludes . append ( DependencyValidationInfo . Include ( path : file, includeLocations : Array ( includeLocations ) ) )
555
569
}
556
570
}
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)
560
573
}
561
574
}
562
575
@@ -623,9 +636,71 @@ public final class ClangNonModularCompileTaskAction: TaskAction {
623
636
}
624
637
}
625
638
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
+
626
671
// Results from tracing header includes with "direct-per-file" filtering.
627
672
// 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 )
631
706
}
0 commit comments