Skip to content

Commit c19f78d

Browse files
authored
Merge pull request #1972 from bnbarham/6.1-pass-allow-errors-for-ast
[6.1] Update the compiler arguments used for background AST builds
2 parents 89f7613 + 786f807 commit c19f78d

13 files changed

+325
-240
lines changed

Contributor Documentation/Implementing a BSP server.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ If the build system loads the entire build graph during initialization, it may i
2929

3030
## Supporting background indexing
3131

32-
To support background indexing, the build system must set `data.prepareProvider: true` in the `build/initialize` response and implement the `buildTarget/prepare` method.
32+
To support background indexing, the build system must set `data.prepareProvider: true` in the `build/initialize` response and implement the `buildTarget/prepare` method. The compiler options used to prepare a target should match those sent for `textDocument/sourceKitOptions` in order to avoid mismatches when loading modules.
3333

3434
## Optional methods
3535

Sources/BuildSystemIntegration/BuildSystemManager.swift

+164-5
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
314314
}
315315
}
316316

317-
private var cachedSourceKitOptions = RequestCache<TextDocumentSourceKitOptionsRequest>()
317+
private var cachedAdjustedSourceKitOptions = RequestCache<TextDocumentSourceKitOptionsRequest>()
318318

319319
private var cachedBuildTargets = Cache<WorkspaceBuildTargetsRequest, [BuildTargetIdentifier: BuildTargetInfo]>()
320320

@@ -518,7 +518,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
518518
} else {
519519
nil
520520
}
521-
self.cachedSourceKitOptions.clear(isolation: self) { cacheKey in
521+
self.cachedAdjustedSourceKitOptions.clear(isolation: self) { cacheKey in
522522
guard let updatedTargets else {
523523
// All targets might have changed
524524
return true
@@ -747,13 +747,22 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
747747
target: target,
748748
language: language
749749
)
750-
751-
let response = try await cachedSourceKitOptions.get(request, isolation: self) { request in
752-
try await buildSystemAdapter.send(request)
750+
let response = try await cachedAdjustedSourceKitOptions.get(request, isolation: self) { request in
751+
let options = try await buildSystemAdapter.send(request)
752+
switch language.semanticKind {
753+
case .swift:
754+
return options?.adjustArgsForSemanticSwiftFunctionality(fileToIndex: document)
755+
case .clang:
756+
return options?.adjustingArgsForSemanticClangFunctionality()
757+
default:
758+
return options
759+
}
753760
}
761+
754762
guard let response else {
755763
return nil
756764
}
765+
757766
return FileBuildSettings(
758767
compilerArguments: response.compilerArguments,
759768
workingDirectory: response.workingDirectory,
@@ -1228,3 +1237,153 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
12281237
private func isDescendant(_ selfPathComponents: [String], of otherPathComponents: [String]) -> Bool {
12291238
return selfPathComponents.dropLast().starts(with: otherPathComponents)
12301239
}
1240+
1241+
fileprivate extension TextDocumentSourceKitOptionsResponse {
1242+
/// Adjust compiler arguments that were created for building to compiler arguments that should be used for indexing
1243+
/// or background AST builds.
1244+
///
1245+
/// This removes compiler arguments that produce output files and adds arguments to eg. allow errors and index the
1246+
/// file.
1247+
func adjustArgsForSemanticSwiftFunctionality(fileToIndex: DocumentURI) -> TextDocumentSourceKitOptionsResponse {
1248+
let optionsToRemove: [CompilerCommandLineOption] = [
1249+
.flag("c", [.singleDash]),
1250+
.flag("disable-cmo", [.singleDash]),
1251+
.flag("emit-dependencies", [.singleDash]),
1252+
.flag("emit-module-interface", [.singleDash]),
1253+
.flag("emit-module", [.singleDash]),
1254+
.flag("emit-objc-header", [.singleDash]),
1255+
.flag("incremental", [.singleDash]),
1256+
.flag("no-color-diagnostics", [.singleDash]),
1257+
.flag("parseable-output", [.singleDash]),
1258+
.flag("save-temps", [.singleDash]),
1259+
.flag("serialize-diagnostics", [.singleDash]),
1260+
.flag("use-frontend-parseable-output", [.singleDash]),
1261+
.flag("validate-clang-modules-once", [.singleDash]),
1262+
.flag("whole-module-optimization", [.singleDash]),
1263+
.flag("experimental-skip-all-function-bodies", frontendName: "Xfrontend", [.singleDash]),
1264+
.flag("experimental-skip-non-inlinable-function-bodies", frontendName: "Xfrontend", [.singleDash]),
1265+
.flag("experimental-skip-non-exportable-decls", frontendName: "Xfrontend", [.singleDash]),
1266+
.flag("experimental-lazy-typecheck", frontendName: "Xfrontend", [.singleDash]),
1267+
1268+
.option("clang-build-session-file", [.singleDash], [.separatedBySpace]),
1269+
.option("emit-module-interface-path", [.singleDash], [.separatedBySpace]),
1270+
.option("emit-module-path", [.singleDash], [.separatedBySpace]),
1271+
.option("emit-objc-header-path", [.singleDash], [.separatedBySpace]),
1272+
.option("emit-package-module-interface-path", [.singleDash], [.separatedBySpace]),
1273+
.option("emit-private-module-interface-path", [.singleDash], [.separatedBySpace]),
1274+
.option("num-threads", [.singleDash], [.separatedBySpace]),
1275+
// Technically, `-o` and the output file don't need to be separated by a space. Eg. `swiftc -oa file.swift` is
1276+
// valid and will write to an output file named `a`.
1277+
// We can't support that because the only way to know that `-output-file-map` is a different flag and not an option
1278+
// to write to an output file named `utput-file-map` is to know all compiler arguments of `swiftc`, which we don't.
1279+
.option("o", [.singleDash], [.separatedBySpace]),
1280+
.option("output-file-map", [.singleDash], [.separatedBySpace, .separatedByEqualSign]),
1281+
]
1282+
1283+
var result: [String] = []
1284+
result.reserveCapacity(compilerArguments.count)
1285+
var iterator = compilerArguments.makeIterator()
1286+
while let argument = iterator.next() {
1287+
switch optionsToRemove.firstMatch(for: argument) {
1288+
case .removeOption:
1289+
continue
1290+
case .removeOptionAndNextArgument:
1291+
_ = iterator.next()
1292+
continue
1293+
case .removeOptionAndPreviousArgument(let name):
1294+
if let previousArg = result.last, previousArg.hasSuffix("-\(name)") {
1295+
_ = result.popLast()
1296+
}
1297+
continue
1298+
case nil:
1299+
break
1300+
}
1301+
result.append(argument)
1302+
}
1303+
1304+
result += [
1305+
// Avoid emitting the ABI descriptor, we don't need it
1306+
"-Xfrontend", "-empty-abi-descriptor",
1307+
]
1308+
1309+
result += supplementalClangIndexingArgs.flatMap { ["-Xcc", $0] }
1310+
1311+
return TextDocumentSourceKitOptionsResponse(compilerArguments: result, workingDirectory: workingDirectory)
1312+
}
1313+
1314+
/// Adjust compiler arguments that were created for building to compiler arguments that should be used for indexing
1315+
/// or background AST builds.
1316+
///
1317+
/// This removes compiler arguments that produce output files and adds arguments to eg. typecheck only.
1318+
func adjustingArgsForSemanticClangFunctionality() -> TextDocumentSourceKitOptionsResponse {
1319+
let optionsToRemove: [CompilerCommandLineOption] = [
1320+
// Disable writing of a depfile
1321+
.flag("M", [.singleDash]),
1322+
.flag("MD", [.singleDash]),
1323+
.flag("MMD", [.singleDash]),
1324+
.flag("MG", [.singleDash]),
1325+
.flag("MM", [.singleDash]),
1326+
.flag("MV", [.singleDash]),
1327+
// Don't create phony targets
1328+
.flag("MP", [.singleDash]),
1329+
// Don't write out compilation databases
1330+
.flag("MJ", [.singleDash]),
1331+
// Don't compile
1332+
.flag("c", [.singleDash]),
1333+
1334+
.flag("fmodules-validate-once-per-build-session", [.singleDash]),
1335+
1336+
// Disable writing of a depfile
1337+
.option("MT", [.singleDash], [.noSpace, .separatedBySpace]),
1338+
.option("MF", [.singleDash], [.noSpace, .separatedBySpace]),
1339+
.option("MQ", [.singleDash], [.noSpace, .separatedBySpace]),
1340+
1341+
// Don't write serialized diagnostic files
1342+
.option("serialize-diagnostics", [.singleDash, .doubleDash], [.separatedBySpace]),
1343+
1344+
.option("fbuild-session-file", [.singleDash], [.separatedByEqualSign]),
1345+
]
1346+
1347+
var result: [String] = []
1348+
result.reserveCapacity(compilerArguments.count)
1349+
var iterator = compilerArguments.makeIterator()
1350+
while let argument = iterator.next() {
1351+
switch optionsToRemove.firstMatch(for: argument) {
1352+
case .removeOption:
1353+
continue
1354+
case .removeOptionAndNextArgument:
1355+
_ = iterator.next()
1356+
continue
1357+
case .removeOptionAndPreviousArgument(let name):
1358+
if let previousArg = result.last, previousArg.hasSuffix("-\(name)") {
1359+
_ = result.popLast()
1360+
}
1361+
continue
1362+
case nil:
1363+
break
1364+
}
1365+
result.append(argument)
1366+
}
1367+
result += supplementalClangIndexingArgs
1368+
result.append(
1369+
"-fsyntax-only"
1370+
)
1371+
return TextDocumentSourceKitOptionsResponse(compilerArguments: result, workingDirectory: workingDirectory)
1372+
}
1373+
}
1374+
1375+
fileprivate let supplementalClangIndexingArgs: [String] = [
1376+
// Retain extra information for indexing
1377+
"-fretain-comments-from-system-headers",
1378+
// Pick up macro definitions during indexing
1379+
"-Xclang", "-detailed-preprocessing-record",
1380+
1381+
// libclang uses 'raw' module-format. Match it so we can reuse the module cache and PCHs that libclang uses.
1382+
"-Xclang", "-fmodule-format=raw",
1383+
1384+
// Be less strict - we want to continue and typecheck/index as much as possible
1385+
"-Xclang", "-fallow-pch-with-compiler-errors",
1386+
"-Xclang", "-fallow-pcm-with-compiler-errors",
1387+
"-Wno-non-modular-include-in-framework-module",
1388+
"-Wno-incomplete-umbrella",
1389+
]

Sources/BuildSystemIntegration/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(BuildSystemIntegration STATIC
1010
BuiltInBuildSystemAdapter.swift
1111
CompilationDatabase.swift
1212
CompilationDatabaseBuildSystem.swift
13+
CompilerCommandLineOption.swift
1314
DetermineBuildSystem.swift
1415
ExternalBuildSystemAdapter.swift
1516
FallbackBuildSettings.swift

Sources/SemanticIndex/CompilerCommandLineOption.swift renamed to Sources/BuildSystemIntegration/CompilerCommandLineOption.swift

+39-4
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@
1212

1313
package struct CompilerCommandLineOption {
1414
/// Return value of `matches(argument:)`.
15-
package enum Match {
15+
package enum Match: Equatable {
1616
/// The `CompilerCommandLineOption` matched the command line argument. The next element in the command line is a
1717
/// separate argument and should not be removed.
1818
case removeOption
1919

2020
/// The `CompilerCommandLineOption` matched the command line argument. The next element in the command line is an
2121
/// argument to this option and should be removed as well.
2222
case removeOptionAndNextArgument
23+
24+
/// The `CompilerCommandLineOption` matched the command line argument. The previous element in the command line is
25+
/// a prefix to this argument and should be removed if it matches `name`.
26+
case removeOptionAndPreviousArgument(name: String)
2327
}
2428

2529
package enum DashSpelling {
@@ -39,16 +43,28 @@ package struct CompilerCommandLineOption {
3943
/// The name of the option, without any preceeding `-` or `--`.
4044
private let name: String
4145

46+
/// The name of the argument that prefixes this flag, without any preceeding `-` or `--` (eg. `Xfrontend`/`Xclang`).
47+
private let frontendName: String?
48+
4249
/// Whether the option can be spelled with one or two dashes.
4350
private let dashSpellings: [DashSpelling]
4451

4552
/// The ways that arguments can specified after the option. Empty if the option is a flag that doesn't take any
4653
/// argument.
4754
private let argumentStyles: [ArgumentStyles]
4855

49-
package static func flag(_ name: String, _ dashSpellings: [DashSpelling]) -> CompilerCommandLineOption {
56+
package static func flag(
57+
_ name: String,
58+
frontendName: String? = nil,
59+
_ dashSpellings: [DashSpelling]
60+
) -> CompilerCommandLineOption {
5061
precondition(!dashSpellings.isEmpty)
51-
return CompilerCommandLineOption(name: name, dashSpellings: dashSpellings, argumentStyles: [])
62+
return CompilerCommandLineOption(
63+
name: name,
64+
frontendName: frontendName,
65+
dashSpellings: dashSpellings,
66+
argumentStyles: []
67+
)
5268
}
5369

5470
package static func option(
@@ -58,10 +74,29 @@ package struct CompilerCommandLineOption {
5874
) -> CompilerCommandLineOption {
5975
precondition(!dashSpellings.isEmpty)
6076
precondition(!argumentStyles.isEmpty)
61-
return CompilerCommandLineOption(name: name, dashSpellings: dashSpellings, argumentStyles: argumentStyles)
77+
return CompilerCommandLineOption(
78+
name: name,
79+
frontendName: nil,
80+
dashSpellings: dashSpellings,
81+
argumentStyles: argumentStyles
82+
)
6283
}
6384

6485
package func matches(argument: String) -> Match? {
86+
let match = matchesIgnoringFrontend(argument: argument)
87+
guard let match, let frontendName else {
88+
return match
89+
}
90+
91+
switch match {
92+
case .removeOption:
93+
return .removeOptionAndPreviousArgument(name: frontendName)
94+
default:
95+
return match
96+
}
97+
}
98+
99+
private func matchesIgnoringFrontend(argument: String) -> Match? {
65100
let argumentName: Substring
66101
if argument.hasPrefix("--") {
67102
if dashSpellings.contains(.doubleDash) {

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
309309
configuration: buildConfiguration,
310310
toolchain: destinationSwiftPMToolchain,
311311
triple: destinationSDK.targetTriple,
312-
flags: buildFlags
312+
flags: buildFlags,
313+
prepareForIndexing: options.backgroundPreparationModeOrDefault.toSwiftPMPreparation
313314
)
314315

315316
packageLoadingQueue.async {
@@ -735,4 +736,17 @@ fileprivate extension URL {
735736
}
736737
}
737738

739+
fileprivate extension SourceKitLSPOptions.BackgroundPreparationMode {
740+
var toSwiftPMPreparation: BuildParameters.PrepareForIndexingMode {
741+
switch self {
742+
case .build:
743+
return .off
744+
case .noLazy:
745+
return .noLazy
746+
case .enabled:
747+
return .on
748+
}
749+
}
750+
}
751+
738752
#endif

Sources/LanguageServerProtocolExtensions/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ add_library(LanguageServerProtocolExtensions STATIC
33
Connection+Send.swift
44
DocumentURI+CustomLogStringConvertible.swift
55
DocumentURI+symlinkTarget.swift
6-
Language+InferredFromFileExtension.swift
6+
Language+Inference.swift
77
LocalConnection.swift
88
QueueBasedMessageHandler.swift
99
RequestAndReply.swift

Sources/LanguageServerProtocolExtensions/Language+InferredFromFileExtension.swift renamed to Sources/LanguageServerProtocolExtensions/Language+Inference.swift

+16
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ import LanguageServerProtocol
1919
#endif
2020

2121
extension Language {
22+
package enum SemanticKind {
23+
case clang
24+
case swift
25+
}
26+
27+
package var semanticKind: SemanticKind? {
28+
switch self {
29+
case .swift:
30+
return .swift
31+
case .c, .cpp, .objective_c, .objective_cpp:
32+
return .clang
33+
default:
34+
return nil
35+
}
36+
}
37+
2238
package init?(inferredFromFileExtension uri: DocumentURI) {
2339
// URL.pathExtension is only set for file URLs but we want to also infer a file extension for non-file URLs like
2440
// untitled:file.cpp

Sources/SemanticIndex/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11

22
add_library(SemanticIndex STATIC
33
CheckedIndex.swift
4-
CompilerCommandLineOption.swift
54
IndexTaskDescription.swift
65
IndexTestHooks.swift
76
PreparationTaskDescription.swift

0 commit comments

Comments
 (0)