Skip to content

Commit 6695859

Browse files
committed
Improve handling of main file vs header file during indexing
Essentially fix two issues in updating the index store: 1. If there was one task to index `HeaderA.h` through `main.c` and one to index `HeaderB.h` through `main.c`, we would not declare a dependency between them in the task scheduler, which meant that we could have two concurrent and racing index tasks for `main.c`. Declare a dependency between any two files that have the same main file 2. `UpdateIndexStoreTaskDescription` was computing the target to index a file in independently of `SemanticIndexManager`. While they currently always line up, we should pass the target in which to index a file to the `UpdateIndexStoreTaskDescription`. Only this way can we guarantee that we actually prepared the target that the file will be indexed in.
1 parent 0600809 commit 6695859

File tree

5 files changed

+158
-82
lines changed

5 files changed

+158
-82
lines changed

Documentation/Files_To_Reindex.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Alternatives would be:
2020

2121
All files that include the header (including via other headers) might be affected by the change, similar to how all `.swift` files that import a module might be affected. Similar to modules, we choose to not re-index all files that include the header because of the same considerations mentioned above.
2222

23-
To re-index the header, we pick one main file that includes the header and re-index that, which will effectively update the index for the header. For existing headers, we know which files import a header from the existing index. For new header files, we assume that it hasn’t been included in any file yet and thus don't re-index it. If the header is included from somewhere lese, it’s likely that the user will modify the file that includes the new header file shortly after, which will index the header and establish the header to main file connection.
23+
To re-index the header, we pick one main file that includes the header and re-index that, which will effectively update the index for the header. For existing headers, we know which files import a header from the existing index. For new header files, we assume that it hasn’t been included in any file yet and thus don't index it. If the user wrote an include to the new header before creating the header itself, we don't know about that include from the existing index. But it’s likely that the user will modify the file that includes the new header file shortly after, which will index the header and establish the header to main file connection.
2424

2525
## `.c` / `.cpp` / `.m`
2626

Sources/SKCore/BuildSystemManager.swift

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -141,32 +141,36 @@ extension BuildSystemManager {
141141
/// Implementation detail of `buildSettings(for:language:)`.
142142
private func buildSettingsFromPrimaryBuildSystem(
143143
for document: DocumentURI,
144+
in target: ConfiguredTarget?,
144145
language: Language
145146
) async throws -> FileBuildSettings? {
146-
guard let buildSystem else {
147-
return nil
148-
}
149-
guard let target = await canonicalConfiguredTarget(for: document) else {
150-
logger.error("Failed to get target for \(document.forLogging)")
147+
guard let buildSystem, let target else {
151148
return nil
152149
}
153150
// FIXME: (async) We should only wait `fallbackSettingsTimeout` for build
154151
// settings and return fallback afterwards. I am not sure yet, how best to
155152
// implement that with Swift concurrency.
156153
// For now, this should be fine because all build systems return
157154
// very quickly from `settings(for:language:)`.
158-
guard let settings = try await buildSystem.buildSettings(for: document, in: target, language: language) else {
159-
return nil
160-
}
161-
return settings
155+
return try await buildSystem.buildSettings(for: document, in: target, language: language)
162156
}
163157

164-
private func buildSettings(
158+
/// Returns the build settings for the given file in the given target.
159+
///
160+
/// Only call this method if it is known that `document` is a main file. Prefer `buildSettingsInferredFromMainFile`
161+
/// otherwise. If `document` is a header file, this will most likely return fallback settings because header files
162+
/// don't have build settings by themselves.
163+
public func buildSettings(
165164
for document: DocumentURI,
165+
in target: ConfiguredTarget?,
166166
language: Language
167167
) async -> FileBuildSettings? {
168168
do {
169-
if let buildSettings = try await buildSettingsFromPrimaryBuildSystem(for: document, language: language) {
169+
if let buildSettings = try await buildSettingsFromPrimaryBuildSystem(
170+
for: document,
171+
in: target,
172+
language: language
173+
) {
170174
return buildSettings
171175
}
172176
} catch {
@@ -194,21 +198,19 @@ extension BuildSystemManager {
194198
/// references to that C file in the build settings by the header file.
195199
public func buildSettingsInferredFromMainFile(
196200
for document: DocumentURI,
197-
language: Language,
198-
logBuildSettings: Bool = true
201+
language: Language
199202
) async -> FileBuildSettings? {
200203
let mainFile = await mainFile(for: document, language: language)
201-
guard var settings = await buildSettings(for: mainFile, language: language) else {
204+
let target = await canonicalConfiguredTarget(for: mainFile)
205+
guard var settings = await buildSettings(for: mainFile, in: target, language: language) else {
202206
return nil
203207
}
204208
if mainFile != document {
205209
// If the main file isn't the file itself, we need to patch the build settings
206210
// to reference `document` instead of `mainFile`.
207211
settings = settings.patching(newFile: document.pseudoPath, originalFile: mainFile.pseudoPath)
208212
}
209-
if logBuildSettings {
210-
await BuildSettingsLogger.shared.log(settings: settings, for: document)
211-
}
213+
await BuildSettingsLogger.shared.log(settings: settings, for: document)
212214
return settings
213215
}
214216

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,14 @@ public final actor SemanticIndexManager {
221221
///
222222
/// If `files` contains a header file, this will return a `FileToIndex` that re-indexes a main file which includes the
223223
/// header file to update the header file's index.
224-
private func filesToIndex(toCover files: some Collection<DocumentURI>) async -> [FileToIndex] {
224+
private func filesToIndex(
225+
toCover files: some Collection<DocumentURI>
226+
) async -> [FileToIndex] {
225227
let sourceFiles = Set(await buildSystemManager.sourceFiles().map(\.uri))
226228
let filesToReIndex = await files.asyncCompactMap { (uri) -> FileToIndex? in
227229
if sourceFiles.contains(uri) {
228230
// If this is a source file, just index it.
229-
return FileToIndex(uri: uri, mainFile: nil)
231+
return .indexableFile(uri)
230232
}
231233
// Otherwise, see if it is a header file. If so, index a main file that that imports it to update header file's
232234
// index.
@@ -239,7 +241,7 @@ public final actor SemanticIndexManager {
239241
guard let mainFile else {
240242
return nil
241243
}
242-
return FileToIndex(uri: uri, mainFile: mainFile)
244+
return .headerFile(header: uri, mainFile: mainFile)
243245
}
244246
return filesToReIndex
245247
}
@@ -338,33 +340,33 @@ public final actor SemanticIndexManager {
338340
}
339341

340342
/// Update the index store for the given files, assuming that their targets have already been prepared.
341-
private func updateIndexStore(for files: [FileToIndex], taskID: UUID, priority: TaskPriority?) async {
343+
private func updateIndexStore(for filesAndTargets: [FileAndTarget], taskID: UUID, priority: TaskPriority?) async {
342344
let taskDescription = AnyIndexTaskDescription(
343345
UpdateIndexStoreTaskDescription(
344-
filesToIndex: Set(files),
346+
filesToIndex: filesAndTargets,
345347
buildSystemManager: self.buildSystemManager,
346348
index: index
347349
)
348350
)
349351
let updateIndexStoreTask = await self.indexTaskScheduler.schedule(priority: priority, taskDescription) { newState in
350352
switch newState {
351353
case .executing:
352-
for file in files {
353-
if case .scheduled((taskID, let task)) = self.indexStatus[file.uri] {
354-
self.indexStatus[file.uri] = .executing((taskID, task))
354+
for fileAndTarget in filesAndTargets {
355+
if case .scheduled((taskID, let task)) = self.indexStatus[fileAndTarget.file.sourceFile] {
356+
self.indexStatus[fileAndTarget.file.sourceFile] = .executing((taskID, task))
355357
}
356358
}
357359
case .cancelledToBeRescheduled:
358-
for file in files {
359-
if case .executing((taskID, let task)) = self.indexStatus[file.uri] {
360-
self.indexStatus[file.uri] = .scheduled((taskID, task))
360+
for fileAndTarget in filesAndTargets {
361+
if case .executing((taskID, let task)) = self.indexStatus[fileAndTarget.file.sourceFile] {
362+
self.indexStatus[fileAndTarget.file.sourceFile] = .scheduled((taskID, task))
361363
}
362364
}
363365
case .finished:
364-
for file in files {
365-
switch self.indexStatus[file.uri] {
366+
for fileAndTarget in filesAndTargets {
367+
switch self.indexStatus[fileAndTarget.file.sourceFile] {
366368
case .executing((taskID, _)):
367-
self.indexStatus[file.uri] = .upToDate
369+
self.indexStatus[fileAndTarget.file.sourceFile] = .upToDate
368370
default:
369371
break
370372
}
@@ -383,24 +385,25 @@ public final actor SemanticIndexManager {
383385
priority: TaskPriority?
384386
) async -> Task<Void, Never> {
385387
let outOfDateFiles = await filesToIndex(toCover: files).filter {
386-
if case .upToDate = indexStatus[$0.uri] {
388+
if case .upToDate = indexStatus[$0.sourceFile] {
387389
return false
388390
}
389391
return true
390392
}
391-
.sorted(by: { $0.uri.stringValue < $1.uri.stringValue }) // sort files to get deterministic indexing order
393+
// sort files to get deterministic indexing order
394+
.sorted(by: { $0.sourceFile.stringValue < $1.sourceFile.stringValue })
392395

393396
// Sort the targets in topological order so that low-level targets get built before high-level targets, allowing us
394397
// to index the low-level targets ASAP.
395398
var filesByTarget: [ConfiguredTarget: [FileToIndex]] = [:]
396-
for file in outOfDateFiles {
397-
guard let target = await buildSystemManager.canonicalConfiguredTarget(for: file.mainFile ?? file.uri) else {
399+
for fileToIndex in outOfDateFiles {
400+
guard let target = await buildSystemManager.canonicalConfiguredTarget(for: fileToIndex.mainFile) else {
398401
logger.error(
399-
"Not indexing \(file.uri.forLogging) because the target could not be determined for main file \(file.mainFile?.forLogging)"
402+
"Not indexing \(fileToIndex.forLogging) because the target could not be determined"
400403
)
401404
continue
402405
}
403-
filesByTarget[target, default: []].append(file)
406+
filesByTarget[target, default: []].append(fileToIndex)
404407
}
405408

406409
var sortedTargets: [ConfiguredTarget] =
@@ -440,7 +443,11 @@ public final actor SemanticIndexManager {
440443
// https://github.com/apple/sourcekit-lsp/issues/1268
441444
for fileBatch in filesByTarget[target]!.partition(intoBatchesOfSize: 1) {
442445
taskGroup.addTask {
443-
await self.updateIndexStore(for: fileBatch, taskID: taskID, priority: priority)
446+
await self.updateIndexStore(
447+
for: fileBatch.map { FileAndTarget(file: $0, target: target) },
448+
taskID: taskID,
449+
priority: priority
450+
)
444451
}
445452
}
446453
}
@@ -455,7 +462,7 @@ public final actor SemanticIndexManager {
455462
// setting it to `.scheduled` because we don't have an `await` call between the creation of `indexTask` and
456463
// this loop, so we still have exclusive access to the `SemanticIndexManager` actor and hence `updateIndexStore`
457464
// can't execute until we have set all index statuses to `.scheduled`.
458-
indexStatus[file.uri] = .scheduled((taskID, indexTask))
465+
indexStatus[file.sourceFile] = .scheduled((taskID, indexTask))
459466
}
460467
indexTasksWereScheduled(filesToIndex.count)
461468
}

0 commit comments

Comments
 (0)