Skip to content

Commit

Permalink
Add support for compile databases not at the root of the workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
GeorgeLyon committed Oct 21, 2023
1 parent e141933 commit 170b44b
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 23 deletions.
45 changes: 38 additions & 7 deletions Sources/SKCore/CompilationDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SKSupport

import struct TSCBasic.AbsolutePath
import protocol TSCBasic.FileSystem
import struct TSCBasic.RelativePath
import var TSCBasic.localFileSystem
import func TSCBasic.resolveSymlinks

Expand Down Expand Up @@ -67,14 +68,28 @@ public protocol CompilationDatabase {
var allCommands: AnySequence<Command> { get }
}

/// Loads the compilation database located in `directory`, if any.
/// Loads the compilation database located in `directory`, if one can be found in `additionalSearchPaths` or in the default search paths of "." and "build".
public func tryLoadCompilationDatabase(
directory: AbsolutePath,
additionalSearchPaths: [RelativePath] = [],
_ fileSystem: FileSystem = localFileSystem
) -> CompilationDatabase? {
let searchPaths =
additionalSearchPaths + [
// These default search paths match the behavior of `clangd`
try! RelativePath(validating: "."),
try! RelativePath(validating: "build"),
]
return
(try? JSONCompilationDatabase(directory: directory, fileSystem))
?? (try? FixedCompilationDatabase(directory: directory, fileSystem))
try! searchPaths
.lazy
.map { directory.appending($0) }
.compactMap {
try
(JSONCompilationDatabase(directory: $0, fileSystem)
?? FixedCompilationDatabase(directory: $0, fileSystem))
}
.first
}

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

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

public init(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
/// Loads the compilation database from `file`
/// - Returns: `nil` if the file does not exist
public init?(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
self.directory = file.dirname

guard fileSystem.exists(file) else {
return nil
}
let bytes = try fileSystem.readFileContents(file)

var fixedArgs: [String] = ["clang"]
Expand Down Expand Up @@ -185,12 +208,20 @@ extension JSONCompilationDatabase: Codable {
}

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

public init(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
/// Loads the compilation database from `file`
/// - Returns: `nil` if the file does not exist
public init?(file: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) throws {
guard fileSystem.exists(file) else {
return nil
}
let bytes = try fileSystem.readFileContents(file)
try bytes.withUnsafeData { data in
self = try JSONDecoder().decode(JSONCompilationDatabase.self, from: data)
Expand Down
16 changes: 12 additions & 4 deletions Sources/SKCore/CompilationDatabaseBuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import SKSupport
import struct Foundation.URL
import struct TSCBasic.AbsolutePath
import protocol TSCBasic.FileSystem
import struct TSCBasic.RelativePath
import var TSCBasic.localFileSystem

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

let projectRoot: AbsolutePath?

let searchPaths: [RelativePath]

let fileSystem: FileSystem

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

public init(projectRoot: AbsolutePath? = nil, fileSystem: FileSystem = localFileSystem) {
public init(projectRoot: AbsolutePath? = nil, searchPaths: [RelativePath], fileSystem: FileSystem = localFileSystem) {
self.fileSystem = fileSystem
self.projectRoot = projectRoot
self.searchPaths = searchPaths
if let path = projectRoot {
self.compdb = tryLoadCompilationDatabase(directory: path, fileSystem)
self.compdb = tryLoadCompilationDatabase(directory: path, additionalSearchPaths: searchPaths, fileSystem)
}
}
}
Expand Down Expand Up @@ -122,7 +126,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
var dir = path
while !dir.isRoot {
dir = dir.parentDirectory
if let db = tryLoadCompilationDatabase(directory: dir, fileSystem) {
if let db = tryLoadCompilationDatabase(directory: dir, additionalSearchPaths: searchPaths, fileSystem) {
compdb = db
break
}
Expand Down Expand Up @@ -150,7 +154,11 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
private func reloadCompilationDatabase() async {
guard let projectRoot = self.projectRoot else { return }

self.compdb = tryLoadCompilationDatabase(directory: projectRoot, self.fileSystem)
self.compdb = tryLoadCompilationDatabase(
directory: projectRoot,
additionalSearchPaths: searchPaths,
self.fileSystem
)

if let delegate = self.delegate {
var changedFiles = Set<DocumentURI>()
Expand Down
2 changes: 1 addition & 1 deletion Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public final class SKSwiftPMTestWorkspace {
/// Connection to the language server.
public let testClient: TestSourceKitLSPClient

/// When `testServer` is not `nil`, the workspace will be opened in that server, otherwise a new server will be created for the workspace
/// 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
public init(
projectDir: URL,
tmpDir: URL,
Expand Down
6 changes: 5 additions & 1 deletion Sources/SKTestSupport/SKTibsTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import XCTest
import enum PackageLoading.Platform
import struct PackageModel.BuildFlags
import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath

public typealias URL = Foundation.URL

Expand Down Expand Up @@ -83,7 +84,10 @@ public final class SKTibsTestWorkspace {

func initWorkspace(clientCapabilities: ClientCapabilities) async throws {
let buildPath = try AbsolutePath(validating: builder.buildRoot.path)
let buildSystem = CompilationDatabaseBuildSystem(projectRoot: buildPath)
let buildSystem = CompilationDatabaseBuildSystem(
projectRoot: buildPath,
searchPaths: try [RelativePath(validating: ".")]
)
let indexDelegate = SourceKitIndexDelegate()
tibsWorkspace.delegate = indexDelegate

Expand Down
6 changes: 6 additions & 0 deletions Sources/SourceKitLSP/SourceKitServer+Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SKCore
import SKSupport

import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath

extension SourceKitServer {

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

/// Additional paths to search for a compilation database, relative to a workspace root.
public var compilationDatabaseSearchPaths: [RelativePath]

/// Additional options for the index.
public var indexOptions: IndexOptions

Expand All @@ -48,13 +52,15 @@ extension SourceKitServer {
public init(
buildSetup: BuildSetup = .default,
clangdOptions: [String] = [],
compilationDatabaseSearchPaths: [RelativePath] = [],
indexOptions: IndexOptions = .init(),
completionOptions: SKCompletionOptions = .init(),
generatedInterfacesPath: AbsolutePath = defaultDirectoryForGeneratedInterfaces,
swiftPublishDiagnosticsDebounceDuration: TimeInterval = 2 /* 2s */
) {
self.buildSetup = buildSetup
self.clangdOptions = clangdOptions
self.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths
self.indexOptions = indexOptions
self.completionOptions = completionOptions
self.generatedInterfacesPath = generatedInterfacesPath
Expand Down
7 changes: 4 additions & 3 deletions Sources/SourceKitLSP/SourceKitServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ private var notificationIDForLogging: Int = 0

/// On every call, returns a new unique number that can be used to identify a notification.
///
/// This is needed so we can consistently refer to a notification using the `category` of the logger.
/// This is needed so we can consistently refer to a notification using the `category` of the logger.
/// Requests don't need this since they already have a unique ID in the LSP protocol.
private func getNextNotificationIDForLogging() -> Int {
return notificationIDForLoggingLock.withLock {
Expand Down Expand Up @@ -648,7 +648,7 @@ extension SourceKitServer: MessageHandler {
await self.withLanguageServiceAndWorkspace(for: notification, notificationHandler: self.willSaveDocument)
case let notification as DidSaveTextDocumentNotification:
await self.withLanguageServiceAndWorkspace(for: notification, notificationHandler: self.didSaveDocument)
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
default:
break
}
Expand Down Expand Up @@ -771,7 +771,7 @@ extension SourceKitServer: MessageHandler {
requestHandler: self.documentDiagnostic,
fallback: .full(.init(items: []))
)
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
// IMPORTANT: When adding a new entry to this switch, also add it to the `TaskMetadata` initializer.
default:
reply(.failure(ResponseError.methodNotFound(R.method)))
}
Expand Down Expand Up @@ -860,6 +860,7 @@ extension SourceKitServer {
capabilityRegistry: capabilityRegistry,
toolchainRegistry: self.toolchainRegistry,
buildSetup: self.options.buildSetup,
compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths,
indexOptions: self.options.indexOptions,
reloadPackageStatusCallback: { status in
guard capabilityRegistry.clientCapabilities.window?.workDoneProgress ?? false else {
Expand Down
4 changes: 3 additions & 1 deletion Sources/SourceKitLSP/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import SKSupport
import SKSwiftPMWorkspace

import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath

/// Same as `??` but allows the right-hand side of the operator to 'await'.
fileprivate func firstNonNil<T>(_ optional: T?, _ defaultValue: @autoclosure () async throws -> T) async rethrows -> T {
Expand Down Expand Up @@ -102,6 +103,7 @@ public final class Workspace {
capabilityRegistry: CapabilityRegistry,
toolchainRegistry: ToolchainRegistry,
buildSetup: BuildSetup,
compilationDatabaseSearchPaths: [RelativePath],
indexOptions: IndexOptions = IndexOptions(),
reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void
) async throws {
Expand All @@ -117,7 +119,7 @@ public final class Workspace {
) {
buildSystem = swiftpm
} else {
buildSystem = CompilationDatabaseBuildSystem(projectRoot: rootPath)
buildSystem = CompilationDatabaseBuildSystem(projectRoot: rootPath, searchPaths: compilationDatabaseSearchPaths)
}
} else {
// We assume that workspaces are directories. This is only true for URLs not for URIs in general.
Expand Down
22 changes: 22 additions & 0 deletions Sources/sourcekit-lsp/SourceKitLSP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import SKSupport
import SourceKitLSP

import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath
import var TSCBasic.localFileSystem

extension AbsolutePath: ExpressibleByArgument {
Expand All @@ -48,6 +49,18 @@ extension AbsolutePath: ExpressibleByArgument {
}
}

extension RelativePath: ExpressibleByArgument {
public init?(argument: String) {
let path = try? RelativePath(validating: argument)

guard let path = path else {
return nil
}

self = path
}
}

extension PathPrefixMapping: ExpressibleByArgument {
public init?(argument: String) {
guard let eqIndex = argument.firstIndex(of: "=") else { return nil }
Expand Down Expand Up @@ -137,6 +150,14 @@ struct SourceKitLSP: ParsableCommand {
)
var indexPrefixMappings = [PathPrefixMapping]()

@Option(
name: .customLong("compilation-db-search-path"),
parsing: .singleValue,
help:
"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."
)
var compilationDatabaseSearchPaths = [RelativePath]()

@Option(
help: "Specify the directory where generated interfaces will be stored"
)
Expand All @@ -157,6 +178,7 @@ struct SourceKitLSP: ParsableCommand {
serverOptions.buildSetup.flags.linkerFlags = buildFlagsLinker
serverOptions.buildSetup.flags.swiftCompilerFlags = buildFlagsSwift
serverOptions.clangdOptions = clangdOptions
serverOptions.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths
serverOptions.indexOptions.indexStorePath = indexStorePath
serverOptions.indexOptions.indexDatabasePath = indexDatabasePath
serverOptions.indexOptions.indexPrefixMappings = indexPrefixMappings
Expand Down

0 comments on commit 170b44b

Please sign in to comment.