Skip to content

Commit 80941d8

Browse files
committed
Allow workspace options to affect build system search
There were a few places that options only took place *after* determining a build system, even though we have multiple that impact the search (eg. `defaultBuildSystem` and `searchPaths`). Additionally track project root and configuration paths separately, so that when searching for implicit workspaces we can make sure to skip creating duplicates.
1 parent 403f88e commit 80941d8

22 files changed

+458
-229
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 16 additions & 20 deletions
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
buildSystemTestHooks: BuildSystemTestHooks,
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
buildSystemTestHooks: testHooks
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
buildSystemTestHooks: testHooks
218213
) { connectionToSourceKitLSP in
@@ -228,12 +223,11 @@ private extension BuildSystemSpec {
228223
return nil
229224
#endif
230225
case .testBuildSystem:
231-
return await Self.createBuiltInBuildSystemAdapter(
232-
projectRoot: projectRoot,
226+
return await createBuiltInBuildSystemAdapter(
233227
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
234228
buildSystemTestHooks: testHooks
235229
) { connectionToSourceKitLSP in
236-
TestBuildSystem(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
230+
TestBuildSystem(configPath: configPath, connectionToSourceKitLSP: connectionToSourceKitLSP)
237231
}
238232
}
239233
}
@@ -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.
@@ -372,7 +367,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
372367
self.toolchainRegistry = toolchainRegistry
373368
self.options = options
374369
self.connectionToClient = connectionToClient
375-
self.projectRoot = buildSystemSpec?.projectRoot
370+
self.configPath = buildSystemSpec?.configPath
376371
self.buildSystemAdapter = await buildSystemSpec?.createBuildSystemAdapter(
377372
toolchainRegistry: toolchainRegistry,
378373
options: options,
@@ -426,6 +421,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
426421
logger.log("Launched a legacy BSP server. Using push-based build settings model.")
427422
let legacyBuildServer = await LegacyBuildServerBuildSystem(
428423
projectRoot: buildSystemSpec.projectRoot,
424+
configPath: buildSystemSpec.configPath,
429425
initializationData: initializeResponse,
430426
externalBuildSystemAdapter
431427
)
@@ -693,7 +689,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
693689
}
694690
if !filesAndDirectories.directories.isEmpty, let documentPathComponents = document.fileURL?.pathComponents {
695691
for (directory, (directoryPathComponents, info)) in filesAndDirectories.directories {
696-
guard let directoryPathComponents, let directoryPath = directory.fileURL else {
692+
guard let directoryPathComponents, directory.fileURL != nil else {
697693
continue
698694
}
699695
if isDescendant(documentPathComponents, of: directoryPathComponents) {

Sources/BuildSystemIntegration/BuiltInBuildSystem.swift

Lines changed: 0 additions & 5 deletions
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

Lines changed: 6 additions & 1 deletion
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

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -116,33 +116,20 @@ package protocol CompilationDatabase {
116116
var sourceItems: [SourceItem] { get }
117117
}
118118

119-
/// Loads the compilation database located in `directory`, if one can be found in `additionalSearchPaths` or in the default search paths of "." and "build".
119+
/// Loads a compilation database from `file`.
120120
package func tryLoadCompilationDatabase(
121-
directory: URL,
122-
additionalSearchPaths: [RelativePath] = []
121+
file: URL
123122
) -> 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-
}
123+
orLog("Failed to load compilation database") { () -> CompilationDatabase? in
124+
switch file.lastPathComponent {
125+
case JSONCompilationDatabase.dbName:
126+
return try JSONCompilationDatabase(file: file)
127+
case FixedCompilationDatabase.dbName:
128+
return try FixedCompilationDatabase(file: file)
129+
default:
130+
return nil
144131
}
145-
.first
132+
}
146133
}
147134

148135
/// Fixed clang-compatible compilation database (compile_flags.txt).
@@ -156,6 +143,8 @@ package func tryLoadCompilationDatabase(
156143
///
157144
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html under Alternatives
158145
package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
146+
package static let dbName: String = "compile_flags.txt"
147+
159148
private let fixedArgs: [String]
160149
private let directory: String
161150

@@ -172,7 +161,7 @@ package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
172161
/// Loads the compilation database located in `directory`, if any.
173162
/// - Returns: `nil` if `compile_flags.txt` was not found
174163
package init?(directory: URL) throws {
175-
let path = directory.appendingPathComponent("compile_flags.txt")
164+
let path = directory.appendingPathComponent(Self.dbName)
176165
try self.init(file: path)
177166
}
178167

@@ -212,6 +201,8 @@ package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
212201
///
213202
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html
214203
package struct JSONCompilationDatabase: CompilationDatabase, Equatable, Codable {
204+
package static let dbName: String = "compile_commands.json"
205+
215206
private var pathToCommands: [DocumentURI: [Int]] = [:]
216207
private var commands: [CompilationDatabaseCompileCommand] = []
217208

@@ -232,7 +223,7 @@ package struct JSONCompilationDatabase: CompilationDatabase, Equatable, Codable
232223
///
233224
/// - Returns: `nil` if `compile_commands.json` was not found
234225
package init?(directory: URL) throws {
235-
let path = directory.appendingPathComponent("compile_commands.json")
226+
let path = directory.appendingPathComponent(Self.dbName)
236227
try self.init(file: path)
237228
}
238229

Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package import LanguageServerProtocol
1818
import LanguageServerProtocolExtensions
1919
import SKLogging
2020
package import SKOptions
21+
import SwiftExtensions
2122
import ToolchainRegistry
2223
import TSCExtensions
2324

@@ -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,14 +104,10 @@ 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

85-
package let fileWatchers: [FileSystemWatcher] = [
86-
FileSystemWatcher(globPattern: "**/compile_commands.json", kind: [.create, .change, .delete]),
87-
FileSystemWatcher(globPattern: "**/compile_flags.txt", kind: [.create, .change, .delete]),
88-
]
110+
package let fileWatchers: [FileSystemWatcher]
89111

90112
private var _indexStorePath: Cachable<URL?> = .noValue
91113
package var indexStorePath: URL? {
@@ -118,18 +140,20 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
118140
package nonisolated var supportsPreparation: Bool { false }
119141

120142
package init?(
121-
projectRoot: URL,
122-
searchPaths: [RelativePath],
143+
configPath: URL,
123144
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) {
145+
) throws {
146+
if let compdb = tryLoadCompilationDatabase(file: configPath) {
129147
self.compdb = compdb
130148
} else {
131149
return nil
132150
}
151+
self.connectionToSourceKitLSP = connectionToSourceKitLSP
152+
153+
self.configPath = configPath
154+
self.fileWatchers = [
155+
FileSystemWatcher(globPattern: try configPath.filePath, kind: [.change])
156+
]
133157
}
134158

135159
package func buildTargets(request: WorkspaceBuildTargetsRequest) async throws -> WorkspaceBuildTargetsResponse {
@@ -182,7 +206,7 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
182206

183207
private func fileEventShouldTriggerCompilationDatabaseReload(event: FileEvent) -> Bool {
184208
switch event.uri.fileURL?.lastPathComponent {
185-
case "compile_commands.json", "compile_flags.txt":
209+
case JSONCompilationDatabase.dbName, FixedCompilationDatabase.dbName:
186210
return true
187211
default:
188212
return false
@@ -192,8 +216,7 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
192216
/// The compilation database has been changed on disk.
193217
/// Reload it and notify the delegate about build setting changes.
194218
private func reloadCompilationDatabase() {
195-
self.compdb = tryLoadCompilationDatabase(directory: projectRoot, additionalSearchPaths: searchPaths)
196-
219+
self.compdb = tryLoadCompilationDatabase(file: configPath)
197220
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
198221
}
199222
}

Sources/BuildSystemIntegration/DetermineBuildSystem.swift

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ import struct TSCBasic.AbsolutePath
3131
#endif
3232

3333
/// Determine which build system should be started to handle the given workspace folder and at which folder that build
34-
/// system's project root is (see `BuiltInBuildSystem.projectRoot(for:options:)`).
34+
/// system's project root is (see `BuiltInBuildSystem.projectRoot(for:options:)`). `onlyConsiderRoot` controls whether
35+
/// paths outside the root should be considered (eg. configuration files in the user's home directory).
3536
///
3637
/// Returns `nil` if no build system can handle this workspace folder.
3738
package func determineBuildSystem(
3839
forWorkspaceFolder workspaceFolder: DocumentURI,
40+
onlyConsiderRoot: Bool,
3941
options: SourceKitLSPOptions
4042
) -> BuildSystemSpec? {
4143
var buildSystemPreference: [WorkspaceType] = [
@@ -49,24 +51,26 @@ package func determineBuildSystem(
4951
return nil
5052
}
5153
for buildSystemType in buildSystemPreference {
54+
var spec: BuildSystemSpec? = nil
55+
5256
switch buildSystemType {
5357
case .buildServer:
54-
if let projectRoot = ExternalBuildSystemAdapter.projectRoot(for: workspaceFolderUrl, options: options) {
55-
return BuildSystemSpec(kind: .buildServer, projectRoot: projectRoot)
56-
}
58+
spec = ExternalBuildSystemAdapter.searchForConfig(
59+
in: workspaceFolderUrl,
60+
onlyConsiderRoot: onlyConsiderRoot,
61+
options: options
62+
)
5763
case .compilationDatabase:
58-
if let projectRoot = CompilationDatabaseBuildSystem.projectRoot(for: workspaceFolderUrl, options: options) {
59-
return BuildSystemSpec(kind: .compilationDatabase, projectRoot: projectRoot)
60-
}
64+
spec = CompilationDatabaseBuildSystem.searchForConfig(in: workspaceFolderUrl, options: options)
6165
case .swiftPM:
6266
#if canImport(PackageModel)
63-
if let projectRoot = SwiftPMBuildSystem.projectRoot(for: workspaceFolderUrl, options: options) {
64-
return BuildSystemSpec(kind: .swiftPM, projectRoot: projectRoot)
65-
}
66-
#else
67-
return nil
67+
spec = SwiftPMBuildSystem.searchForConfig(in: workspaceFolderUrl, options: options)
6868
#endif
6969
}
70+
71+
if spec != nil {
72+
return spec
73+
}
7074
}
7175

7276
return nil

0 commit comments

Comments
 (0)