@@ -42,9 +42,40 @@ import struct TSCBasic.RelativePath
42
42
43
43
fileprivate typealias RequestCache < Request: RequestType & Hashable > = Cache < Request , Request . Response >
44
44
45
+ /// An output path returned from the build server in the `SourceItem.data.outputPath` field.
46
+ package enum OutputPath : Hashable , Comparable , CustomLogStringConvertible {
47
+ /// An output path returned from the build server.
48
+ case path( String )
49
+
50
+ /// The build server does not support output paths.
51
+ case notSupported
52
+
53
+ package var description : String {
54
+ switch self {
55
+ case . notSupported: return " <output path not supported> "
56
+ case . path( let path) : return path
57
+ }
58
+ }
59
+
60
+ package var redactedDescription : String {
61
+ switch self {
62
+ case . notSupported: return " <output path not supported> "
63
+ case . path( let path) : return path. hashForLogging
64
+ }
65
+ }
66
+ }
67
+
45
68
package struct SourceFileInfo : Sendable {
69
+ /// Maps the targets that this source file is a member of to the output path the file has within that target.
70
+ ///
71
+ /// The value in the dictionary can be:
72
+ /// - `.path` if the build server supports output paths and produced a result
73
+ /// - `.notSupported` if the build server does not support output paths.
74
+ /// - `nil` if the build server supports output paths but did not return an output path for this file in this target.
75
+ package var targetsToOutputPaths : [ BuildTargetIdentifier : OutputPath ? ]
76
+
46
77
/// The targets that this source file is a member of
47
- package var targets : Set < BuildTargetIdentifier >
78
+ package var targets : some Collection < BuildTargetIdentifier > & Sendable { targetsToOutputPaths . keys }
48
79
49
80
/// `true` if this file belongs to the root project that the user is working on. It is false, if the file belongs
50
81
/// to a dependency of the project.
@@ -64,8 +95,24 @@ package struct SourceFileInfo: Sendable {
64
95
guard let other else {
65
96
return self
66
97
}
98
+ let mergedTargetsToOutputPaths = targetsToOutputPaths. merging (
99
+ other. targetsToOutputPaths,
100
+ uniquingKeysWith: { lhs, rhs in
101
+ if lhs == rhs {
102
+ return lhs
103
+ }
104
+ logger. error ( " Received mismatching output files: \( lhs? . forLogging) vs \( rhs? . forLogging) " )
105
+ // Deterministically pick an output file if they mismatch. But really, this shouldn't happen.
106
+ switch ( lhs, rhs) {
107
+ case ( let lhs? , nil ) : return lhs
108
+ case ( nil , let rhs? ) : return rhs
109
+ case ( nil , nil ) : return nil // Should be handled above already
110
+ case ( let lhs? , let rhs? ) : return min ( lhs, rhs)
111
+ }
112
+ }
113
+ )
67
114
return SourceFileInfo (
68
- targets : targets . union ( other . targets ) ,
115
+ targetsToOutputPaths : mergedTargetsToOutputPaths ,
69
116
isPartOfRootProject: other. isPartOfRootProject || isPartOfRootProject,
70
117
mayContainTests: other. mayContainTests || mayContainTests,
71
118
isBuildable: other. isBuildable || isBuildable
@@ -85,15 +132,6 @@ private struct BuildTargetInfo {
85
132
var dependents : Set < BuildTargetIdentifier >
86
133
}
87
134
88
- fileprivate extension SourceItem {
89
- var sourceKitData : SourceKitSourceItemData ? {
90
- guard dataKind == . sourceKit else {
91
- return nil
92
- }
93
- return SourceKitSourceItemData ( fromLSPAny: data)
94
- }
95
- }
96
-
97
135
fileprivate extension BuildTarget {
98
136
var sourceKitData : SourceKitBuildTarget ? {
99
137
guard dataKind == . sourceKit else {
@@ -759,26 +797,52 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
759
797
return languageFromBuildSystem ?? Language ( inferredFromFileExtension: document)
760
798
}
761
799
762
- /// Returns all the targets that the document is part of .
763
- package func targets ( for document: DocumentURI ) async -> Set < BuildTargetIdentifier > {
800
+ /// Retrieve information about the given source file within the build server .
801
+ package func sourceFileInfo ( for document: DocumentURI ) async -> SourceFileInfo ? {
764
802
return await orLog ( " Getting targets for source file " ) {
765
- var result : Set < BuildTargetIdentifier > = [ ]
803
+ var result : SourceFileInfo ? = nil
766
804
let filesAndDirectories = try await sourceFilesAndDirectories ( )
767
- if let targets = filesAndDirectories. files [ document] ? . targets {
768
- result. formUnion ( targets )
805
+ if let info = filesAndDirectories. files [ document] {
806
+ result = result ? . merging ( info ) ?? info
769
807
}
770
808
if !filesAndDirectories. directories. isEmpty, let documentPathComponents = document. fileURL? . pathComponents {
771
809
for (_, ( directoryPathComponents, info) ) in filesAndDirectories. directories {
772
810
guard let directoryPathComponents else {
773
811
continue
774
812
}
775
813
if isDescendant ( documentPathComponents, of: directoryPathComponents) {
776
- result. formUnion ( info. targets )
814
+ result = result ? . merging ( info) ?? info
777
815
}
778
816
}
779
817
}
780
818
return result
781
- } ?? [ ]
819
+ }
820
+ }
821
+
822
+ /// Returns all the targets that the document is part of.
823
+ package func targets( for document: DocumentURI ) async -> [ BuildTargetIdentifier ] {
824
+ guard let targets = await sourceFileInfo ( for: document) ? . targets else {
825
+ return [ ]
826
+ }
827
+ return Array ( targets)
828
+ }
829
+
830
+ /// The `OutputPath` with which `uri` should be indexed in `target`. This may return:
831
+ /// - `.path` if the build server supports output paths and produced a result
832
+ /// - `.notSupported` if the build server does not support output paths. In this case we will assume that the index
833
+ /// for `uri` is up-to-date if we have any up-to-date unit for it.
834
+ /// - `nil` if the build server supports output paths but did not return an output path for `uri` in `target`.
835
+ package func outputPath( for document: DocumentURI , in target: BuildTargetIdentifier ) async throws -> OutputPath ? {
836
+ guard await initializationData? . outputPathsProvider ?? false else {
837
+ // Early exit if the build server doesn't support output paths.
838
+ return . notSupported
839
+ }
840
+ guard let sourceFileInfo = await sourceFileInfo ( for: document) ,
841
+ let outputPath = sourceFileInfo. targetsToOutputPaths [ target]
842
+ else {
843
+ return nil
844
+ }
845
+ return outputPath
782
846
}
783
847
784
848
/// Returns the `BuildTargetIdentifier` that should be used for semantic functionality of the given document.
@@ -1061,7 +1125,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1061
1125
1062
1126
/// Returns the list of targets that might depend on the given target and that need to be re-prepared when a file in
1063
1127
/// `target` is modified.
1064
- package func targets( dependingOn targetIds: Set < BuildTargetIdentifier > ) async -> [ BuildTargetIdentifier ] {
1128
+ package func targets( dependingOn targetIds: some Collection < BuildTargetIdentifier > ) async -> [ BuildTargetIdentifier ] {
1065
1129
guard
1066
1130
let buildTargets = await orLog ( " Getting build targets for dependents " , { try await self . buildTargets ( ) } )
1067
1131
else {
@@ -1160,7 +1224,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1160
1224
/// Return the output paths for all source files known to the build server.
1161
1225
///
1162
1226
/// See `SourceKitSourceItemData.outputFilePath` for details.
1163
- package func outputPathInAllTargets ( ) async throws -> [ String ] {
1227
+ package func outputPathsInAllTargets ( ) async throws -> [ String ] {
1164
1228
return try await outputPaths ( in: Set ( buildTargets ( ) . map ( \. key) ) )
1165
1229
}
1166
1230
@@ -1196,6 +1260,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1196
1260
/// - Important: This method returns both buildable and non-buildable source files. Callers need to check
1197
1261
/// `SourceFileInfo.isBuildable` if they are only interested in buildable source files.
1198
1262
private func sourceFilesAndDirectories( ) async throws -> SourceFilesAndDirectories {
1263
+ let supportsOutputPaths = await initializationData? . outputPathsProvider ?? false
1264
+
1199
1265
return try await cachedSourceFilesAndDirectories. get (
1200
1266
SourceFilesAndDirectoriesKey ( ) ,
1201
1267
isolation: self
@@ -1210,12 +1276,21 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
1210
1276
let isPartOfRootProject = !( target? . tags. contains ( . dependency) ?? false )
1211
1277
let mayContainTests = target? . tags. contains ( . test) ?? true
1212
1278
for sourceItem in sourcesItem. sources {
1279
+ let sourceKitData = sourceItem. sourceKitData
1280
+ let outputPath : OutputPath ? =
1281
+ if !supportsOutputPaths {
1282
+ . notSupported
1283
+ } else if let outputPath = sourceKitData? . outputPath {
1284
+ . path( outputPath)
1285
+ } else {
1286
+ nil
1287
+ }
1213
1288
let info = SourceFileInfo (
1214
- targets : [ sourcesItem. target] ,
1289
+ targetsToOutputPaths : [ sourcesItem. target: outputPath ] ,
1215
1290
isPartOfRootProject: isPartOfRootProject,
1216
1291
mayContainTests: mayContainTests,
1217
1292
isBuildable: !( target? . tags. contains ( . notBuildable) ?? false )
1218
- && !( sourceItem . sourceKitData? . isHeader ?? false )
1293
+ && !( sourceKitData? . isHeader ?? false )
1219
1294
)
1220
1295
switch sourceItem. kind {
1221
1296
case . file:
0 commit comments