@@ -26,9 +26,40 @@ import struct TSCBasic.RelativePath
26
26
27
27
fileprivate typealias RequestCache < Request: RequestType & Hashable > = Cache < Request , Request . Response >
28
28
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
+
29
52
package 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
+
30
61
/// 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 }
32
63
33
64
/// `true` if this file belongs to the root project that the user is working on. It is false, if the file belongs
34
65
/// to a dependency of the project.
@@ -48,8 +79,24 @@ package struct SourceFileInfo: Sendable {
48
79
guard let other else {
49
80
return self
50
81
}
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
+ )
51
98
return SourceFileInfo (
52
- targets : targets . union ( other . targets ) ,
99
+ targetsToOutputPaths : mergedTargetsToOutputPaths ,
53
100
isPartOfRootProject: other. isPartOfRootProject || isPartOfRootProject,
54
101
mayContainTests: other. mayContainTests || mayContainTests,
55
102
isBuildable: other. isBuildable || isBuildable
@@ -69,15 +116,6 @@ private struct BuildTargetInfo {
69
116
var dependents : Set < BuildTargetIdentifier >
70
117
}
71
118
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
-
81
119
fileprivate extension BuildTarget {
82
120
var sourceKitData : SourceKitBuildTarget ? {
83
121
guard dataKind == . sourceKit else {
@@ -743,26 +781,52 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
743
781
return languageFromBuildSystem ?? Language ( inferredFromFileExtension: document)
744
782
}
745
783
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 ? {
748
786
return await orLog ( " Getting targets for source file " ) {
749
- var result : Set < BuildTargetIdentifier > = [ ]
787
+ var result : SourceFileInfo ? = nil
750
788
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
753
791
}
754
792
if !filesAndDirectories. directories. isEmpty, let documentPathComponents = document. fileURL? . pathComponents {
755
793
for (_, ( directoryPathComponents, info) ) in filesAndDirectories. directories {
756
794
guard let directoryPathComponents else {
757
795
continue
758
796
}
759
797
if isDescendant ( documentPathComponents, of: directoryPathComponents) {
760
- result. formUnion ( info. targets )
798
+ result = result ? . merging ( info) ?? info
761
799
}
762
800
}
763
801
}
764
802
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
766
830
}
767
831
768
832
/// Returns the `BuildTargetIdentifier` that should be used for semantic functionality of the given document.
@@ -1045,7 +1109,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1045
1109
1046
1110
/// Returns the list of targets that might depend on the given target and that need to be re-prepared when a file in
1047
1111
/// `target` is modified.
1048
- package func targets( dependingOn targetIds: Set < BuildTargetIdentifier > ) async -> [ BuildTargetIdentifier ] {
1112
+ package func targets( dependingOn targetIds: some Collection < BuildTargetIdentifier > ) async -> [ BuildTargetIdentifier ] {
1049
1113
guard
1050
1114
let buildTargets = await orLog ( " Getting build targets for dependents " , { try await self . buildTargets ( ) } )
1051
1115
else {
@@ -1144,7 +1208,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1144
1208
/// Return the output paths for all source files known to the build server.
1145
1209
///
1146
1210
/// See `SourceKitSourceItemData.outputFilePath` for details.
1147
- package func outputPathInAllTargets ( ) async throws -> [ String ] {
1211
+ package func outputPathsInAllTargets ( ) async throws -> [ String ] {
1148
1212
return try await outputPaths ( in: Set ( buildTargets ( ) . map ( \. key) ) )
1149
1213
}
1150
1214
@@ -1180,6 +1244,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1180
1244
/// - Important: This method returns both buildable and non-buildable source files. Callers need to check
1181
1245
/// `SourceFileInfo.isBuildable` if they are only interested in buildable source files.
1182
1246
private func sourceFilesAndDirectories( ) async throws -> SourceFilesAndDirectories {
1247
+ let supportsOutputPaths = await initializationData? . outputPathsProvider ?? false
1248
+
1183
1249
return try await cachedSourceFilesAndDirectories. get (
1184
1250
SourceFilesAndDirectoriesKey ( ) ,
1185
1251
isolation: self
@@ -1194,12 +1260,21 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1194
1260
let isPartOfRootProject = !( target? . tags. contains ( . dependency) ?? false )
1195
1261
let mayContainTests = target? . tags. contains ( . test) ?? true
1196
1262
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
+ }
1197
1272
let info = SourceFileInfo (
1198
- targets : [ sourcesItem. target] ,
1273
+ targetsToOutputPaths : [ sourcesItem. target: outputPath ] ,
1199
1274
isPartOfRootProject: isPartOfRootProject,
1200
1275
mayContainTests: mayContainTests,
1201
1276
isBuildable: !( target? . tags. contains ( . notBuildable) ?? false )
1202
- && !( sourceItem . sourceKitData? . isHeader ?? false )
1277
+ && !( sourceKitData? . isHeader ?? false )
1203
1278
)
1204
1279
switch sourceItem. kind {
1205
1280
case . file:
0 commit comments