Skip to content

Commit 9c9d771

Browse files
committed
Track which targets are up-to-date and avoid preparation of targets that are known to be up-to-date
Fixes #1258 rdar://127475948
1 parent c58fa70 commit 9c9d771

File tree

2 files changed

+80
-15
lines changed

2 files changed

+80
-15
lines changed

Sources/SKCore/BuildSystem.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import BuildServerProtocol
14+
import LSPLogging
1415
import LanguageServerProtocol
1516

1617
import struct TSCBasic.AbsolutePath
@@ -50,7 +51,7 @@ public struct SourceFileInfo: Sendable {
5051

5152
/// A target / run destination combination. For example, a configured target can represent building the target
5253
/// `MyLibrary` for iOS.
53-
public struct ConfiguredTarget: Hashable, Sendable {
54+
public struct ConfiguredTarget: Hashable, Sendable, CustomLogStringConvertible {
5455
/// An opaque string that represents the target.
5556
///
5657
/// The target's ID should be generated by the build system that handles the target and only interpreted by that
@@ -67,6 +68,14 @@ public struct ConfiguredTarget: Hashable, Sendable {
6768
self.targetID = targetID
6869
self.runDestinationID = runDestinationID
6970
}
71+
72+
public var description: String {
73+
"\(targetID)-\(runDestinationID)"
74+
}
75+
76+
public var redactedDescription: String {
77+
"\(targetID.hashForLogging)-\(runDestinationID.hashForLogging)"
78+
}
7079
}
7180

7281
/// An error build systems can throw from `prepare` if they don't support preparation of targets.

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ import LanguageServerProtocol
1616
import SKCore
1717

1818
/// Describes the state of indexing for a single source file
19-
private enum FileIndexStatus {
19+
private enum IndexStatus {
2020
/// The index is up-to-date.
2121
case upToDate
22-
/// The file is not up to date and we have scheduled a task to index it but that index operation hasn't been started
23-
/// yet.
22+
/// The file or target is not up to date and we have scheduled a task to update the index store for the file / prepare
23+
/// the target it but that index operation hasn't been started yet.
2424
case scheduled(Task<Void, Never>)
25-
/// We are currently actively indexing this file, ie. we are running a subprocess that indexes the file.
25+
/// We are currently actively updating the index store for the file / preparing the target, ie. we are running a
26+
/// subprocess that updates the index store / prepares a target.
2627
case executing(Task<Void, Never>)
2728

2829
var description: String {
@@ -46,15 +47,20 @@ public final actor SemanticIndexManager {
4647
/// The build system manager that is used to get compiler arguments for a file.
4748
private let buildSystemManager: BuildSystemManager
4849

49-
/// The index status of the source files that the `SemanticIndexManager` knows about.
50-
///
51-
/// Files that have never been indexed are not in this dictionary.
52-
private var indexStatus: [DocumentURI: FileIndexStatus] = [:]
53-
5450
/// The task to generate the build graph (resolving package dependencies, generating the build description,
5551
/// ...). `nil` if no build graph is currently being generated.
5652
private var generateBuildGraphTask: Task<Void, Never>?
5753

54+
/// The preparation status of the targets that the `SemanticIndexManager` has started preparation for.
55+
///
56+
/// Targets will be removed from this dictionary when they are no longer known to be up-to-date.
57+
private var preparationStatus: [ConfiguredTarget: IndexStatus] = [:]
58+
59+
/// The index status of the source files that the `SemanticIndexManager` knows about.
60+
///
61+
/// Files will be removed from this dictionary if their index is no longer up-to-date.
62+
private var indexStatus: [DocumentURI: IndexStatus] = [:]
63+
5864
/// The `TaskScheduler` that manages the scheduling of index tasks. This is shared among all `SemanticIndexManager`s
5965
/// in the process, to ensure that we don't schedule more index operations than processor cores from multiple
6066
/// workspaces.
@@ -79,13 +85,13 @@ public final actor SemanticIndexManager {
7985
///
8086
/// See `FileIndexStatus` for the distinction between `scheduled` and `executing`.
8187
public var inProgressIndexTasks: (scheduled: [DocumentURI], executing: [DocumentURI]) {
82-
let scheduled = indexStatus.compactMap { (uri: DocumentURI, status: FileIndexStatus) in
88+
let scheduled = indexStatus.compactMap { (uri: DocumentURI, status: IndexStatus) in
8389
if case .scheduled = status {
8490
return uri
8591
}
8692
return nil
8793
}
88-
let inProgress = indexStatus.compactMap { (uri: DocumentURI, status: FileIndexStatus) in
94+
let inProgress = indexStatus.compactMap { (uri: DocumentURI, status: IndexStatus) in
8995
if case .executing = status {
9096
return uri
9197
}
@@ -192,6 +198,11 @@ public final actor SemanticIndexManager {
192198
for uri in changedFiles {
193199
indexStatus[uri] = nil
194200
}
201+
// Clear the preparation status so that we re-prepare them. If the target hasn't been affected by the file changes,
202+
// we rely on a null build during preparation to fast re-preparation.
203+
// Ideally, we would have more fine-grained dependency management here and only mark those targets out-of-date that
204+
// might be affected by the changed files.
205+
preparationStatus.removeAll()
195206
await scheduleBackgroundIndex(files: changedFiles)
196207
}
197208

@@ -226,14 +237,59 @@ public final actor SemanticIndexManager {
226237

227238
/// Prepare the given targets for indexing
228239
private func prepare(targets: [ConfiguredTarget], priority: TaskPriority?) async {
240+
let targetsToPrepare = targets.filter {
241+
if case .upToDate = preparationStatus[$0] {
242+
return false
243+
}
244+
return true
245+
}
229246
let taskDescription = AnyIndexTaskDescription(
230247
PreparationTaskDescription(
231-
targetsToPrepare: targets,
248+
targetsToPrepare: targetsToPrepare,
232249
buildSystemManager: self.buildSystemManager
233250
)
234251
)
235-
await self.indexTaskScheduler.schedule(priority: priority, taskDescription).value
236-
self.indexTaskDidFinish()
252+
let preparationTask = await self.indexTaskScheduler.schedule(priority: priority, taskDescription) { newState in
253+
switch newState {
254+
case .executing:
255+
for target in targetsToPrepare {
256+
if case .scheduled(let task) = self.preparationStatus[target] {
257+
self.preparationStatus[target] = .executing(task)
258+
} else {
259+
logger.fault(
260+
"""
261+
Preparation status of \(target.forLogging) is in an unexpected state \
262+
'\(self.preparationStatus[target]?.description ?? "<nil>", privacy: .public)' when preparation task \
263+
started executing
264+
"""
265+
)
266+
}
267+
}
268+
case .cancelledToBeRescheduled:
269+
for target in targetsToPrepare {
270+
if case .executing(let task) = self.preparationStatus[target] {
271+
self.preparationStatus[target] = .scheduled(task)
272+
} else {
273+
logger.fault(
274+
"""
275+
Preparation status of \(target.forLogging) is in an unexpected state \
276+
'\(self.preparationStatus[target]?.description ?? "<nil>", privacy: .public)' when preparation task \
277+
is cancelled to be rescheduled.
278+
"""
279+
)
280+
}
281+
}
282+
case .finished:
283+
for target in targetsToPrepare {
284+
self.preparationStatus[target] = .upToDate
285+
}
286+
self.indexTaskDidFinish()
287+
}
288+
}
289+
for target in targetsToPrepare {
290+
preparationStatus[target] = .scheduled(preparationTask)
291+
}
292+
await preparationTask.value
237293
}
238294

239295
/// Update the index store for the given files, assuming that their targets have already been prepared.

0 commit comments

Comments
 (0)