Skip to content

Commit 9345a4f

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 9345a4f

File tree

3 files changed

+155
-72
lines changed

3 files changed

+155
-72
lines changed

Sources/SKCore/BuildSystemManager.swift

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,10 @@ 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
@@ -161,12 +158,22 @@ extension BuildSystemManager {
161158
return settings
162159
}
163160

164-
private func buildSettings(
161+
/// Returns the build settings for the given file in the given target.
162+
///
163+
/// Only call this method if it is known that `document` is a main file. Prefer `buildSettingsInferredFromMainFile`
164+
/// otherwise. If `document` is a header file, this will most likely return fallback settings because header files
165+
/// don't have build settings by themselves.
166+
public func buildSettings(
165167
for document: DocumentURI,
168+
in target: ConfiguredTarget?,
166169
language: Language
167170
) async -> FileBuildSettings? {
168171
do {
169-
if let buildSettings = try await buildSettingsFromPrimaryBuildSystem(for: document, language: language) {
172+
if let buildSettings = try await buildSettingsFromPrimaryBuildSystem(
173+
for: document,
174+
in: target,
175+
language: language
176+
) {
170177
return buildSettings
171178
}
172179
} catch {
@@ -194,21 +201,22 @@ extension BuildSystemManager {
194201
/// references to that C file in the build settings by the header file.
195202
public func buildSettingsInferredFromMainFile(
196203
for document: DocumentURI,
197-
language: Language,
198-
logBuildSettings: Bool = true
204+
language: Language
199205
) async -> FileBuildSettings? {
200206
let mainFile = await mainFile(for: document, language: language)
201-
guard var settings = await buildSettings(for: mainFile, language: language) else {
207+
guard let target = await canonicalConfiguredTarget(for: document) else {
208+
logger.error("Failed to get target for \(document.forLogging)")
209+
return nil
210+
}
211+
guard var settings = await buildSettings(for: mainFile, in: target, language: language) else {
202212
return nil
203213
}
204214
if mainFile != document {
205215
// If the main file isn't the file itself, we need to patch the build settings
206216
// to reference `document` instead of `mainFile`.
207217
settings = settings.patching(newFile: document.pseudoPath, originalFile: mainFile.pseudoPath)
208218
}
209-
if logBuildSettings {
210-
await BuildSettingsLogger.shared.log(settings: settings, for: document)
211-
}
219+
await BuildSettingsLogger.shared.log(settings: settings, for: document)
212220
return settings
213221
}
214222

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 26 additions & 19 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,10 +340,10 @@ 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 files: [FileAndTarget], taskID: UUID, priority: TaskPriority?) async {
342344
let taskDescription = AnyIndexTaskDescription(
343345
UpdateIndexStoreTaskDescription(
344-
filesToIndex: Set(files),
346+
filesToIndex: files,
345347
buildSystemManager: self.buildSystemManager,
346348
index: index
347349
)
@@ -350,21 +352,21 @@ public final actor SemanticIndexManager {
350352
switch newState {
351353
case .executing:
352354
for file in files {
353-
if case .scheduled((taskID, let task)) = self.indexStatus[file.uri] {
354-
self.indexStatus[file.uri] = .executing((taskID, task))
355+
if case .scheduled((taskID, let task)) = self.indexStatus[file.file.sourceFile] {
356+
self.indexStatus[file.file.sourceFile] = .executing((taskID, task))
355357
}
356358
}
357359
case .cancelledToBeRescheduled:
358360
for file in files {
359-
if case .executing((taskID, let task)) = self.indexStatus[file.uri] {
360-
self.indexStatus[file.uri] = .scheduled((taskID, task))
361+
if case .executing((taskID, let task)) = self.indexStatus[file.file.sourceFile] {
362+
self.indexStatus[file.file.sourceFile] = .scheduled((taskID, task))
361363
}
362364
}
363365
case .finished:
364366
for file in files {
365-
switch self.indexStatus[file.uri] {
367+
switch self.indexStatus[file.file.sourceFile] {
366368
case .executing((taskID, _)):
367-
self.indexStatus[file.uri] = .upToDate
369+
self.indexStatus[file.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)