Skip to content

Commit d9bba40

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`). Do not search up the directory tree when searching for a build system for a workspace. Only allow this for implicit workspaces (created when no existing workspace can handle a file) and only up to any existing workspace roots.
1 parent 1cd010d commit d9bba40

15 files changed

+326
-145
lines changed

Documentation/Configuration File.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The structure of the file is currently not guaranteed to be stable. Options may
4343
- `logLevel: "debug"|"info"|"default"|"error"|"fault"`: The level from which one onwards log messages should be written.
4444
- `privacyLevel: "public"|"private"|"sensitive"`: Whether potentially sensitive information should be redacted. Default is `public`, which redacts potentially sensitive information.
4545
- `inputMirrorDirectory: string`: Write all input received by SourceKit-LSP on stdin to a file in this directory. Useful to record and replay an entire SourceKit-LSP session.
46-
- `defaultWorkspaceType: "buildserver"|"compdb"|"swiftpm"`: Overrides workspace type selection logic.
46+
- `defaultWorkspaceType: "swiftPM"|"compilationDatabase"|"buildServer"`: Overrides workspace type selection logic.
4747
- `generatedFilesPath: string`: Directory in which generated interfaces and macro expansions should be stored.
4848
- `backgroundIndexing: bool`: Explicitly enable or disable background indexing.
4949
- `backgroundPreparationMode: "build"|"noLazy"|"enabled"`: Determines how background indexing should prepare a target. Possible values are:

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,6 @@ private extension BuildSystemSpec {
203203
) { connectionToSourceKitLSP in
204204
CompilationDatabaseBuildSystem(
205205
projectRoot: projectRoot,
206-
searchPaths: (options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap {
207-
try? RelativePath(validating: $0)
208-
},
209206
connectionToSourceKitLSP: connectionToSourceKitLSP
210207
)
211208
}

Sources/BuildSystemIntegration/CompilationDatabase.swift

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -116,33 +116,19 @@ 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 the compilation database located in `directory`.
120120
package func tryLoadCompilationDatabase(
121-
directory: URL,
122-
additionalSearchPaths: [RelativePath] = []
121+
directory: 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+
if let jsonDb = try JSONCompilationDatabase(directory: directory) {
125+
return jsonDb
126+
}
127+
if let fixedDb = try FixedCompilationDatabase(directory: directory) {
128+
return fixedDb
144129
}
145-
.first
130+
return nil
131+
}
146132
}
147133

148134
/// Fixed clang-compatible compilation database (compile_flags.txt).
@@ -156,6 +142,8 @@ package func tryLoadCompilationDatabase(
156142
///
157143
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html under Alternatives
158144
package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
145+
package static let dbName: String = "compile_flags.txt"
146+
159147
private let fixedArgs: [String]
160148
private let directory: String
161149

@@ -172,7 +160,7 @@ package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
172160
/// Loads the compilation database located in `directory`, if any.
173161
/// - Returns: `nil` if `compile_flags.txt` was not found
174162
package init?(directory: URL) throws {
175-
let path = directory.appendingPathComponent("compile_flags.txt")
163+
let path = directory.appendingPathComponent(Self.dbName)
176164
try self.init(file: path)
177165
}
178166

@@ -212,6 +200,8 @@ package struct FixedCompilationDatabase: CompilationDatabase, Equatable {
212200
///
213201
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html
214202
package struct JSONCompilationDatabase: CompilationDatabase, Equatable, Codable {
203+
package static let dbName: String = "compile_commands.json"
204+
215205
private var pathToCommands: [DocumentURI: [Int]] = [:]
216206
private var commands: [CompilationDatabaseCompileCommand] = []
217207

@@ -232,7 +222,7 @@ package struct JSONCompilationDatabase: CompilationDatabase, Equatable, Codable
232222
///
233223
/// - Returns: `nil` if `compile_commands.json` was not found
234224
package init?(directory: URL) throws {
235-
let path = directory.appendingPathComponent("compile_commands.json")
225+
let path = directory.appendingPathComponent(Self.dbName)
236226
try self.init(file: path)
237227
}
238228

Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,29 @@ fileprivate enum Cachable<Value> {
6262
/// one compilation database, located at the project root.
6363
package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
6464
static package func projectRoot(for workspaceFolder: URL, options: SourceKitLSPOptions) -> URL? {
65-
if tryLoadCompilationDatabase(directory: workspaceFolder) != nil {
66-
return workspaceFolder
67-
}
68-
return nil
65+
let searchPaths =
66+
(options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap {
67+
try? RelativePath(validating: $0)
68+
} + [
69+
// These default search paths match the behavior of `clangd`
70+
try! RelativePath(validating: "."),
71+
try! RelativePath(validating: "build"),
72+
]
73+
74+
return
75+
searchPaths
76+
.lazy
77+
.compactMap({ searchPath in
78+
let path = workspaceFolder.appending(searchPath)
79+
if FileManager.default.isFile(at: path.appendingPathComponent(JSONCompilationDatabase.dbName))
80+
|| FileManager.default.isFile(at: path.appendingPathComponent(FixedCompilationDatabase.dbName))
81+
{
82+
return path
83+
}
84+
85+
return nil
86+
})
87+
.first
6988
}
7089

7190
/// The compilation database.
@@ -78,7 +97,6 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
7897
}
7998

8099
private let connectionToSourceKitLSP: any Connection
81-
private let searchPaths: [RelativePath]
82100

83101
package let projectRoot: URL
84102

@@ -119,13 +137,11 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
119137

120138
package init?(
121139
projectRoot: URL,
122-
searchPaths: [RelativePath],
123140
connectionToSourceKitLSP: any Connection
124141
) {
125142
self.projectRoot = projectRoot
126-
self.searchPaths = searchPaths
127143
self.connectionToSourceKitLSP = connectionToSourceKitLSP
128-
if let compdb = tryLoadCompilationDatabase(directory: projectRoot, additionalSearchPaths: searchPaths) {
144+
if let compdb = tryLoadCompilationDatabase(directory: projectRoot) {
129145
self.compdb = compdb
130146
} else {
131147
return nil
@@ -182,7 +198,7 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
182198

183199
private func fileEventShouldTriggerCompilationDatabaseReload(event: FileEvent) -> Bool {
184200
switch event.uri.fileURL?.lastPathComponent {
185-
case "compile_commands.json", "compile_flags.txt":
201+
case JSONCompilationDatabase.dbName, FixedCompilationDatabase.dbName:
186202
return true
187203
default:
188204
return false
@@ -192,8 +208,7 @@ package actor CompilationDatabaseBuildSystem: BuiltInBuildSystem {
192208
/// The compilation database has been changed on disk.
193209
/// Reload it and notify the delegate about build setting changes.
194210
private func reloadCompilationDatabase() {
195-
self.compdb = tryLoadCompilationDatabase(directory: projectRoot, additionalSearchPaths: searchPaths)
196-
211+
self.compdb = tryLoadCompilationDatabase(directory: projectRoot)
197212
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
198213
}
199214
}

Sources/BuildSystemIntegration/DetermineBuildSystem.swift

Lines changed: 8 additions & 2 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] = [
@@ -51,7 +53,11 @@ package func determineBuildSystem(
5153
for buildSystemType in buildSystemPreference {
5254
switch buildSystemType {
5355
case .buildServer:
54-
if let projectRoot = ExternalBuildSystemAdapter.projectRoot(for: workspaceFolderUrl, options: options) {
56+
if let projectRoot = ExternalBuildSystemAdapter.projectRoot(
57+
for: workspaceFolderUrl,
58+
onlyConsiderRoot: onlyConsiderRoot,
59+
options: options
60+
) {
5561
return BuildSystemSpec(kind: .buildServer, projectRoot: projectRoot)
5662
}
5763
case .compilationDatabase:

Sources/BuildSystemIntegration/ExternalBuildSystemAdapter.swift

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,12 @@ actor ExternalBuildSystemAdapter {
9999
/// Used to delay restarting in case of a crash loop.
100100
private var lastRestart: Date?
101101

102-
static package func projectRoot(for workspaceFolder: URL, options: SourceKitLSPOptions) -> URL? {
103-
guard getConfigPath(for: workspaceFolder) != nil else {
102+
static package func projectRoot(
103+
for workspaceFolder: URL,
104+
onlyConsiderRoot: Bool,
105+
options: SourceKitLSPOptions
106+
) -> URL? {
107+
guard getConfigPath(for: workspaceFolder, onlyConsiderRoot: onlyConsiderRoot) != nil else {
104108
return nil
105109
}
106110
return workspaceFolder
@@ -187,38 +191,40 @@ actor ExternalBuildSystemAdapter {
187191
).connection
188192
}
189193

190-
private static func getConfigPath(for workspaceFolder: URL? = nil) -> URL? {
194+
private static func getConfigPath(for workspaceFolder: URL? = nil, onlyConsiderRoot: Bool = false) -> URL? {
191195
var buildServerConfigLocations: [URL?] = []
192196
if let workspaceFolder = workspaceFolder {
193197
buildServerConfigLocations.append(workspaceFolder.appendingPathComponent(".bsp"))
194198
}
195199

196-
#if os(Windows)
197-
if let localAppData = ProcessInfo.processInfo.environment["LOCALAPPDATA"] {
198-
buildServerConfigLocations.append(URL(fileURLWithPath: localAppData).appendingPathComponent("bsp"))
199-
}
200-
if let programData = ProcessInfo.processInfo.environment["PROGRAMDATA"] {
201-
buildServerConfigLocations.append(URL(fileURLWithPath: programData).appendingPathComponent("bsp"))
202-
}
203-
#else
204-
if let xdgDataHome = ProcessInfo.processInfo.environment["XDG_DATA_HOME"] {
205-
buildServerConfigLocations.append(URL(fileURLWithPath: xdgDataHome).appendingPathComponent("bsp"))
206-
}
200+
if !onlyConsiderRoot {
201+
#if os(Windows)
202+
if let localAppData = ProcessInfo.processInfo.environment["LOCALAPPDATA"] {
203+
buildServerConfigLocations.append(URL(fileURLWithPath: localAppData).appendingPathComponent("bsp"))
204+
}
205+
if let programData = ProcessInfo.processInfo.environment["PROGRAMDATA"] {
206+
buildServerConfigLocations.append(URL(fileURLWithPath: programData).appendingPathComponent("bsp"))
207+
}
208+
#else
209+
if let xdgDataHome = ProcessInfo.processInfo.environment["XDG_DATA_HOME"] {
210+
buildServerConfigLocations.append(URL(fileURLWithPath: xdgDataHome).appendingPathComponent("bsp"))
211+
}
207212

208-
if let libraryUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
209-
buildServerConfigLocations.append(libraryUrl.appendingPathComponent("bsp"))
210-
}
213+
if let libraryUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
214+
buildServerConfigLocations.append(libraryUrl.appendingPathComponent("bsp"))
215+
}
211216

212-
if let xdgDataDirs = ProcessInfo.processInfo.environment["XDG_DATA_DIRS"] {
213-
buildServerConfigLocations += xdgDataDirs.split(separator: ":").map { xdgDataDir in
214-
URL(fileURLWithPath: String(xdgDataDir)).appendingPathComponent("bsp")
217+
if let xdgDataDirs = ProcessInfo.processInfo.environment["XDG_DATA_DIRS"] {
218+
buildServerConfigLocations += xdgDataDirs.split(separator: ":").map { xdgDataDir in
219+
URL(fileURLWithPath: String(xdgDataDir)).appendingPathComponent("bsp")
220+
}
215221
}
216-
}
217222

218-
if let libraryUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .systemDomainMask).first {
219-
buildServerConfigLocations.append(libraryUrl.appendingPathComponent("bsp"))
223+
if let libraryUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .systemDomainMask).first {
224+
buildServerConfigLocations.append(libraryUrl.appendingPathComponent("bsp"))
225+
}
226+
#endif
220227
}
221-
#endif
222228

223229
for case let buildServerConfigLocation? in buildServerConfigLocations {
224230
let jsonFiles =

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,20 +227,15 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
227227
private var targetDependencies: [BuildTargetIdentifier: Set<BuildTargetIdentifier>] = [:]
228228

229229
static package func projectRoot(for path: URL, options: SourceKitLSPOptions) -> URL? {
230-
guard var path = orLog("Getting realpath for project root", { try path.realpath }) else {
230+
guard let path = orLog("Getting realpath for project root", { try path.realpath }) else {
231231
return nil
232232
}
233-
while true {
234-
let packagePath = path.appendingPathComponent("Package.swift")
235-
if (try? String(contentsOf: packagePath, encoding: .utf8))?.contains("PackageDescription") ?? false {
236-
return path
237-
}
238233

239-
if (try? AbsolutePath(validating: path.filePath))?.isRoot ?? true {
240-
break
241-
}
242-
path.deleteLastPathComponent()
234+
let packagePath = path.appendingPathComponent("Package.swift")
235+
if (try? String(contentsOf: packagePath, encoding: .utf8))?.contains("PackageDescription") ?? false {
236+
return path
243237
}
238+
244239
return nil
245240
}
246241

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
280280
set { logging = newValue }
281281
}
282282

283-
/// Default workspace type (buildserver|compdb|swiftpm). Overrides workspace type selection logic.
283+
/// Default workspace type (swiftPM|compilationDatabase|buildServer). Overrides workspace type selection logic.
284284
public var defaultWorkspaceType: WorkspaceType?
285285
public var generatedFilesPath: String?
286286

@@ -444,6 +444,17 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
444444
)
445445
}
446446

447+
public static func merging(base: SourceKitLSPOptions, workspaceFolder: DocumentURI) -> SourceKitLSPOptions {
448+
return SourceKitLSPOptions.merging(
449+
base: base,
450+
override: SourceKitLSPOptions(
451+
path: workspaceFolder.fileURL?
452+
.appendingPathComponent(".sourcekit-lsp")
453+
.appendingPathComponent("config.json")
454+
)
455+
)
456+
}
457+
447458
public var generatedFilesAbsolutePath: URL {
448459
if let generatedFilesPath {
449460
return URL(fileURLWithPath: generatedFilesPath)

Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ package struct IndexedSingleSwiftFileTestProject {
133133
let encoder = JSONEncoder()
134134
encoder.outputFormatting = .prettyPrinted
135135
try encoder.encode(compilationDatabase).write(
136-
to: testWorkspaceDirectory.appendingPathComponent("compile_commands.json")
136+
to: testWorkspaceDirectory.appendingPathComponent(JSONCompilationDatabase.dbName)
137137
)
138138

139139
// Run swiftc to build the index store

0 commit comments

Comments
 (0)