Skip to content

Commit a850cb6

Browse files
committed
When a file is changed, only mark targets that depend on it as out-of-date
1 parent 490871b commit a850cb6

File tree

8 files changed

+90
-19
lines changed

8 files changed

+90
-19
lines changed

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ extension BuildServerBuildSystem: BuildSystem {
285285
return nil
286286
}
287287

288+
public func targets(dependingOn targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
289+
return nil
290+
}
291+
288292
public func prepare(targets: [ConfiguredTarget]) async throws {
289293
throw PrepareNotSupportedError()
290294
}

Sources/SKCore/BuildSystem.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ public protocol BuildSystem: AnyObject, Sendable {
147147
/// `nil` if the build system doesn't support topological sorting of targets.
148148
func topologicalSort(of targets: [ConfiguredTarget]) async -> [ConfiguredTarget]?
149149

150+
/// Returns the list of targets that might depend on the given target and that need to be re-prepared when a file in
151+
/// `target` is modified.
152+
///
153+
/// The returned list can be an over-approximation, in which case the indexer will perform more work than strictly
154+
/// necessary by scheduling re-preparation of a target where it isn't necessary.
155+
///
156+
/// Returning `nil` indicates that all targets should be considered depending on the given target.
157+
func targets(dependingOn targets: [ConfiguredTarget]) async -> [ConfiguredTarget]?
158+
150159
/// Prepare the given targets for indexing and semantic functionality. This should build all swift modules of target
151160
/// dependencies.
152161
func prepare(targets: [ConfiguredTarget]) async throws

Sources/SKCore/BuildSystemManager.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,17 @@ extension BuildSystemManager {
126126
}
127127
}
128128

129+
/// Returns all the `ConfiguredTarget`s that the document is part of.
130+
public func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] {
131+
return await buildSystem?.configuredTargets(for: document) ?? []
132+
}
133+
129134
/// Returns the `ConfiguredTarget` that should be used for semantic functionality of the given document.
130135
public func canonicalConfiguredTarget(for document: DocumentURI) async -> ConfiguredTarget? {
131136
// Sort the configured targets to deterministically pick the same `ConfiguredTarget` every time.
132137
// We could allow the user to specify a preference of one target over another. For now this is not necessary because
133138
// no build system currently returns multiple targets for a source file.
134-
return await buildSystem?.configuredTargets(for: document)
139+
return await configuredTargets(for: document)
135140
.sorted { ($0.targetID, $0.runDestinationID) < ($1.targetID, $1.runDestinationID) }
136141
.first
137142
}
@@ -220,6 +225,10 @@ extension BuildSystemManager {
220225
return await buildSystem?.topologicalSort(of: targets)
221226
}
222227

228+
public func targets(dependingOn targets: [ConfiguredTarget]) async -> [ConfiguredTarget]? {
229+
return await buildSystem?.targets(dependingOn: targets)
230+
}
231+
223232
public func prepare(targets: [ConfiguredTarget]) async throws {
224233
try await buildSystem?.prepare(targets: targets)
225234
}

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
135135
return nil
136136
}
137137

138+
public func targets(dependingOn targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
139+
return nil
140+
}
141+
138142
public func registerForChangeNotifications(for uri: DocumentURI) async {
139143
self.watchedFiles.insert(uri)
140144
}

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ private func getDefaultToolchain(_ toolchainRegistry: ToolchainRegistry) async -
6363
return await toolchainRegistry.default
6464
}
6565

66+
fileprivate extension ConfiguredTarget {
67+
init(_ buildTarget: any SwiftBuildTarget) {
68+
self.init(targetID: buildTarget.name, runDestinationID: "dummy")
69+
}
70+
71+
static let forPackageManifest = ConfiguredTarget(targetID: "", runDestinationID: "")
72+
}
73+
6674
/// Swift Package Manager build system and workspace support.
6775
///
6876
/// This class implements the `BuildSystem` interface to provide the build settings for a Swift
@@ -101,10 +109,10 @@ public actor SwiftPMBuildSystem {
101109
var fileToTarget: [AbsolutePath: SwiftBuildTarget] = [:]
102110
var sourceDirToTarget: [AbsolutePath: SwiftBuildTarget] = [:]
103111

104-
/// Maps target ids (aka. `ConfiguredTarget.targetID`) to their SwiftPM build target as well as an index in their
105-
/// topological sorting. Targets with lower index are more low level, ie. targets with higher indices depend on
106-
/// targets with lower indices.
107-
var targets: [String: (index: Int, buildTarget: SwiftBuildTarget)] = [:]
112+
/// Maps configured targets ids to their SwiftPM build target as well as an index in their topological sorting.
113+
///
114+
/// Targets with lower index are more low level, ie. targets with higher indices depend on targets with lower indices.
115+
var targets: [ConfiguredTarget: (index: Int, buildTarget: SwiftBuildTarget)] = [:]
108116

109117
/// The URIs for which the delegate has registered for change notifications,
110118
/// mapped to the language the delegate specified when registering for change notifications.
@@ -289,7 +297,7 @@ extension SwiftPMBuildSystem {
289297

290298
self.targets = Dictionary(
291299
try buildDescription.allTargetsInTopologicalOrder(in: modulesGraph).enumerated().map { (index, target) in
292-
return (key: target.name, (index, target))
300+
return (key: ConfiguredTarget(target), (index, target))
293301
},
294302
uniquingKeysWith: { first, second in
295303
logger.fault("Found two targets with the same name \(first.buildTarget.name)")
@@ -362,11 +370,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
362370
return nil
363371
}
364372

365-
if configuredTarget.targetID == "" {
373+
if configuredTarget == .forPackageManifest {
366374
return try settings(forPackageManifest: path)
367375
}
368376

369-
guard let buildTarget = self.targets[configuredTarget.targetID]?.buildTarget else {
377+
guard let buildTarget = self.targets[configuredTarget]?.buildTarget else {
370378
logger.error("Did not find target with name \(configuredTarget.targetID)")
371379
return nil
372380
}
@@ -397,13 +405,13 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
397405
}
398406

399407
if let target = try? buildTarget(for: path) {
400-
return [ConfiguredTarget(targetID: target.name, runDestinationID: "dummy")]
408+
return [ConfiguredTarget(target)]
401409
}
402410

403411
if path.basename == "Package.swift" {
404412
// We use an empty target name to represent the package manifest since an empty target name is not valid for any
405413
// user-defined target.
406-
return [ConfiguredTarget(targetID: "", runDestinationID: "dummy")]
414+
return [ConfiguredTarget.forPackageManifest]
407415
}
408416

409417
if url.pathExtension == "h", let target = try? target(forHeader: path) {
@@ -415,19 +423,40 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
415423

416424
public func topologicalSort(of targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
417425
return targets.sorted { (lhs: ConfiguredTarget, rhs: ConfiguredTarget) -> Bool in
418-
let lhsIndex = self.targets[lhs.targetID]?.index ?? self.targets.count
419-
let rhsIndex = self.targets[lhs.targetID]?.index ?? self.targets.count
426+
let lhsIndex = self.targets[lhs]?.index ?? self.targets.count
427+
let rhsIndex = self.targets[lhs]?.index ?? self.targets.count
420428
return lhsIndex < rhsIndex
421429
}
422430
}
423431

432+
public func targets(dependingOn targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
433+
let targetIndices = targets.compactMap { self.targets[$0]?.index }
434+
let minimumTargetIndex: Int?
435+
if targetIndices.count == targets.count {
436+
minimumTargetIndex = targetIndices.min()
437+
} else {
438+
// One of the targets didn't have an entry in self.targets. We don't know what might depend on it.
439+
minimumTargetIndex = nil
440+
}
441+
442+
// Files that occur before the target in the topological sorting don't depend on it.
443+
// Ideally, we should consult the dependency graph here for more accurate dependency analysis instead of relying on
444+
// a flattened list (https://github.com/apple/sourcekit-lsp/issues/1312).
445+
return self.targets.compactMap { (configuredTarget, value) -> ConfiguredTarget? in
446+
if let minimumTargetIndex, value.index <= minimumTargetIndex {
447+
return nil
448+
}
449+
return configuredTarget
450+
}
451+
}
452+
424453
public func prepare(targets: [ConfiguredTarget]) async throws {
425454
// TODO (indexing): Support preparation of multiple targets at once.
426455
// https://github.com/apple/sourcekit-lsp/issues/1262
427456
for target in targets {
428457
try await prepare(singleTarget: target)
429458
}
430-
let filesInPreparedTargets = targets.flatMap { self.targets[$0.targetID]?.buildTarget.sources ?? [] }
459+
let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] }
431460
await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init)))
432461
}
433462

@@ -620,7 +649,7 @@ extension SwiftPMBuildSystem {
620649
var dir = path.parentDirectory
621650
while !dir.isRoot {
622651
if let buildTarget = sourceDirToTarget[dir] {
623-
return ConfiguredTarget(targetID: buildTarget.name, runDestinationID: "dummy")
652+
return ConfiguredTarget(buildTarget)
624653
}
625654
dir = dir.parentDirectory
626655
}

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,19 @@ public final actor SemanticIndexManager {
209209
for uri in changedFiles {
210210
indexStatus[uri] = nil
211211
}
212-
// Clear the preparation status so that we re-prepare them. If the target hasn't been affected by the file changes,
213-
// we rely on a null build during preparation to fast re-preparation.
214-
// Ideally, we would have more fine-grained dependency management here and only mark those targets out-of-date that
215-
// might be affected by the changed files.
216-
preparationStatus.removeAll()
212+
// Note that configured targets are the right abstraction layer here (instead of a non-configured target) because a
213+
// build system might have targets that include different source files. Hence a source file might be in target T
214+
// configured for macOS but not in target T configured for iOS.
215+
let targets = await changedFiles.asyncMap { await buildSystemManager.configuredTargets(for: $0) }.flatMap { $0 }
216+
if let dependentTargets = await buildSystemManager.targets(dependingOn: targets) {
217+
for dependentTarget in dependentTargets {
218+
preparationStatus[dependentTarget] = nil
219+
}
220+
} else {
221+
// We couldn't determine which targets depend on the modified targets. Be conservative and assume all of them do.
222+
preparationStatus = [:]
223+
}
224+
217225
await scheduleBackgroundIndex(files: changedFiles)
218226
}
219227

Tests/SKCoreTests/BuildSystemManagerTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,10 @@ class ManualBuildSystem: BuildSystem {
477477
return nil
478478
}
479479

480+
public func targets(dependingOn targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
481+
return nil
482+
}
483+
480484
func registerForChangeNotifications(for uri: DocumentURI) async {
481485
}
482486

Tests/SourceKitLSPTests/BuildSystemTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ actor TestBuildSystem: BuildSystem {
6969
return nil
7070
}
7171

72+
public func targets(dependingOn targets: [ConfiguredTarget]) -> [ConfiguredTarget]? {
73+
return nil
74+
}
75+
7276
func registerForChangeNotifications(for uri: DocumentURI) async {
7377
watchedFiles.insert(uri)
7478
}

0 commit comments

Comments
 (0)