Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for a compile database file not at the root of a workspace #915

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 {
ahoppen marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really related, but I was confused by the wording here when browsing the codebase.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I recently changed testServer to testClient and looks like I forgot that comment. Thanks for updating it.

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