@@ -110,57 +110,38 @@ package struct SwiftPMTestHooks: Sendable {
110
110
/// Package Manager (SwiftPM) package. The settings are determined by loading the Package.swift
111
111
/// manifest using `libSwiftPM` and constructing a build plan using the default (debug) parameters.
112
112
package actor SwiftPMBuildSystem {
113
-
114
113
package enum Error : Swift . Error {
115
-
116
114
/// Could not find a manifest (Package.swift file). This is not a package.
117
115
case noManifest( workspacePath: TSCAbsolutePath )
118
116
119
117
/// Could not determine an appropriate toolchain for swiftpm to use for manifest loading.
120
118
case cannotDetermineHostToolchain
121
119
}
122
120
121
+ // MARK: Integration with SourceKit-LSP
122
+
123
+ /// Options that allow the user to pass extra compiler flags.
124
+ private let options : SourceKitLSPOptions
125
+
126
+ private let testHooks : SwiftPMTestHooks
127
+
123
128
/// Delegate to handle any build system events.
124
129
package weak var delegate : BuildSystemIntegration . BuildSystemDelegate ? = nil
125
130
126
131
package func setDelegate( _ delegate: BuildSystemIntegration . BuildSystemDelegate ? ) async {
127
132
self . delegate = delegate
128
133
}
129
134
135
+ /// This callback is informed when `reloadPackage` starts and ends executing.
136
+ private var reloadPackageStatusCallback : ( ReloadPackageStatus ) async -> Void
137
+
130
138
/// Callbacks that should be called if the list of possible test files has changed.
131
139
private var testFilesDidChangeCallbacks : [ ( ) async -> Void ] = [ ]
132
140
133
- private let workspacePath : TSCAbsolutePath
134
-
135
- /// Options that allow the user to pass extra compiler flags.
136
- private let options : SourceKitLSPOptions
137
-
138
- /// The directory containing `Package.swift`.
139
- package var projectRoot : TSCAbsolutePath
140
-
141
- private var buildDescription : SourceKitLSPAPI . BuildDescription ?
142
- private var modulesGraph : ModulesGraph
143
- private let workspace : Workspace
144
- package let toolsBuildParameters : BuildParameters
145
- package let destinationBuildParameters : BuildParameters
146
- private let fileSystem : FileSystem
147
- private let toolchain : Toolchain
148
-
149
- private var fileToTargets : [ DocumentURI : [ SwiftBuildTarget ] ] = [ : ]
150
- private var sourceDirToTargets : [ DocumentURI : [ SwiftBuildTarget ] ] = [ : ]
151
-
152
- /// Maps configured targets ids to their SwiftPM build target as well as an index in their topological sorting.
153
- ///
154
- /// Targets with lower index are more low level, ie. targets with higher indices depend on targets with lower indices.
155
- private var targets : [ ConfiguredTarget : ( index: Int , buildTarget: SwiftBuildTarget ) ] = [ : ]
156
-
157
141
/// The URIs for which the delegate has registered for change notifications,
158
142
/// mapped to the language the delegate specified when registering for change notifications.
159
143
private var watchedFiles : Set < DocumentURI > = [ ]
160
144
161
- /// This callback is informed when `reloadPackage` starts and ends executing.
162
- private var reloadPackageStatusCallback : ( ReloadPackageStatus ) async -> Void
163
-
164
145
/// Debounces calls to `delegate.filesDependenciesUpdated`.
165
146
///
166
147
/// This is to ensure we don't call `filesDependenciesUpdated` for the same file multiple time if the client does not
@@ -171,16 +152,43 @@ package actor SwiftPMBuildSystem {
171
152
/// Force-unwrapped optional because initializing it requires access to `self`.
172
153
private var fileDependenciesUpdatedDebouncer : Debouncer < Set < DocumentURI > > ! = nil
173
154
155
+ /// Whether the `SwiftPMBuildSystem` is pointed at a `.index-build` directory that's independent of the
156
+ /// user's build.
157
+ private var isForIndexBuild : Bool { options. backgroundIndexingOrDefault }
158
+
159
+ // MARK: Build system options (set once and not modified)
160
+
161
+ /// The path at which the LSP workspace is opened. This might be a subdirectory of the path that contains the
162
+ /// `Package.swift`.
163
+ private let workspacePath : TSCAbsolutePath
164
+
165
+ /// The directory containing `Package.swift`.
166
+ package let projectRoot : TSCAbsolutePath
167
+
168
+ package let toolsBuildParameters : BuildParameters
169
+ package let destinationBuildParameters : BuildParameters
170
+
171
+ private let fileSystem : FileSystem
172
+ private let toolchain : Toolchain
173
+ private let workspace : Workspace
174
+
174
175
/// A `ObservabilitySystem` from `SwiftPM` that logs.
175
176
private let observabilitySystem = ObservabilitySystem ( { scope, diagnostic in
176
177
logger. log ( level: diagnostic. severity. asLogLevel, " SwiftPM log: \( diagnostic. description) " )
177
178
} )
178
179
179
- /// Whether the `SwiftPMBuildSystem` is pointed at a `.index-build` directory that's independent of the
180
- /// user's build.
181
- private var isForIndexBuild : Bool { options. backgroundIndexingOrDefault }
180
+ // MARK: Build system state (modified on package reload)
182
181
183
- private let testHooks : SwiftPMTestHooks
182
+ /// The entry point via with we can access the `SourceKitLSPAPI` provided by SwiftPM.
183
+ private var buildDescription : SourceKitLSPAPI . BuildDescription ?
184
+
185
+ /// Maps source and header files to the target that include them.
186
+ private var fileToTargets : [ DocumentURI : Set < ConfiguredTarget > ] = [ : ]
187
+
188
+ /// Maps configured targets ids to their SwiftPM build target as well as the depth at which they occur in the build
189
+ /// graph. Top level targets on which no other target depends have a depth of `1`. Targets with dependencies have a
190
+ /// greater depth.
191
+ private var targets : [ ConfiguredTarget : ( buildTarget: SwiftBuildTarget , depth: Int ) ] = [ : ]
184
192
185
193
/// Creates a build system using the Swift Package Manager, if this workspace is a package.
186
194
///
@@ -306,12 +314,6 @@ package actor SwiftPMBuildSystem {
306
314
flags: buildFlags
307
315
)
308
316
309
- self . modulesGraph = try ModulesGraph (
310
- rootPackages: [ ] ,
311
- packages: IdentifiableSet ( ) ,
312
- dependencies: [ ] ,
313
- binaryArtifacts: [ : ]
314
- )
315
317
self . reloadPackageStatusCallback = reloadPackageStatusCallback
316
318
317
319
// The debounce duration of 500ms was chosen arbitrarily without scientific research.
@@ -394,45 +396,26 @@ extension SwiftPMBuildSystem {
394
396
/// Make sure to execute any throwing statements before setting any
395
397
/// properties because otherwise we might end up in an inconsistent state
396
398
/// with only some properties modified.
397
- self . modulesGraph = modulesGraph
398
-
399
- self . targets = Dictionary (
400
- try buildDescription. allTargetsInTopologicalOrder ( in: modulesGraph) . enumerated ( ) . map { ( index, target) in
401
- return ( key: ConfiguredTarget ( target) , value: ( index, target) )
402
- } ,
403
- uniquingKeysWith: { first, second in
404
- logger. fault ( " Found two targets with the same name \( first. buildTarget. name) " )
405
- return second
406
- }
407
- )
408
399
409
- self . fileToTargets = [ DocumentURI: [ SwiftBuildTarget] ] (
410
- modulesGraph. allModules. flatMap { target in
411
- return target. sources. paths. compactMap { ( filePath) -> ( key: DocumentURI , value: [ SwiftBuildTarget ] ) ? in
412
- guard let buildTarget = buildDescription. getBuildTarget ( for: target, in: modulesGraph) else {
413
- return nil
414
- }
415
- return ( key: DocumentURI ( filePath. asURL) , value: [ buildTarget] )
400
+ self . targets = [ : ]
401
+ self . fileToTargets = [ : ]
402
+ buildDescription. traverseModules { buildTarget, parent, depth in
403
+ let configuredTarget = ConfiguredTarget ( buildTarget)
404
+ var depth = depth
405
+ if let existingDepth = targets [ configuredTarget] ? . depth {
406
+ depth = max ( existingDepth, depth)
407
+ } else {
408
+ for source in buildTarget. sources + buildTarget. headers {
409
+ fileToTargets [ DocumentURI ( source) , default: [ ] ] . insert ( configuredTarget)
416
410
}
417
- } ,
418
- uniquingKeysWith: { $0 + $1 }
419
- )
420
-
421
- self . sourceDirToTargets = [ DocumentURI: [ SwiftBuildTarget] ] (
422
- modulesGraph. allModules. compactMap { ( target) -> ( DocumentURI , [ SwiftBuildTarget ] ) ? in
423
- guard let buildTarget = buildDescription. getBuildTarget ( for: target, in: modulesGraph) else {
424
- return nil
425
- }
426
- return ( key: DocumentURI ( target. sources. root. asURL) , value: [ buildTarget] )
427
- } ,
428
- uniquingKeysWith: { $0 + $1 }
429
- )
411
+ }
412
+ targets [ configuredTarget] = ( buildTarget, depth)
413
+ }
430
414
431
- guard let delegate = self . delegate else {
432
- return
415
+ if let delegate = self . delegate {
416
+ await delegate. fileBuildSettingsChanged ( self . watchedFiles)
417
+ await delegate. fileHandlingCapabilityChanged ( )
433
418
}
434
- await delegate. fileBuildSettingsChanged ( self . watchedFiles)
435
- await delegate. fileHandlingCapabilityChanged ( )
436
419
for testFilesDidChangeCallback in testFilesDidChangeCallbacks {
437
420
await testFilesDidChangeCallback ( )
438
421
}
@@ -546,7 +529,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
546
529
547
530
let targets = buildTargets ( for: uri)
548
531
if !targets. isEmpty {
549
- return targets. map ( ConfiguredTarget . init )
532
+ return targets. sorted { ( $0 . targetID , $0 . runDestinationID ) < ( $1 . targetID , $1 . runDestinationID ) }
550
533
}
551
534
552
535
if path. basename == " Package.swift "
@@ -557,10 +540,6 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
557
540
return [ ConfiguredTarget . forPackageManifest]
558
541
}
559
542
560
- if let targets = try ? inferredTargets ( for: path) {
561
- return targets
562
- }
563
-
564
543
return [ ]
565
544
}
566
545
@@ -570,27 +549,27 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
570
549
571
550
package func topologicalSort( of targets: [ ConfiguredTarget ] ) -> [ ConfiguredTarget ] ? {
572
551
return targets. sorted { ( lhs: ConfiguredTarget , rhs: ConfiguredTarget ) -> Bool in
573
- let lhsIndex = self . targets [ lhs] ? . index ?? self . targets . count
574
- let rhsIndex = self . targets [ rhs] ? . index ?? self . targets . count
575
- return lhsIndex < rhsIndex
552
+ let lhsDepth = self . targets [ lhs] ? . depth ?? 0
553
+ let rhsDepth = self . targets [ rhs] ? . depth ?? 0
554
+ return lhsDepth > rhsDepth
576
555
}
577
556
}
578
557
579
558
package func targets( dependingOn targets: [ ConfiguredTarget ] ) -> [ ConfiguredTarget ] ? {
580
- let targetIndices = targets. compactMap { self . targets [ $0] ? . index }
581
- let minimumTargetIndex : Int ?
582
- if targetIndices . count == targets. count {
583
- minimumTargetIndex = targetIndices . min ( )
559
+ let targetDepths = targets. compactMap { self . targets [ $0] ? . depth }
560
+ let minimumTargetDepth : Int ?
561
+ if targetDepths . count == targets. count {
562
+ minimumTargetDepth = targetDepths . max ( )
584
563
} else {
585
564
// One of the targets didn't have an entry in self.targets. We don't know what might depend on it.
586
- minimumTargetIndex = nil
565
+ minimumTargetDepth = nil
587
566
}
588
567
589
568
// Files that occur before the target in the topological sorting don't depend on it.
590
569
// Ideally, we should consult the dependency graph here for more accurate dependency analysis instead of relying on
591
570
// a flattened list (https://github.com/swiftlang/sourcekit-lsp/issues/1312).
592
571
return self . targets. compactMap { ( configuredTarget, value) -> ConfiguredTarget ? in
593
- if let minimumTargetIndex , value. index <= minimumTargetIndex {
572
+ if let minimumTargetDepth , value. depth >= minimumTargetDepth {
594
573
return nil
595
574
}
596
575
return configuredTarget
@@ -715,7 +694,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
715
694
}
716
695
717
696
/// Returns the resolved target descriptions for the given file, if one is known.
718
- private func buildTargets( for file: DocumentURI ) -> [ SwiftBuildTarget ] {
697
+ private func buildTargets( for file: DocumentURI ) -> Set < ConfiguredTarget > {
719
698
if let targets = fileToTargets [ file] {
720
699
return targets
721
700
}
@@ -765,7 +744,9 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
765
744
guard event. uri. fileURL? . pathExtension == " swift " , let targets = fileToTargets [ event. uri] else {
766
745
continue
767
746
}
768
- filesWithUpdatedDependencies. formUnion ( targets. flatMap ( \. sources) . map ( DocumentURI . init) )
747
+ for target in targets {
748
+ filesWithUpdatedDependencies. formUnion ( self . targets [ target] ? . buildTarget. sources. map ( DocumentURI . init) ?? [ ] )
749
+ }
769
750
}
770
751
771
752
// If a `.swiftmodule` file is updated, this means that we have performed a build / are
@@ -791,66 +772,28 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
791
772
}
792
773
793
774
package func sourceFiles( ) -> [ SourceFileInfo ] {
794
- return fileToTargets. compactMap { ( uri, targets) -> SourceFileInfo ? in
795
- // We should only set mayContainTests to `true` for files from test targets
796
- // (https://github.com/swiftlang/sourcekit-lsp/issues/1174).
797
- return SourceFileInfo (
798
- uri: uri,
799
- isPartOfRootProject: targets. contains ( where: \. isPartOfRootPackage) ,
800
- mayContainTests: true
801
- )
775
+ var sourceFiles : [ DocumentURI : SourceFileInfo ] = [ : ]
776
+ for (buildTarget, depth) in self . targets. values {
777
+ for sourceFile in buildTarget. sources {
778
+ let uri = DocumentURI ( sourceFile)
779
+ sourceFiles [ uri] = SourceFileInfo (
780
+ uri: uri,
781
+ isPartOfRootProject: depth == 1 || ( sourceFiles [ uri] ? . isPartOfRootProject ?? false ) ,
782
+ mayContainTests: true
783
+ )
784
+ }
802
785
}
786
+ return sourceFiles. values. sorted { $0. uri. pseudoPath < $1. uri. pseudoPath }
803
787
}
804
788
805
789
package func addSourceFilesDidChangeCallback( _ callback: @Sendable @escaping ( ) async -> Void ) async {
806
790
testFilesDidChangeCallbacks. append ( callback)
807
791
}
808
- }
809
-
810
- extension SwiftPMBuildSystem {
811
-
812
- // MARK: Implementation details
813
792
814
793
/// Retrieve settings for a package manifest (Package.swift).
815
794
private func settings( forPackageManifest path: AbsolutePath ) throws -> FileBuildSettings ? {
816
- func impl( _ path: AbsolutePath ) -> FileBuildSettings ? {
817
- for package in modulesGraph. packages where path == package . manifest. path {
818
- let compilerArgs = workspace. interpreterFlags ( for: package . path) + [ path. pathString]
819
- return FileBuildSettings ( compilerArguments: compilerArgs)
820
- }
821
- return nil
822
- }
823
-
824
- if let result = impl ( path) {
825
- return result
826
- }
827
-
828
- let canonicalPath = try resolveSymlinks ( path)
829
- return canonicalPath == path ? nil : impl ( canonicalPath)
830
- }
831
-
832
- /// This finds the target a file belongs to based on its location in the file system.
833
- ///
834
- /// This is primarily intended to find the target a header belongs to.
835
- private func inferredTargets( for path: AbsolutePath ) throws -> [ ConfiguredTarget ] {
836
- func impl( _ path: AbsolutePath ) throws -> [ ConfiguredTarget ] {
837
- var dir = path. parentDirectory
838
- while !dir. isRoot {
839
- if let buildTargets = sourceDirToTargets [ DocumentURI ( dir. asURL) ] {
840
- return buildTargets. map ( ConfiguredTarget . init)
841
- }
842
- dir = dir. parentDirectory
843
- }
844
- return [ ]
845
- }
846
-
847
- let result = try impl ( path)
848
- if !result. isEmpty {
849
- return result
850
- }
851
-
852
- let canonicalPath = try resolveSymlinks ( path)
853
- return try canonicalPath == path ? [ ] : impl ( canonicalPath)
795
+ let compilerArgs = workspace. interpreterFlags ( for: path. parentDirectory) + [ path. pathString]
796
+ return FileBuildSettings ( compilerArguments: compilerArgs)
854
797
}
855
798
}
856
799
0 commit comments