Skip to content

Commit 645b511

Browse files
authored
Merge pull request #915 from GeorgeLyon/dev/george/compilation-db-search-paths
2 parents 8dd93d1 + 170b44b commit 645b511

File tree

10 files changed

+159
-23
lines changed

10 files changed

+159
-23
lines changed

Sources/SKCore/CompilationDatabase.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SKSupport
1515

1616
import struct TSCBasic.AbsolutePath
1717
import protocol TSCBasic.FileSystem
18+
import struct TSCBasic.RelativePath
1819
import var TSCBasic.localFileSystem
1920
import func TSCBasic.resolveSymlinks
2021

@@ -67,14 +68,28 @@ public protocol CompilationDatabase {
6768
var allCommands: AnySequence<Command> { get }
6869
}
6970

70-
/// Loads the compilation database located in `directory`, if any.
71+
/// Loads the compilation database located in `directory`, if one can be found in `additionalSearchPaths` or in the default search paths of "." and "build".
7172
public func tryLoadCompilationDatabase(
7273
directory: AbsolutePath,
74+
additionalSearchPaths: [RelativePath] = [],
7375
_ fileSystem: FileSystem = localFileSystem
7476
) -> CompilationDatabase? {
77+
let searchPaths =
78+
additionalSearchPaths + [
79+
// These default search paths match the behavior of `clangd`
80+
try! RelativePath(validating: "."),
81+
try! RelativePath(validating: "build"),
82+
]
7583
return
76-
(try? JSONCompilationDatabase(directory: directory, fileSystem))
77-
?? (try? FixedCompilationDatabase(directory: directory, fileSystem))
84+
try! searchPaths
85+
.lazy
86+
.map { directory.appending($0) }
87+
.compactMap {
88+
try
89+
(JSONCompilationDatabase(directory: $0, fileSystem)
90+
?? FixedCompilationDatabase(directory: $0, fileSystem))
91+
}
92+
.first
7893
}
7994

8095
/// Fixed clang-compatible compilation database (compile_flags.txt).
@@ -99,13 +114,21 @@ public struct FixedCompilationDatabase: CompilationDatabase, Equatable {
99114
}
100115

101116
extension FixedCompilationDatabase {
102-
public init(directory: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
117+
/// Loads the compilation database located in `directory`, if any.
118+
/// - Returns: `nil` if `compile_flags.txt` was not found
119+
public init?(directory: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
103120
let path = directory.appending(component: "compile_flags.txt")
104121
try self.init(file: path, fileSystem)
105122
}
106123

107-
public init(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
124+
/// Loads the compilation database from `file`
125+
/// - Returns: `nil` if the file does not exist
126+
public init?(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
108127
self.directory = file.dirname
128+
129+
guard fileSystem.exists(file) else {
130+
return nil
131+
}
109132
let bytes = try fileSystem.readFileContents(file)
110133

111134
var fixedArgs: [String] = ["clang"]
@@ -185,12 +208,20 @@ extension JSONCompilationDatabase: Codable {
185208
}
186209

187210
extension JSONCompilationDatabase {
188-
public init(directory: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
211+
/// Loads the compilation database located in `directory`, if any.
212+
///
213+
/// - Returns: `nil` if `compile_commands.json` was not found
214+
public init?(directory: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
189215
let path = directory.appending(component: "compile_commands.json")
190216
try self.init(file: path, fileSystem)
191217
}
192218

193-
public init(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
219+
/// Loads the compilation database from `file`
220+
/// - Returns: `nil` if the file does not exist
221+
public init?(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
222+
guard fileSystem.exists(file) else {
223+
return nil
224+
}
194225
let bytes = try fileSystem.readFileContents(file)
195226
try bytes.withUnsafeData { data in
196227
self = try JSONDecoder().decode(JSONCompilationDatabase.self, from: data)

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import SKSupport
1919
import struct Foundation.URL
2020
import struct TSCBasic.AbsolutePath
2121
import protocol TSCBasic.FileSystem
22+
import struct TSCBasic.RelativePath
2223
import var TSCBasic.localFileSystem
2324

2425
/// A `BuildSystem` based on loading clang-compatible compilation database(s).
@@ -44,6 +45,8 @@ public actor CompilationDatabaseBuildSystem {
4445

4546
let projectRoot: AbsolutePath?
4647

48+
let searchPaths: [RelativePath]
49+
4750
let fileSystem: FileSystem
4851

4952
/// The URIs for which the delegate has registered for change notifications,
@@ -70,11 +73,12 @@ public actor CompilationDatabaseBuildSystem {
7073
return nil
7174
}
7275

73-
public init(projectRoot: AbsolutePath? = nil, fileSystem: FileSystem = localFileSystem) {
76+
public init(projectRoot: AbsolutePath? = nil, searchPaths: [RelativePath], fileSystem: FileSystem = localFileSystem) {
7477
self.fileSystem = fileSystem
7578
self.projectRoot = projectRoot
79+
self.searchPaths = searchPaths
7680
if let path = projectRoot {
77-
self.compdb = tryLoadCompilationDatabase(directory: path, fileSystem)
81+
self.compdb = tryLoadCompilationDatabase(directory: path, additionalSearchPaths: searchPaths, fileSystem)
7882
}
7983
}
8084
}
@@ -122,7 +126,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
122126
var dir = path
123127
while !dir.isRoot {
124128
dir = dir.parentDirectory
125-
if let db = tryLoadCompilationDatabase(directory: dir, fileSystem) {
129+
if let db = tryLoadCompilationDatabase(directory: dir, additionalSearchPaths: searchPaths, fileSystem) {
126130
compdb = db
127131
break
128132
}
@@ -150,7 +154,11 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
150154
private func reloadCompilationDatabase() async {
151155
guard let projectRoot = self.projectRoot else { return }
152156

153-
self.compdb = tryLoadCompilationDatabase(directory: projectRoot, self.fileSystem)
157+
self.compdb = tryLoadCompilationDatabase(
158+
directory: projectRoot,
159+
additionalSearchPaths: searchPaths,
160+
self.fileSystem
161+
)
154162

155163
if let delegate = self.delegate {
156164
var changedFiles = Set<DocumentURI>()

Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public final class SKSwiftPMTestWorkspace {
5555
/// Connection to the language server.
5656
public let testClient: TestSourceKitLSPClient
5757

58-
/// When `testServer` is not `nil`, the workspace will be opened in that server, otherwise a new server will be created for the workspace
58+
/// When `testClient` is not `nil`, the workspace will be opened in that client's server, otherwise a new client will be created for the workspace
5959
public init(
6060
projectDir: URL,
6161
tmpDir: URL,

Sources/SKTestSupport/SKTibsTestWorkspace.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import XCTest
2424
import enum PackageLoading.Platform
2525
import struct PackageModel.BuildFlags
2626
import struct TSCBasic.AbsolutePath
27+
import struct TSCBasic.RelativePath
2728

2829
public typealias URL = Foundation.URL
2930

@@ -97,7 +98,10 @@ public final class SKTibsTestWorkspace {
9798

9899
func initWorkspace(clientCapabilities: ClientCapabilities) async throws {
99100
let buildPath = try AbsolutePath(validating: builder.buildRoot.path)
100-
let buildSystem = CompilationDatabaseBuildSystem(projectRoot: buildPath)
101+
let buildSystem = CompilationDatabaseBuildSystem(
102+
projectRoot: buildPath,
103+
searchPaths: try [RelativePath(validating: ".")]
104+
)
101105
let indexDelegate = SourceKitIndexDelegate()
102106
tibsWorkspace.delegate = indexDelegate
103107

Sources/SourceKitLSP/SourceKitServer+Options.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import SKCore
1616
import SKSupport
1717

1818
import struct TSCBasic.AbsolutePath
19+
import struct TSCBasic.RelativePath
1920

2021
extension SourceKitServer {
2122

@@ -29,6 +30,9 @@ extension SourceKitServer {
2930
/// Additional arguments to pass to `clangd` on the command-line.
3031
public var clangdOptions: [String]
3132

33+
/// Additional paths to search for a compilation database, relative to a workspace root.
34+
public var compilationDatabaseSearchPaths: [RelativePath]
35+
3236
/// Additional options for the index.
3337
public var indexOptions: IndexOptions
3438

@@ -48,13 +52,15 @@ extension SourceKitServer {
4852
public init(
4953
buildSetup: BuildSetup = .default,
5054
clangdOptions: [String] = [],
55+
compilationDatabaseSearchPaths: [RelativePath] = [],
5156
indexOptions: IndexOptions = .init(),
5257
completionOptions: SKCompletionOptions = .init(),
5358
generatedInterfacesPath: AbsolutePath = defaultDirectoryForGeneratedInterfaces,
5459
swiftPublishDiagnosticsDebounceDuration: TimeInterval = 2 /* 2s */
5560
) {
5661
self.buildSetup = buildSetup
5762
self.clangdOptions = clangdOptions
63+
self.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths
5864
self.indexOptions = indexOptions
5965
self.completionOptions = completionOptions
6066
self.generatedInterfacesPath = generatedInterfacesPath

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ private var notificationIDForLogging: Int = 0
597597

598598
/// On every call, returns a new unique number that can be used to identify a notification.
599599
///
600-
/// This is needed so we can consistently refer to a notification using the `category` of the logger.
600+
/// This is needed so we can consistently refer to a notification using the `category` of the logger.
601601
/// Requests don't need this since they already have a unique ID in the LSP protocol.
602602
private func getNextNotificationIDForLogging() -> Int {
603603
return notificationIDForLoggingLock.withLock {
@@ -648,7 +648,7 @@ extension SourceKitServer: MessageHandler {
648648
await self.withLanguageServiceAndWorkspace(for: notification, notificationHandler: self.willSaveDocument)
649649
case let notification as DidSaveTextDocumentNotification:
650650
await self.withLanguageServiceAndWorkspace(for: notification, notificationHandler: self.didSaveDocument)
651-
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
651+
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
652652
default:
653653
break
654654
}
@@ -771,7 +771,7 @@ extension SourceKitServer: MessageHandler {
771771
requestHandler: self.documentDiagnostic,
772772
fallback: .full(.init(items: []))
773773
)
774-
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
774+
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
775775
default:
776776
reply(.failure(ResponseError.methodNotFound(R.method)))
777777
}
@@ -860,6 +860,7 @@ extension SourceKitServer {
860860
capabilityRegistry: capabilityRegistry,
861861
toolchainRegistry: self.toolchainRegistry,
862862
buildSetup: self.options.buildSetup,
863+
compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths,
863864
indexOptions: self.options.indexOptions,
864865
reloadPackageStatusCallback: { status in
865866
guard capabilityRegistry.clientCapabilities.window?.workDoneProgress ?? false else {

Sources/SourceKitLSP/Workspace.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import SKSupport
1818
import SKSwiftPMWorkspace
1919

2020
import struct TSCBasic.AbsolutePath
21+
import struct TSCBasic.RelativePath
2122

2223
/// Same as `??` but allows the right-hand side of the operator to 'await'.
2324
fileprivate func firstNonNil<T>(_ optional: T?, _ defaultValue: @autoclosure () async throws -> T) async rethrows -> T {
@@ -102,6 +103,7 @@ public final class Workspace {
102103
capabilityRegistry: CapabilityRegistry,
103104
toolchainRegistry: ToolchainRegistry,
104105
buildSetup: BuildSetup,
106+
compilationDatabaseSearchPaths: [RelativePath],
105107
indexOptions: IndexOptions = IndexOptions(),
106108
reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void
107109
) async throws {
@@ -117,7 +119,7 @@ public final class Workspace {
117119
) {
118120
buildSystem = swiftpm
119121
} else {
120-
buildSystem = CompilationDatabaseBuildSystem(projectRoot: rootPath)
122+
buildSystem = CompilationDatabaseBuildSystem(projectRoot: rootPath, searchPaths: compilationDatabaseSearchPaths)
121123
}
122124
} else {
123125
// We assume that workspaces are directories. This is only true for URLs not for URIs in general.

Sources/sourcekit-lsp/SourceKitLSP.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import SKSupport
2222
import SourceKitLSP
2323

2424
import struct TSCBasic.AbsolutePath
25+
import struct TSCBasic.RelativePath
2526
import var TSCBasic.localFileSystem
2627

2728
extension AbsolutePath: ExpressibleByArgument {
@@ -48,6 +49,18 @@ extension AbsolutePath: ExpressibleByArgument {
4849
}
4950
}
5051

52+
extension RelativePath: ExpressibleByArgument {
53+
public init?(argument: String) {
54+
let path = try? RelativePath(validating: argument)
55+
56+
guard let path = path else {
57+
return nil
58+
}
59+
60+
self = path
61+
}
62+
}
63+
5164
extension PathPrefixMapping: ExpressibleByArgument {
5265
public init?(argument: String) {
5366
guard let eqIndex = argument.firstIndex(of: "=") else { return nil }
@@ -137,6 +150,14 @@ struct SourceKitLSP: ParsableCommand {
137150
)
138151
var indexPrefixMappings = [PathPrefixMapping]()
139152

153+
@Option(
154+
name: .customLong("compilation-db-search-path"),
155+
parsing: .singleValue,
156+
help:
157+
"Specify a relative path where sourcekit-lsp should search for `compile_commands.json` or `compile_flags.txt` relative to the root of a workspace. Multiple search paths may be specified by repeating this option."
158+
)
159+
var compilationDatabaseSearchPaths = [RelativePath]()
160+
140161
@Option(
141162
help: "Specify the directory where generated interfaces will be stored"
142163
)
@@ -157,6 +178,7 @@ struct SourceKitLSP: ParsableCommand {
157178
serverOptions.buildSetup.flags.linkerFlags = buildFlagsLinker
158179
serverOptions.buildSetup.flags.swiftCompilerFlags = buildFlagsSwift
159180
serverOptions.clangdOptions = clangdOptions
181+
serverOptions.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths
160182
serverOptions.indexOptions.indexStorePath = indexStorePath
161183
serverOptions.indexOptions.indexDatabasePath = indexDatabasePath
162184
serverOptions.indexOptions.indexPrefixMappings = indexPrefixMappings

0 commit comments

Comments
 (0)