Skip to content

Commit cc11fe1

Browse files
committed
Implement topologicalSort inside BuildSystemManager using dependency information
1 parent bb96ff4 commit cc11fe1

File tree

5 files changed

+59
-59
lines changed

5 files changed

+59
-59
lines changed

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,6 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
336336

337337
package func waitForUpToDateBuildGraph() async {}
338338

339-
package func topologicalSort(of targets: [BuildTargetIdentifier]) async -> [BuildTargetIdentifier]? {
340-
return nil
341-
}
342-
343339
package func addSourceFilesDidChangeCallback(_ callback: @escaping () async -> Void) {
344340
// BuildServerBuildSystem does not support syntactic test discovery or background indexing.
345341
// (https://github.com/swiftlang/sourcekit-lsp/issues/1173).

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
118118

119119
private var cachedTargetSources = RequestCache<BuildTargetSourcesRequest>()
120120

121+
private var cachedTargetDepths: (buildTargets: [BuildTarget], depths: [BuildTargetIdentifier: Int])? = nil
122+
121123
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
122124
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
123125
/// was found.
@@ -485,8 +487,50 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
485487
await self.buildSystem?.underlyingBuildSystem.waitForUpToDateBuildGraph()
486488
}
487489

490+
/// The root targets of the project have depth of 0 and all target dependencies have a greater depth than the target
491+
// itself.
492+
private func targetDepths(for buildTargets: [BuildTarget]) -> [BuildTargetIdentifier: Int] {
493+
if let cachedTargetDepths, cachedTargetDepths.buildTargets == buildTargets {
494+
return cachedTargetDepths.depths
495+
}
496+
var nonRoots: Set<BuildTargetIdentifier> = []
497+
for buildTarget in buildTargets {
498+
nonRoots.formUnion(buildTarget.dependencies)
499+
}
500+
let targetsById = Dictionary(elements: buildTargets, keyedBy: \.id)
501+
var depths: [BuildTargetIdentifier: Int] = [:]
502+
let rootTargets = buildTargets.filter { !nonRoots.contains($0.id) }
503+
var worksList: [(target: BuildTargetIdentifier, depth: Int)] = rootTargets.map { ($0.id, 0) }
504+
while let (target, depth) = worksList.popLast() {
505+
depths[target] = max(depths[target, default: 0], depth)
506+
for dependency in targetsById[target]?.dependencies ?? [] {
507+
// Check if we have already recorded this target with a greater depth, in which case visiting it again will
508+
// not increase its depth or any of its children.
509+
if depths[target, default: 0] < depth + 1 {
510+
worksList.append((dependency, depth + 1))
511+
}
512+
}
513+
}
514+
cachedTargetDepths = (buildTargets, depths)
515+
return depths
516+
}
517+
518+
/// Sort the targets so that low-level targets occur before high-level targets.
519+
///
520+
/// This sorting is best effort but allows the indexer to prepare and index low-level targets first, which allows
521+
/// index data to be available earlier.
522+
///
523+
/// `nil` if the build system doesn't support topological sorting of targets.
488524
package func topologicalSort(of targets: [BuildTargetIdentifier]) async throws -> [BuildTargetIdentifier]? {
489-
return await buildSystem?.underlyingBuildSystem.topologicalSort(of: targets)
525+
guard let workspaceTargets = await orLog("Getting build targets for topological sort", { try await buildTargets() })
526+
else {
527+
return nil
528+
}
529+
530+
let depths = targetDepths(for: workspaceTargets)
531+
return targets.sorted { (lhs: BuildTargetIdentifier, rhs: BuildTargetIdentifier) -> Bool in
532+
return depths[lhs, default: 0] > depths[rhs, default: 0]
533+
}
490534
}
491535

492536
/// Returns the list of targets that might depend on the given target and that need to be re-prepared when a file in

Sources/BuildSystemIntegration/BuiltInBuildSystem.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,6 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable {
105105
/// Wait until the build graph has been loaded.
106106
func waitForUpToDateBuildGraph() async
107107

108-
/// Sort the targets so that low-level targets occur before high-level targets.
109-
///
110-
/// This sorting is best effort but allows the indexer to prepare and index low-level targets first, which allows
111-
/// index data to be available earlier.
112-
///
113-
/// `nil` if the build system doesn't support topological sorting of targets.
114-
func topologicalSort(of targets: [BuildTargetIdentifier]) async -> [BuildTargetIdentifier]?
115-
116108
/// The toolchain that should be used to open the given document.
117109
///
118110
/// If `nil` is returned, then the default toolchain for the given language is used.

Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,6 @@ extension CompilationDatabaseBuildSystem: BuiltInBuildSystem {
159159

160160
package func waitForUpToDateBuildGraph() async {}
161161

162-
package func topologicalSort(of targets: [BuildTargetIdentifier]) -> [BuildTargetIdentifier]? {
163-
return nil
164-
}
165-
166162
private func database(for uri: DocumentURI) -> CompilationDatabase? {
167163
if let url = uri.fileURL, let path = try? AbsolutePath(validating: url.path) {
168164
return database(for: path)

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,8 @@ package actor SwiftPMBuildSystem {
210210
/// Maps source and header files to the target that include them.
211211
private var fileToTargets: [DocumentURI: Set<BuildTargetIdentifier>] = [:]
212212

213-
/// Maps target ids to their SwiftPM build target as well as the depth at which they occur in the build
214-
/// graph. Top level targets on which no other target depends have a depth of `1`. Targets with dependencies have a
215-
/// greater depth.
216-
private var targets: [BuildTargetIdentifier: (buildTarget: SwiftBuildTarget, depth: Int)] = [:]
213+
/// Maps target ids to their SwiftPM build target.
214+
private var swiftPMTargets: [BuildTargetIdentifier: SwiftBuildTarget] = [:]
217215

218216
private var targetDependencies: [BuildTargetIdentifier: Set<BuildTargetIdentifier>] = [:]
219217

@@ -442,7 +440,7 @@ extension SwiftPMBuildSystem {
442440
/// properties because otherwise we might end up in an inconsistent state
443441
/// with only some properties modified.
444442

445-
self.targets = [:]
443+
self.swiftPMTargets = [:]
446444
self.fileToTargets = [:]
447445
self.targetDependencies = [:]
448446

@@ -451,10 +449,7 @@ extension SwiftPMBuildSystem {
451449
guard let targetIdentifier else {
452450
return
453451
}
454-
var depth = depth
455-
if let existingDepth = targets[targetIdentifier]?.depth {
456-
depth = max(existingDepth, depth)
457-
} else {
452+
if swiftPMTargets[targetIdentifier] == nil {
458453
for source in buildTarget.sources + buildTarget.headers {
459454
fileToTargets[DocumentURI(source), default: []].insert(targetIdentifier)
460455
}
@@ -464,7 +459,7 @@ extension SwiftPMBuildSystem {
464459
{
465460
self.targetDependencies[parentIdentifier, default: []].insert(targetIdentifier)
466461
}
467-
targets[targetIdentifier] = (buildTarget, depth)
462+
swiftPMTargets[targetIdentifier] = buildTarget
468463
}
469464

470465
await messageHandler?.sendNotificationToSourceKitLSP(DidChangeBuildTargetNotification(changes: nil))
@@ -523,9 +518,9 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
523518
}
524519

525520
package func buildTargets(request: BuildTargetsRequest) async throws -> BuildTargetsResponse {
526-
let targets = self.targets.map { (targetId, target) in
521+
let targets = self.swiftPMTargets.map { (targetId, target) in
527522
var tags: [BuildTargetTag] = [.test]
528-
if target.depth != 1 {
523+
if !target.isPartOfRootPackage {
529524
tags.append(.dependency)
530525
}
531526
return BuildTarget(
@@ -547,10 +542,10 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
547542
// TODO: Query The SwiftPM build system for the document's language and add it to SourceItem.data
548543
// (https://github.com/swiftlang/sourcekit-lsp/issues/1267)
549544
for target in request.targets {
550-
guard let swiftPMTarget = self.targets[target] else {
545+
guard let swiftPMTarget = self.swiftPMTargets[target] else {
551546
continue
552547
}
553-
let sources = swiftPMTarget.buildTarget.sources.map {
548+
let sources = swiftPMTarget.sources.map {
554549
SourceItem(uri: DocumentURI($0), kind: .file, generated: false)
555550
}
556551
result.append(SourcesItem(target: target, sources: sources))
@@ -568,13 +563,13 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
568563
return try settings(forPackageManifest: path)
569564
}
570565

571-
guard let buildTarget = self.targets[request.target]?.buildTarget else {
566+
guard let swiftPMTarget = self.swiftPMTargets[request.target] else {
572567
logger.fault("Did not find target \(request.target.forLogging)")
573568
return nil
574569
}
575570

576-
if !buildTarget.sources.lazy.map(DocumentURI.init).contains(request.textDocument.uri),
577-
let substituteFile = buildTarget.sources.sorted(by: { $0.path < $1.path }).first
571+
if !swiftPMTarget.sources.lazy.map(DocumentURI.init).contains(request.textDocument.uri),
572+
let substituteFile = swiftPMTarget.sources.sorted(by: { $0.path < $1.path }).first
578573
{
579574
logger.info("Getting compiler arguments for \(url) using substitute file \(substituteFile)")
580575
// If `url` is not part of the target's source, it's most likely a header file. Fake compiler arguments for it
@@ -585,7 +580,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
585580
// getting its compiler arguments and then patching up the compiler arguments by replacing the substitute file
586581
// with the `.cpp` file.
587582
let buildSettings = FileBuildSettings(
588-
compilerArguments: try await compilerArguments(for: DocumentURI(substituteFile), in: buildTarget),
583+
compilerArguments: try await compilerArguments(for: DocumentURI(substituteFile), in: swiftPMTarget),
589584
workingDirectory: projectRoot.pathString
590585
).patching(newFile: try resolveSymlinks(path).pathString, originalFile: substituteFile.absoluteString)
591586
return SourceKitOptionsResponse(
@@ -595,7 +590,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
595590
}
596591

597592
return SourceKitOptionsResponse(
598-
compilerArguments: try await compilerArguments(for: request.textDocument.uri, in: buildTarget),
593+
compilerArguments: try await compilerArguments(for: request.textDocument.uri, in: swiftPMTarget),
599594
workingDirectory: projectRoot.pathString
600595
)
601596
}
@@ -635,14 +630,6 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
635630
await self.packageLoadingQueue.async {}.valuePropagatingCancellation
636631
}
637632

638-
package func topologicalSort(of targets: [BuildTargetIdentifier]) -> [BuildTargetIdentifier]? {
639-
return targets.sorted { (lhs: BuildTargetIdentifier, rhs: BuildTargetIdentifier) -> Bool in
640-
let lhsDepth = self.targets[lhs]?.depth ?? 0
641-
let rhsDepth = self.targets[rhs]?.depth ?? 0
642-
return lhsDepth > rhsDepth
643-
}
644-
}
645-
646633
package func prepare(request: PrepareTargetsRequest) async throws -> VoidResponse {
647634
// TODO: Support preparation of multiple targets at once. (https://github.com/swiftlang/sourcekit-lsp/issues/1262)
648635
for target in request.targets {
@@ -804,21 +791,6 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem {
804791
}
805792
}
806793

807-
package func sourceFiles() -> [SourceFileInfo] {
808-
var sourceFiles: [DocumentURI: SourceFileInfo] = [:]
809-
for (buildTarget, depth) in self.targets.values {
810-
for sourceFile in buildTarget.sources {
811-
let uri = DocumentURI(sourceFile)
812-
sourceFiles[uri] = SourceFileInfo(
813-
uri: uri,
814-
isPartOfRootProject: depth == 1 || (sourceFiles[uri]?.isPartOfRootProject ?? false),
815-
mayContainTests: true
816-
)
817-
}
818-
}
819-
return sourceFiles.values.sorted { $0.uri.pseudoPath < $1.uri.pseudoPath }
820-
}
821-
822794
package func addSourceFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async {
823795
testFilesDidChangeCallbacks.append(callback)
824796
}

0 commit comments

Comments
 (0)