Skip to content

Commit a005021

Browse files
committed
Support indexing of mulitple Swift files within the same compiler invocation
Fixes #1268
1 parent e60894c commit a005021

File tree

9 files changed

+608
-72
lines changed

9 files changed

+608
-72
lines changed

Contributor Documentation/BSP Extensions.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,12 @@ export interface SourceKitSourceItemData {
130130
kind?: "source" | "header" | "doccCatalog";
131131

132132
/**
133-
* The output path that is during indexing for this file, ie. the `-index-unit-output-path`, if it is specified
134-
* in the compiler arguments or the file that is passed as `-o`, if `-index-unit-output-path` is not specified.
133+
* The output path that is a string that uniquely identifies the index output of this file in this target. If an index
134+
* store should be re-used between build and background indexing, it must match the `-o` path or
135+
* `-index-unit-output-path` used during the build.
136+
*
137+
* The index unit output path historically matched the path used for `-o` during compilation but has since evolved to
138+
* be an opaque string. In particular, it does not have to match to any file on disk.
135139
*
136140
* This allows SourceKit-LSP to remove index entries for source files that are removed from a target but remain
137141
* present on disk and to index a file that is part of multiple targets in the context of each target.

Sources/BuildServerIntegration/BuildSettingsLogger.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
package import LanguageServerProtocol
1414
package import SKLogging
15+
import SwiftExtensions
1516

1617
// MARK: - Build settings logger
1718

@@ -27,11 +28,31 @@ package actor BuildSettingsLogger {
2728
Self.log(level: level, settings: settings, for: uri)
2829
}
2930

30-
/// Log the given build settings.
31+
/// Log the given build settings for a single file
3132
///
3233
/// In contrast to the instance method `log`, this will always log the build settings. The instance method only logs
3334
/// the build settings if they have changed.
3435
package static func log(level: LogLevel = .default, settings: FileBuildSettings, for uri: DocumentURI) {
36+
log(level: level, settings: settings, for: [uri])
37+
}
38+
39+
/// Log the given build settings for a list of source files that all share the same build settings.
40+
///
41+
/// In contrast to the instance method `log`, this will always log the build settings. The instance method only logs
42+
/// the build settings if they have changed.
43+
package static func log(level: LogLevel = .default, settings: FileBuildSettings, for uris: [DocumentURI]) {
44+
let header: String
45+
if let uri = uris.only {
46+
header = "Build settings for \(uri.forLogging)"
47+
} else if let firstUri = uris.first {
48+
header = "Build settings for \(firstUri.forLogging) and \(firstUri) and \(uris.count - 1) others"
49+
} else {
50+
header = "Build settings for empty list"
51+
}
52+
log(level: level, settings: settings, header: header)
53+
}
54+
55+
private static func log(level: LogLevel = .default, settings: FileBuildSettings, header: String) {
3556
let log = """
3657
Compiler Arguments:
3758
\(settings.compilerArguments.joined(separator: "\n"))
@@ -47,7 +68,7 @@ package actor BuildSettingsLogger {
4768
logger.log(
4869
level: level,
4970
"""
50-
Build settings for \(uri.forLogging) (\(index + 1)/\(chunks.count))
71+
\(header) (\(index + 1)/\(chunks.count))
5172
\(chunk)
5273
"""
5374
)

Sources/BuildServerIntegration/FileBuildSettings.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import LanguageServerProtocolExtensions
1818
///
1919
/// Encapsulates all the settings needed to compile a single file, including the compiler arguments
2020
/// and working directory. `FileBuildSettings`` are typically the result of a build server query.
21-
package struct FileBuildSettings: Equatable, Sendable {
22-
21+
package struct FileBuildSettings: Hashable, Sendable {
2322
/// The compiler arguments to use for this file.
2423
package var compilerArguments: [String]
2524

Sources/BuildServerIntegration/SwiftPMBuildServer.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,11 @@ package actor SwiftPMBuildServer: BuiltInBuildServer {
514514

515515
throw NonFileURIError(uri: file)
516516
}
517+
#if compiler(>=6.4)
518+
#warning(
519+
"Once we can guarantee that the toolchain can index multiple Swift files in a single invocation, we no longer need to set -index-unit-output-path since it's always set using an -output-file-map"
520+
)
521+
#endif
517522
var compilerArguments = try buildTarget.compileArguments(for: fileURL)
518523
if buildTarget.compiler == .swift {
519524
compilerArguments += [

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,15 @@ package actor SkipUnless {
472472
}
473473
}
474474

475+
package static func canIndexMultipleSwiftFilesInSingleInvocation(
476+
file: StaticString = #filePath,
477+
line: UInt = #line
478+
) async throws {
479+
return try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(6, 2), file: file, line: line) {
480+
return await ToolchainRegistry.forTesting.default?.canIndexMultipleSwiftFilesInSingleInvocation ?? false
481+
}
482+
}
483+
475484
private static func getSourceKitD() async throws -> SourceKitD {
476485
guard let sourcekitdPath = await ToolchainRegistry.forTesting.default?.sourcekitd else {
477486
throw GenericError("Could not find SourceKitD")

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,13 @@ package final actor SemanticIndexManager {
928928
// (https://github.com/swiftlang/sourcekit-lsp/issues/1262)
929929
for targetsBatch in sortedTargets.partition(intoBatchesOfSize: 1) {
930930
let preparationTaskID = UUID()
931-
let filesToIndex = targetsBatch.flatMap({ filesByTarget[$0]! })
931+
let filesToIndex = targetsBatch.flatMap { (target) -> [FileIndexInfo] in
932+
guard let files = filesByTarget[target] else {
933+
logger.fault("Unexpectedly found no files for target in target batch")
934+
return []
935+
}
936+
return files
937+
}
932938

933939
// First schedule preparation of the targets. We schedule the preparation outside of `indexTask` so that we
934940
// deterministically prepare targets in the topological order for indexing. If we triggered preparation inside the
@@ -957,36 +963,36 @@ package final actor SemanticIndexManager {
957963

958964
// And after preparation is done, index the files in the targets.
959965
await withTaskGroup(of: Void.self) { taskGroup in
960-
for target in targetsBatch {
961-
var filesByLanguage: [Language: [FileIndexInfo]] = [:]
962-
for fileInfo in filesByTarget[target]! {
963-
filesByLanguage[fileInfo.language, default: []].append(fileInfo)
966+
let fileInfos = targetsBatch.flatMap { (target) -> [FileIndexInfo] in
967+
guard let files = filesByTarget[target] else {
968+
logger.fault("Unexpectedly found no files for target in target batch")
969+
return []
964970
}
965-
for (language, fileInfos) in filesByLanguage {
966-
// TODO: Once swiftc supports indexing of multiple files in a single invocation, increase the batch size to
967-
// allow it to share AST builds between multiple files within a target.
968-
// (https://github.com/swiftlang/sourcekit-lsp/issues/1268)
969-
for fileBatch in fileInfos.partition(intoBatchesOfSize: 1) {
970-
taskGroup.addTask {
971-
let fileAndOutputPaths: [FileAndOutputPath] = fileBatch.compactMap {
972-
guard $0.target == target else {
973-
logger.fault(
974-
"FileIndexInfo refers to different target than should be indexed \($0.target.forLogging) vs \(target.forLogging)"
975-
)
976-
return nil
977-
}
978-
return FileAndOutputPath(file: $0.file, outputPath: $0.outputPath)
979-
}
980-
await self.updateIndexStore(
981-
for: fileAndOutputPaths,
982-
target: target,
983-
language: language,
984-
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
985-
preparationTaskID: preparationTaskID,
986-
priority: priority
971+
return files
972+
}
973+
let batches = await UpdateIndexStoreTaskDescription.batches(
974+
toIndex: fileInfos,
975+
buildServerManager: buildServerManager
976+
)
977+
for (target, language, fileBatch) in batches {
978+
taskGroup.addTask {
979+
let fileAndOutputPaths: [FileAndOutputPath] = fileBatch.compactMap {
980+
guard $0.target == target else {
981+
logger.fault(
982+
"FileIndexInfo refers to different target than should be indexed: \($0.target.forLogging) vs \(target.forLogging)"
987983
)
984+
return nil
988985
}
986+
return FileAndOutputPath(file: $0.file, outputPath: $0.outputPath)
989987
}
988+
await self.updateIndexStore(
989+
for: fileAndOutputPaths,
990+
target: target,
991+
language: language,
992+
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
993+
preparationTaskID: preparationTaskID,
994+
priority: priority
995+
)
990996
}
991997
}
992998
await taskGroup.waitForAll()

0 commit comments

Comments
 (0)