Skip to content

Commit d5978d5

Browse files
authored
Merge pull request #1971 from bnbarham/pass-allow-errors-for-ast
Update the compiler arguments used for background AST builds
2 parents 825c17e + ab12429 commit d5978d5

13 files changed

+325
-229
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
@@ -325,7 +325,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
325325
}
326326
}
327327

328-
private var cachedSourceKitOptions = RequestCache<TextDocumentSourceKitOptionsRequest>()
328+
private var cachedAdjustedSourceKitOptions = RequestCache<TextDocumentSourceKitOptionsRequest>()
329329

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

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

Sources/BuildSystemIntegration/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_library(BuildSystemIntegration STATIC
99
BuiltInBuildSystem.swift
1010
BuiltInBuildSystemAdapter.swift
1111
CompilationDatabase.swift
12+
CompilerCommandLineOption.swift
1213
DetermineBuildSystem.swift
1314
ExternalBuildSystemAdapter.swift
1415
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
@@ -287,7 +287,8 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
287287
configuration: buildConfiguration,
288288
toolchain: destinationSwiftPMToolchain,
289289
triple: destinationSDK.targetTriple,
290-
flags: buildFlags
290+
flags: buildFlags,
291+
prepareForIndexing: options.backgroundPreparationModeOrDefault.toSwiftPMPreparation
291292
)
292293

293294
packageLoadingQueue.async {
@@ -709,4 +710,17 @@ fileprivate extension URL {
709710
}
710711
}
711712

713+
fileprivate extension SourceKitLSPOptions.BackgroundPreparationMode {
714+
var toSwiftPMPreparation: BuildParameters.PrepareForIndexingMode {
715+
switch self {
716+
case .build:
717+
return .off
718+
case .noLazy:
719+
return .noLazy
720+
case .enabled:
721+
return .on
722+
}
723+
}
724+
}
725+
712726
#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
IndexHooks.swift
76
PreparationTaskDescription.swift

0 commit comments

Comments
 (0)