Skip to content

Commit bb699c9

Browse files
authored
Merge pull request #1889 from bnbarham/build-system-override
Allow workspace options to affect build system search
2 parents 84c3c4d + 44bd97b commit bb699c9

23 files changed

+549
-383
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

+16-20
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,7 @@ private enum BuildSystemAdapter {
142142
}
143143

144144
private extension BuildSystemSpec {
145-
private static func createBuiltInBuildSystemAdapter(
146-
projectRoot: URL,
145+
private func createBuiltInBuildSystemAdapter(
147146
messagesToSourceKitLSPHandler: any MessageHandler,
148147
buildSystemHooks: BuildSystemHooks,
149148
_ createBuildSystem: @Sendable (_ connectionToSourceKitLSP: any Connection) async throws -> BuiltInBuildSystem?
@@ -186,6 +185,7 @@ private extension BuildSystemSpec {
186185
let buildSystem = await orLog("Creating external build system") {
187186
try await ExternalBuildSystemAdapter(
188187
projectRoot: projectRoot,
188+
configPath: configPath,
189189
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
190190
)
191191
}
@@ -196,23 +196,18 @@ private extension BuildSystemSpec {
196196
logger.log("Created external build server at \(projectRoot)")
197197
return .external(buildSystem)
198198
case .compilationDatabase:
199-
return await Self.createBuiltInBuildSystemAdapter(
200-
projectRoot: projectRoot,
199+
return await createBuiltInBuildSystemAdapter(
201200
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
202201
buildSystemHooks: buildSystemHooks
203202
) { connectionToSourceKitLSP in
204-
CompilationDatabaseBuildSystem(
205-
projectRoot: projectRoot,
206-
searchPaths: (options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap {
207-
try? RelativePath(validating: $0)
208-
},
203+
try CompilationDatabaseBuildSystem(
204+
configPath: configPath,
209205
connectionToSourceKitLSP: connectionToSourceKitLSP
210206
)
211207
}
212208
case .swiftPM:
213209
#if canImport(PackageModel)
214-
return await Self.createBuiltInBuildSystemAdapter(
215-
projectRoot: projectRoot,
210+
return await createBuiltInBuildSystemAdapter(
216211
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
217212
buildSystemHooks: buildSystemHooks
218213
) { connectionToSourceKitLSP in
@@ -228,8 +223,7 @@ private extension BuildSystemSpec {
228223
return nil
229224
#endif
230225
case .injected(let injector):
231-
return await Self.createBuiltInBuildSystemAdapter(
232-
projectRoot: projectRoot,
226+
return await createBuiltInBuildSystemAdapter(
233227
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
234228
buildSystemHooks: buildSystemHooks
235229
) { connectionToSourceKitLSP in
@@ -248,13 +242,14 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
248242

249243
package let messageHandlingQueue = AsyncQueue<BuildSystemMessageDependencyTracker>()
250244

251-
/// The root of the project that this build system manages.
245+
/// The path to the main configuration file (or directory) that this build system manages.
252246
///
253-
/// For example, in SwiftPM packages this is the folder containing Package.swift.
254-
/// For compilation databases it is the root folder based on which the compilation database was found.
247+
/// Some examples:
248+
/// - The path to `Package.swift` for SwiftPM packages
249+
/// - The path to `compile_commands.json` for a JSON compilation database
255250
///
256251
/// `nil` if the `BuildSystemManager` does not have an underlying build system.
257-
package let projectRoot: URL?
252+
package let configPath: URL?
258253

259254
/// The files for which the delegate has requested change notifications, ie. the files for which the delegate wants to
260255
/// get `fileBuildSettingsChanged` and `filesDependenciesUpdated` callbacks.
@@ -359,7 +354,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
359354
self.toolchainRegistry = toolchainRegistry
360355
self.options = options
361356
self.connectionToClient = connectionToClient
362-
self.projectRoot = buildSystemSpec?.projectRoot
357+
self.configPath = buildSystemSpec?.configPath
363358
self.buildSystemAdapter = await buildSystemSpec?.createBuildSystemAdapter(
364359
toolchainRegistry: toolchainRegistry,
365360
options: options,
@@ -413,6 +408,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
413408
logger.log("Launched a legacy BSP server. Using push-based build settings model.")
414409
let legacyBuildServer = await LegacyBuildServerBuildSystem(
415410
projectRoot: buildSystemSpec.projectRoot,
411+
configPath: buildSystemSpec.configPath,
416412
initializationData: initializeResponse,
417413
externalBuildSystemAdapter
418414
)
@@ -679,8 +675,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
679675
result.formUnion(targets)
680676
}
681677
if !filesAndDirectories.directories.isEmpty, let documentPathComponents = document.fileURL?.pathComponents {
682-
for (directory, (directoryPathComponents, info)) in filesAndDirectories.directories {
683-
guard let directoryPathComponents, let directoryPath = directory.fileURL else {
678+
for (_, (directoryPathComponents, info)) in filesAndDirectories.directories {
679+
guard let directoryPathComponents else {
684680
continue
685681
}
686682
if isDescendant(documentPathComponents, of: directoryPathComponents) {

Sources/BuildSystemIntegration/BuiltInBuildSystem.swift

-5
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ package struct PrepareNotSupportedError: Error, CustomStringConvertible {
3535

3636
/// Provider of FileBuildSettings and other build-related information.
3737
package protocol BuiltInBuildSystem: AnyObject, Sendable {
38-
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
39-
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
40-
/// was found.
41-
var projectRoot: URL { get async }
42-
4338
/// The files to watch for changes.
4439
var fileWatchers: [FileSystemWatcher] { get async }
4540

Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,16 @@ package struct BuildSystemSpec {
3535

3636
package var kind: Kind
3737

38+
/// The folder that best describes the root of the project that this build system handles.
3839
package var projectRoot: URL
3940

40-
package init(kind: BuildSystemSpec.Kind, projectRoot: URL) {
41+
/// The main path that provides the build system configuration.
42+
package var configPath: URL
43+
44+
package init(kind: BuildSystemSpec.Kind, projectRoot: URL, configPath: URL) {
4145
self.kind = kind
4246
self.projectRoot = projectRoot
47+
self.configPath = configPath
4348
}
4449
}
4550

Sources/BuildSystemIntegration/CompilationDatabase.swift

+17-30
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import LanguageServerProtocolExtensions
1818
import SKLogging
1919
import SwiftExtensions
2020
import TSCExtensions
21-
22-
package import struct TSCBasic.RelativePath
2321
#else
2422
import BuildServerProtocol
2523
import Foundation
@@ -28,8 +26,6 @@ import LanguageServerProtocolExtensions
2826
import SKLogging
2927
import SwiftExtensions
3028
import TSCExtensions
31-
32-
import struct TSCBasic.RelativePath
3329
#endif
3430

3531
#if os(Windows)
@@ -116,33 +112,20 @@ package protocol CompilationDatabase {
116112
var sourceItems: [SourceItem] { get }
117113
}
118114

119-
/// Loads the compilation database located in `directory`, if one can be found in `additionalSearchPaths` or in the default search paths of "." and "build".
115+
/// Loads a compilation database from `file`.
120116
package func tryLoadCompilationDatabase(
121-
directory: URL,
122-
additionalSearchPaths: [RelativePath] = []
117+
file: URL
123118
) -> CompilationDatabase? {
124-
let searchPaths =
125-
additionalSearchPaths + [
126-
// These default search paths match the behavior of `clangd`
127-
try! RelativePath(validating: "."),
128-
try! RelativePath(validating: "build"),
129-
]
130-
return
131-
searchPaths
132-
.lazy
133-
.map { directory.appending($0) }
134-
.compactMap { searchPath in
135-
orLog("Failed to load compilation database") { () -> CompilationDatabase? in
136-
if let compDb = try JSONCompilationDatabase(directory: searchPath) {
137-
return compDb
138-
}
139-
if let compDb = try FixedCompilationDatabase(directory: searchPath) {
140-
return compDb
141-
}
142-
return nil
143-
}
119+
orLog("Failed to load compilation database") { () -> CompilationDatabase? in
120+
switch file.lastPathComponent {
121+
case JSONCompilationDatabase.dbName:
122+
return try JSONCompilationDatabase(file: file)
123+
case FixedCompilationDatabase.dbName:
124+
return try FixedCompilationDatabase(file: file)
125+
default:
126+
return nil
144127
}
145-
.first
128+
}
146129
}
147130

148131
/// Fixed clang-compatible compilation database (compile_flags.txt).
@@ -156,6 +139,8 @@ package func tryLoadCompilationDatabase(
156139
///
157140
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html under Alternatives
158141
package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
142+
package static let dbName: String = "compile_flags.txt"
143+
159144
private let fixedArgs: [String]
160145
private let directory: String
161146

@@ -172,7 +157,7 @@ package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
172157
/// Loads the compilation database located in `directory`, if any.
173158
/// - Returns: `nil` if `compile_flags.txt` was not found
174159
package init?(directory: URL) throws {
175-
let path = directory.appendingPathComponent("compile_flags.txt")
160+
let path = directory.appendingPathComponent(Self.dbName)
176161
try self.init(file: path)
177162
}
178163

@@ -212,6 +197,8 @@ package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
212197
///
213198
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html
214199
package struct JSONCompilationDatabase: CompilationDatabase, Equatable, Codable {
200+
package static let dbName: String = "compile_commands.json"
201+
215202
private var pathToCommands: [DocumentURI: [Int]] = [:]
216203
private var commands: [CompilationDatabaseCompileCommand] = []
217204

@@ -232,7 +219,7 @@ package struct JSONCompilationDatabase: CompilationDatabase, Equatable, Codable
232219
///
233220
/// - Returns: `nil` if `compile_commands.json` was not found
234221
package init?(directory: URL) throws {
235-
let path = directory.appendingPathComponent("compile_commands.json")
222+
let path = directory.appendingPathComponent(Self.dbName)
236223
try self.init(file: path)
237224
}
238225

Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift

+48-24
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ package import LanguageServerProtocol
1818
import LanguageServerProtocolExtensions
1919
import SKLogging
2020
package import SKOptions
21+
import SwiftExtensions
2122
import ToolchainRegistry
2223
import TSCExtensions
2324

24-
package import struct TSCBasic.RelativePath
25+
import struct TSCBasic.RelativePath
2526
#else
2627
import BuildServerProtocol
2728
import Dispatch
@@ -30,6 +31,7 @@ import LanguageServerProtocol
3031
import LanguageServerProtocolExtensions
3132
import SKLogging
3233
import SKOptions
34+
import SwiftExtensions
3335
import ToolchainRegistry
3436
import TSCExtensions
3537

@@ -59,13 +61,37 @@ fileprivate enum Cachable<Value> {
5961
/// A `BuildSystem` based on loading clang-compatible compilation database(s).
6062
///
6163
/// Provides build settings from a `CompilationDatabase` found by searching a project. For now, only
62-
/// one compilation database, located at the project root.
64+
/// one compilation database located within the given seach paths (defaulting to the root or inside `build`).
6365
package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
64-
static package func projectRoot(for workspaceFolder: URL, options: SourceKitLSPOptions) -> URL? {
65-
if tryLoadCompilationDatabase(directory: workspaceFolder) != nil {
66-
return workspaceFolder
67-
}
68-
return nil
66+
static package func searchForConfig(in workspaceFolder: URL, options: SourceKitLSPOptions) -> BuildSystemSpec? {
67+
let searchPaths =
68+
(options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap {
69+
try? RelativePath(validating: $0)
70+
} + [
71+
// These default search paths match the behavior of `clangd`
72+
try! RelativePath(validating: "."),
73+
try! RelativePath(validating: "build"),
74+
]
75+
76+
return
77+
searchPaths
78+
.lazy
79+
.compactMap { searchPath in
80+
let path = workspaceFolder.appending(searchPath)
81+
82+
let jsonPath = path.appendingPathComponent(JSONCompilationDatabase.dbName)
83+
if FileManager.default.isFile(at: jsonPath) {
84+
return BuildSystemSpec(kind: .compilationDatabase, projectRoot: workspaceFolder, configPath: jsonPath)
85+
}
86+
87+
let fixedPath = path.appendingPathComponent(FixedCompilationDatabase.dbName)
88+
if FileManager.default.isFile(at: fixedPath) {
89+
return BuildSystemSpec(kind: .compilationDatabase, projectRoot: workspaceFolder, configPath: fixedPath)
90+
}
91+
92+
return nil
93+
}
94+
.first
6995
}
7096

7197
/// The compilation database.
@@ -78,10 +104,15 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
78104
}
79105

80106
private let connectionToSourceKitLSP: any Connection
81-
private let searchPaths: [RelativePath]
82107

83-
package let projectRoot: URL
108+
package let configPath: URL
84109

110+
// Watch for all all changes to `compile_commands.json` and `compile_flags.txt` instead of just the one at
111+
// `configPath` so that we cover the following semi-common scenario:
112+
// The user has a build that stores `compile_commands.json` in `mybuild`. In order to pick it up, they create a
113+
// symlink from `<project root>/compile_commands.json` to `mybuild/compile_commands.json`. We want to get notified
114+
// about the change to `mybuild/compile_commands.json` because it effectively changes the contents of
115+
// `<project root>/compile_commands.json`.
85116
package let fileWatchers: [FileSystemWatcher] = [
86117
FileSystemWatcher(globPattern: "**/compile_commands.json", kind: [.create, .change, .delete]),
87118
FileSystemWatcher(globPattern: "**/compile_flags.txt", kind: [.create, .change, .delete]),
@@ -118,18 +149,17 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
118149
package nonisolated var supportsPreparation: Bool { false }
119150

120151
package init?(
121-
projectRoot: URL,
122-
searchPaths: [RelativePath],
152+
configPath: URL,
123153
connectionToSourceKitLSP: any Connection
124-
) {
125-
self.projectRoot = projectRoot
126-
self.searchPaths = searchPaths
127-
self.connectionToSourceKitLSP = connectionToSourceKitLSP
128-
if let compdb = tryLoadCompilationDatabase(directory: projectRoot, additionalSearchPaths: searchPaths) {
154+
) throws {
155+
if let compdb = tryLoadCompilationDatabase(file: configPath) {
129156
self.compdb = compdb
130157
} else {
131158
return nil
132159
}
160+
self.connectionToSourceKitLSP = connectionToSourceKitLSP
161+
162+
self.configPath = configPath
133163
}
134164

135165
package func buildTargets(request: WorkspaceBuildTargetsRequest) async throws -> WorkspaceBuildTargetsResponse {
@@ -181,19 +211,13 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
181211
}
182212

183213
private func fileEventShouldTriggerCompilationDatabaseReload(event: FileEvent) -> Bool {
184-
switch event.uri.fileURL?.lastPathComponent {
185-
case "compile_commands.json", "compile_flags.txt":
186-
return true
187-
default:
188-
return false
189-
}
214+
return event.uri.fileURL?.lastPathComponent == configPath.lastPathComponent
190215
}
191216

192217
/// The compilation database has been changed on disk.
193218
/// Reload it and notify the delegate about build setting changes.
194219
private func reloadCompilationDatabase() {
195-
self.compdb = tryLoadCompilationDatabase(directory: projectRoot, additionalSearchPaths: searchPaths)
196-
220+
self.compdb = tryLoadCompilationDatabase(file: configPath)
197221
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
198222
}
199223
}

0 commit comments

Comments
 (0)