Files
sourcekit-lsp/Sources/BuildServerIntegration/FixedCompilationDatabaseBuildServer.swift
Alex Hoppen 95538e7de9 Migrate appendingPathComponent to appending(component:)
`appending(component:)` is the more modern API and can take multiple path components at the same time.
2025-09-23 16:57:56 +02:00

140 lines
5.3 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
package import BuildServerProtocol
package import Foundation
package import LanguageServerProtocol
import SKLogging
import SwiftExtensions
func lastIndexStorePathArgument(in compilerArgs: [String]) -> String? {
if let indexStorePathIndex = compilerArgs.lastIndex(of: "-index-store-path"),
indexStorePathIndex + 1 < compilerArgs.count
{
return compilerArgs[indexStorePathIndex + 1]
}
return nil
}
/// A `BuiltInBuildServer` that provides compiler arguments from a `compile_flags.txt` file.
package actor FixedCompilationDatabaseBuildServer: BuiltInBuildServer {
package static let dbName = "compile_flags.txt"
private let connectionToSourceKitLSP: any Connection
package let configPath: URL
/// The compiler arguments from the fixed compilation database.
///
/// Note that this does not contain the path to a compiler.
var compilerArgs: [String]
// Watch for all all changes to `compile_flags.txt` and `compile_flags.txt` instead of just the one at
// `configPath` so that we cover the following semi-common scenario:
// The user has a build that stores `compile_flags.txt` in `mybuild`. In order to pick it up, they create a
// symlink from `<project root>/compile_flags.txt` to `mybuild/compile_flags.txt`. We want to get notified
// about the change to `mybuild/compile_flags.txt` because it effectively changes the contents of
// `<project root>/compile_flags.txt`.
package let fileWatchers: [FileSystemWatcher] = [
FileSystemWatcher(globPattern: "**/compile_flags.txt", kind: [.create, .change, .delete])
]
package var indexStorePath: URL? {
guard let indexStorePath = lastIndexStorePathArgument(in: compilerArgs) else {
return nil
}
return URL(fileURLWithPath: indexStorePath, relativeTo: configPath.deletingLastPathComponent())
}
package var indexDatabasePath: URL? {
indexStorePath?.deletingLastPathComponent().appending(component: "IndexDatabase")
}
package nonisolated var supportsPreparationAndOutputPaths: Bool { false }
private static func parseCompileFlags(at configPath: URL) throws -> [String] {
let fileContents: String = try String(contentsOf: configPath, encoding: .utf8)
var compilerArgs: [String] = []
fileContents.enumerateLines { line, _ in
compilerArgs.append(line.trimmingCharacters(in: .whitespacesAndNewlines))
}
return compilerArgs
}
package init(
configPath: URL,
connectionToSourceKitLSP: any Connection
) throws {
self.connectionToSourceKitLSP = connectionToSourceKitLSP
self.configPath = configPath
self.compilerArgs = try Self.parseCompileFlags(at: configPath)
}
package func buildTargets(request: WorkspaceBuildTargetsRequest) async throws -> WorkspaceBuildTargetsResponse {
return WorkspaceBuildTargetsResponse(targets: [
BuildTarget(
id: .dummy,
tags: [.test],
capabilities: BuildTargetCapabilities(),
// Be conservative with the languages that might be used in the target. SourceKit-LSP doesn't use this property.
languageIds: [.c, .cpp, .objective_c, .objective_cpp, .swift],
dependencies: []
)
])
}
package func buildTargetSources(request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
guard request.targets.contains(.dummy) else {
return BuildTargetSourcesResponse(items: [])
}
return BuildTargetSourcesResponse(items: [
SourcesItem(
target: .dummy,
sources: [SourceItem(uri: URI(configPath.deletingLastPathComponent()), kind: .directory, generated: false)]
)
])
}
package func didChangeWatchedFiles(notification: OnWatchedFilesDidChangeNotification) {
if notification.changes.contains(where: { $0.uri.fileURL?.lastPathComponent == Self.dbName }) {
self.reloadCompilationDatabase()
}
}
package func prepare(request: BuildTargetPrepareRequest) async throws -> VoidResponse {
throw ResponseError.methodNotFound(BuildTargetPrepareRequest.method)
}
package func sourceKitOptions(
request: TextDocumentSourceKitOptionsRequest
) async throws -> TextDocumentSourceKitOptionsResponse? {
return TextDocumentSourceKitOptionsResponse(
compilerArguments: compilerArgs + [request.textDocument.uri.pseudoPath],
workingDirectory: try? configPath.deletingLastPathComponent().filePath
)
}
package func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse {
return VoidResponse()
}
/// The compilation database has been changed on disk.
/// Reload it and notify the delegate about build setting changes.
private func reloadCompilationDatabase() {
orLog("Reloading fixed compilation database") {
self.compilerArgs = try Self.parseCompileFlags(at: configPath)
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
}
}
}