@@ -26,9 +26,40 @@ import struct TSCBasic.RelativePath
2626
2727fileprivate typealias RequestCache < Request: RequestType & Hashable > = Cache < Request , Request . Response >
2828
29+ /// An output path returned from the build server in the `SourceItem.data.outputPath` field.
30+ package enum OutputPath : Hashable , Comparable , CustomLogStringConvertible {
31+ /// An output path returned from the build server.
32+ case path( String )
33+
34+ /// The build server does not support output paths.
35+ case notSupported
36+
37+ package var description : String {
38+ switch self {
39+ case . notSupported: return " <output path not supported> "
40+ case . path( let path) : return path
41+ }
42+ }
43+
44+ package var redactedDescription : String {
45+ switch self {
46+ case . notSupported: return " <output path not supported> "
47+ case . path( let path) : return path. hashForLogging
48+ }
49+ }
50+ }
51+
2952package struct SourceFileInfo : Sendable {
53+ /// Maps the targets that this source file is a member of to the output path the file has within that target.
54+ ///
55+ /// The value in the dictionary can be:
56+ /// - `.path` if the build server supports output paths and produced a result
57+ /// - `.notSupported` if the build server does not support output paths.
58+ /// - `nil` if the build server supports output paths but did not return an output path for this file in this target.
59+ package var targetsToOutputPaths : [ BuildTargetIdentifier : OutputPath ? ]
60+
3061 /// The targets that this source file is a member of
31- package var targets : Set < BuildTargetIdentifier >
62+ package var targets : some Collection < BuildTargetIdentifier > & Sendable { targetsToOutputPaths . keys }
3263
3364 /// `true` if this file belongs to the root project that the user is working on. It is false, if the file belongs
3465 /// to a dependency of the project.
@@ -48,8 +79,24 @@ package struct SourceFileInfo: Sendable {
4879 guard let other else {
4980 return self
5081 }
82+ let mergedTargetsToOutputPaths = targetsToOutputPaths. merging (
83+ other. targetsToOutputPaths,
84+ uniquingKeysWith: { lhs, rhs in
85+ if lhs == rhs {
86+ return lhs
87+ }
88+ logger. error ( " Received mismatching output files: \( lhs? . forLogging) vs \( rhs? . forLogging) " )
89+ // Deterministically pick an output file if they mismatch. But really, this shouldn't happen.
90+ switch ( lhs, rhs) {
91+ case ( let lhs? , nil ) : return lhs
92+ case ( nil , let rhs? ) : return rhs
93+ case ( nil , nil ) : return nil // Should be handled above already
94+ case ( let lhs? , let rhs? ) : return min ( lhs, rhs)
95+ }
96+ }
97+ )
5198 return SourceFileInfo (
52- targets : targets . union ( other . targets ) ,
99+ targetsToOutputPaths : mergedTargetsToOutputPaths ,
53100 isPartOfRootProject: other. isPartOfRootProject || isPartOfRootProject,
54101 mayContainTests: other. mayContainTests || mayContainTests,
55102 isBuildable: other. isBuildable || isBuildable
@@ -69,15 +116,6 @@ private struct BuildTargetInfo {
69116 var dependents : Set < BuildTargetIdentifier >
70117}
71118
72- fileprivate extension SourceItem {
73- var sourceKitData : SourceKitSourceItemData ? {
74- guard dataKind == . sourceKit else {
75- return nil
76- }
77- return SourceKitSourceItemData ( fromLSPAny: data)
78- }
79- }
80-
81119fileprivate extension BuildTarget {
82120 var sourceKitData : SourceKitBuildTarget ? {
83121 guard dataKind == . sourceKit else {
@@ -743,26 +781,52 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
743781 return languageFromBuildSystem ?? Language ( inferredFromFileExtension: document)
744782 }
745783
746- /// Returns all the targets that the document is part of .
747- package func targets ( for document: DocumentURI ) async -> Set < BuildTargetIdentifier > {
784+ /// Retrieve information about the given source file within the build server .
785+ package func sourceFileInfo ( for document: DocumentURI ) async -> SourceFileInfo ? {
748786 return await orLog ( " Getting targets for source file " ) {
749- var result : Set < BuildTargetIdentifier > = [ ]
787+ var result : SourceFileInfo ? = nil
750788 let filesAndDirectories = try await sourceFilesAndDirectories ( )
751- if let targets = filesAndDirectories. files [ document] ? . targets {
752- result. formUnion ( targets )
789+ if let info = filesAndDirectories. files [ document] {
790+ result = result ? . merging ( info ) ?? info
753791 }
754792 if !filesAndDirectories. directories. isEmpty, let documentPathComponents = document. fileURL? . pathComponents {
755793 for (_, ( directoryPathComponents, info) ) in filesAndDirectories. directories {
756794 guard let directoryPathComponents else {
757795 continue
758796 }
759797 if isDescendant ( documentPathComponents, of: directoryPathComponents) {
760- result. formUnion ( info. targets )
798+ result = result ? . merging ( info) ?? info
761799 }
762800 }
763801 }
764802 return result
765- } ?? [ ]
803+ }
804+ }
805+
806+ /// Returns all the targets that the document is part of.
807+ package func targets( for document: DocumentURI ) async -> [ BuildTargetIdentifier ] {
808+ guard let targets = await sourceFileInfo ( for: document) ? . targets else {
809+ return [ ]
810+ }
811+ return Array ( targets)
812+ }
813+
814+ /// The `OutputPath` with which `uri` should be indexed in `target`. This may return:
815+ /// - `.path` if the build server supports output paths and produced a result
816+ /// - `.notSupported` if the build server does not support output paths. In this case we will assume that the index
817+ /// for `uri` is up-to-date if we have any up-to-date unit for it.
818+ /// - `nil` if the build server supports output paths but did not return an output path for `uri` in `target`.
819+ package func outputPath( for document: DocumentURI , in target: BuildTargetIdentifier ) async throws -> OutputPath ? {
820+ guard await initializationData? . outputPathsProvider ?? false else {
821+ // Early exit if the build server doesn't support output paths.
822+ return . notSupported
823+ }
824+ guard let sourceFileInfo = await sourceFileInfo ( for: document) ,
825+ let outputPath = sourceFileInfo. targetsToOutputPaths [ target]
826+ else {
827+ return nil
828+ }
829+ return outputPath
766830 }
767831
768832 /// Returns the `BuildTargetIdentifier` that should be used for semantic functionality of the given document.
@@ -1045,7 +1109,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
10451109
10461110 /// Returns the list of targets that might depend on the given target and that need to be re-prepared when a file in
10471111 /// `target` is modified.
1048- package func targets( dependingOn targetIds: Set < BuildTargetIdentifier > ) async -> [ BuildTargetIdentifier ] {
1112+ package func targets( dependingOn targetIds: some Collection < BuildTargetIdentifier > ) async -> [ BuildTargetIdentifier ] {
10491113 guard
10501114 let buildTargets = await orLog ( " Getting build targets for dependents " , { try await self . buildTargets ( ) } )
10511115 else {
@@ -1144,7 +1208,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
11441208 /// Return the output paths for all source files known to the build server.
11451209 ///
11461210 /// See `SourceKitSourceItemData.outputFilePath` for details.
1147- package func outputPathInAllTargets ( ) async throws -> [ String ] {
1211+ package func outputPathsInAllTargets ( ) async throws -> [ String ] {
11481212 return try await outputPaths ( in: Set ( buildTargets ( ) . map ( \. key) ) )
11491213 }
11501214
@@ -1180,6 +1244,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
11801244 /// - Important: This method returns both buildable and non-buildable source files. Callers need to check
11811245 /// `SourceFileInfo.isBuildable` if they are only interested in buildable source files.
11821246 private func sourceFilesAndDirectories( ) async throws -> SourceFilesAndDirectories {
1247+ let supportsOutputPaths = await initializationData? . outputPathsProvider ?? false
1248+
11831249 return try await cachedSourceFilesAndDirectories. get (
11841250 SourceFilesAndDirectoriesKey ( ) ,
11851251 isolation: self
@@ -1194,12 +1260,21 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
11941260 let isPartOfRootProject = !( target? . tags. contains ( . dependency) ?? false )
11951261 let mayContainTests = target? . tags. contains ( . test) ?? true
11961262 for sourceItem in sourcesItem. sources {
1263+ let sourceKitData = sourceItem. sourceKitData
1264+ let outputPath : OutputPath ? =
1265+ if !supportsOutputPaths {
1266+ . notSupported
1267+ } else if let outputPath = sourceKitData? . outputPath {
1268+ . path( outputPath)
1269+ } else {
1270+ nil
1271+ }
11971272 let info = SourceFileInfo (
1198- targets : [ sourcesItem. target] ,
1273+ targetsToOutputPaths : [ sourcesItem. target: outputPath ] ,
11991274 isPartOfRootProject: isPartOfRootProject,
12001275 mayContainTests: mayContainTests,
12011276 isBuildable: !( target? . tags. contains ( . notBuildable) ?? false )
1202- && !( sourceItem . sourceKitData? . isHeader ?? false )
1277+ && !( sourceKitData? . isHeader ?? false )
12031278 )
12041279 switch sourceItem. kind {
12051280 case . file:
0 commit comments