@@ -110,57 +110,38 @@ package struct SwiftPMTestHooks: Sendable {
110110/// Package Manager (SwiftPM) package. The settings are determined by loading the Package.swift
111111/// manifest using `libSwiftPM` and constructing a build plan using the default (debug) parameters.
112112package actor SwiftPMBuildSystem {
113-
114113 package enum Error : Swift . Error {
115-
116114 /// Could not find a manifest (Package.swift file). This is not a package.
117115 case noManifest( workspacePath: TSCAbsolutePath )
118116
119117 /// Could not determine an appropriate toolchain for swiftpm to use for manifest loading.
120118 case cannotDetermineHostToolchain
121119 }
122120
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+
123128 /// Delegate to handle any build system events.
124129 package weak var delegate : BuildSystemIntegration . BuildSystemDelegate ? = nil
125130
126131 package func setDelegate( _ delegate: BuildSystemIntegration . BuildSystemDelegate ? ) async {
127132 self . delegate = delegate
128133 }
129134
135+ /// This callback is informed when `reloadPackage` starts and ends executing.
136+ private var reloadPackageStatusCallback : ( ReloadPackageStatus ) async -> Void
137+
130138 /// Callbacks that should be called if the list of possible test files has changed.
131139 private var testFilesDidChangeCallbacks : [ ( ) async -> Void ] = [ ]
132140
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-
157141 /// The URIs for which the delegate has registered for change notifications,
158142 /// mapped to the language the delegate specified when registering for change notifications.
159143 private var watchedFiles : Set < DocumentURI > = [ ]
160144
161- /// This callback is informed when `reloadPackage` starts and ends executing.
162- private var reloadPackageStatusCallback : ( ReloadPackageStatus ) async -> Void
163-
164145 /// Debounces calls to `delegate.filesDependenciesUpdated`.
165146 ///
166147 /// 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 {
171152 /// Force-unwrapped optional because initializing it requires access to `self`.
172153 private var fileDependenciesUpdatedDebouncer : Debouncer < Set < DocumentURI > > ! = nil
173154
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+
174175 /// A `ObservabilitySystem` from `SwiftPM` that logs.
175176 private let observabilitySystem = ObservabilitySystem ( { scope, diagnostic in
176177 logger. log ( level: diagnostic. severity. asLogLevel, " SwiftPM log: \( diagnostic. description) " )
177178 } )
178179
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)
182181
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 ) ] = [ : ]
184192
185193 /// Creates a build system using the Swift Package Manager, if this workspace is a package.
186194 ///
@@ -306,12 +314,6 @@ package actor SwiftPMBuildSystem {
306314 flags: buildFlags
307315 )
308316
309- self . modulesGraph = try ModulesGraph (
310- rootPackages: [ ] ,
311- packages: IdentifiableSet ( ) ,
312- dependencies: [ ] ,
313- binaryArtifacts: [ : ]
314- )
315317 self . reloadPackageStatusCallback = reloadPackageStatusCallback
316318
317319 // The debounce duration of 500ms was chosen arbitrarily without scientific research.
@@ -394,45 +396,26 @@ extension SwiftPMBuildSystem {
394396 /// Make sure to execute any throwing statements before setting any
395397 /// properties because otherwise we might end up in an inconsistent state
396398 /// 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- )
408399
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)
416410 }
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+ }
430414
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 ( )
433418 }
434- await delegate. fileBuildSettingsChanged ( self . watchedFiles)
435- await delegate. fileHandlingCapabilityChanged ( )
436419 for testFilesDidChangeCallback in testFilesDidChangeCallbacks {
437420 await testFilesDidChangeCallback ( )
438421 }
@@ -546,7 +529,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
546529
547530 let targets = buildTargets ( for: uri)
548531 if !targets. isEmpty {
549- return targets. map ( ConfiguredTarget . init )
532+ return targets. sorted { ( $0 . targetID , $0 . runDestinationID ) < ( $1 . targetID , $1 . runDestinationID ) }
550533 }
551534
552535 if path. basename == " Package.swift "
@@ -557,10 +540,6 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
557540 return [ ConfiguredTarget . forPackageManifest]
558541 }
559542
560- if let targets = try ? inferredTargets ( for: path) {
561- return targets
562- }
563-
564543 return [ ]
565544 }
566545
@@ -570,27 +549,27 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
570549
571550 package func topologicalSort( of targets: [ ConfiguredTarget ] ) -> [ ConfiguredTarget ] ? {
572551 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
576555 }
577556 }
578557
579558 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 ( )
584563 } else {
585564 // 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
587566 }
588567
589568 // Files that occur before the target in the topological sorting don't depend on it.
590569 // Ideally, we should consult the dependency graph here for more accurate dependency analysis instead of relying on
591570 // a flattened list (https://github.com/swiftlang/sourcekit-lsp/issues/1312).
592571 return self . targets. compactMap { ( configuredTarget, value) -> ConfiguredTarget ? in
593- if let minimumTargetIndex , value. index <= minimumTargetIndex {
572+ if let minimumTargetDepth , value. depth >= minimumTargetDepth {
594573 return nil
595574 }
596575 return configuredTarget
@@ -715,7 +694,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
715694 }
716695
717696 /// 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 > {
719698 if let targets = fileToTargets [ file] {
720699 return targets
721700 }
@@ -765,7 +744,9 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
765744 guard event. uri. fileURL? . pathExtension == " swift " , let targets = fileToTargets [ event. uri] else {
766745 continue
767746 }
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+ }
769750 }
770751
771752 // If a `.swiftmodule` file is updated, this means that we have performed a build / are
@@ -791,66 +772,28 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuildSystem {
791772 }
792773
793774 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+ }
802785 }
786+ return sourceFiles. values. sorted { $0. uri. pseudoPath < $1. uri. pseudoPath }
803787 }
804788
805789 package func addSourceFilesDidChangeCallback( _ callback: @Sendable @escaping ( ) async -> Void ) async {
806790 testFilesDidChangeCallbacks. append ( callback)
807791 }
808- }
809-
810- extension SwiftPMBuildSystem {
811-
812- // MARK: Implementation details
813792
814793 /// Retrieve settings for a package manifest (Package.swift).
815794 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)
854797 }
855798}
856799
0 commit comments