mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
We made quite a few fixes recently to make sure that path handling works correctly using `URL` on Windows. Use `URL` in most places to have a single type that represents file paths instead of sometimes using `AbsolutePath`. While doing so, also remove usages of `TSCBasic.FileSystem` an `InMemoryFileSystem`. The pattern of using `InMemoryFileSystem` for tests was never consistently used and it was a little confusing that some types took a `FileSystem` parameter while other always assumed to work on the local file system.
164 lines
5.0 KiB
Swift
164 lines
5.0 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if compiler(>=6)
|
|
package import Csourcekitd
|
|
package import Foundation
|
|
import SKLogging
|
|
import SwiftExtensions
|
|
#else
|
|
import Csourcekitd
|
|
import Foundation
|
|
import SKLogging
|
|
import SwiftExtensions
|
|
#endif
|
|
|
|
extension sourcekitd_api_keys: @unchecked Sendable {}
|
|
extension sourcekitd_api_requests: @unchecked Sendable {}
|
|
extension sourcekitd_api_values: @unchecked Sendable {}
|
|
|
|
/// Wrapper for sourcekitd, taking care of initialization, shutdown, and notification handler
|
|
/// multiplexing.
|
|
///
|
|
/// Users of this class should not call the api functions `initialize`, `shutdown`, or
|
|
/// `set_notification_handler`, which are global state managed internally by this class.
|
|
package actor DynamicallyLoadedSourceKitD: SourceKitD {
|
|
/// The path to the sourcekitd dylib.
|
|
package let path: URL
|
|
|
|
/// The handle to the dylib.
|
|
let dylib: DLHandle
|
|
|
|
/// The sourcekitd API functions.
|
|
package let api: sourcekitd_api_functions_t
|
|
|
|
/// Convenience for accessing known keys.
|
|
package let keys: sourcekitd_api_keys
|
|
|
|
/// Convenience for accessing known keys.
|
|
package let requests: sourcekitd_api_requests
|
|
|
|
/// Convenience for accessing known keys.
|
|
package let values: sourcekitd_api_values
|
|
|
|
private nonisolated let notificationHandlingQueue = AsyncQueue<Serial>()
|
|
|
|
/// List of notification handlers that will be called for each notification.
|
|
private var notificationHandlers: [WeakSKDNotificationHandler] = []
|
|
|
|
package static func getOrCreate(dylibPath: URL) async throws -> SourceKitD {
|
|
try await SourceKitDRegistry.shared
|
|
.getOrAdd(dylibPath, create: { try DynamicallyLoadedSourceKitD(dylib: dylibPath) })
|
|
}
|
|
|
|
init(dylib path: URL) throws {
|
|
self.path = path
|
|
#if os(Windows)
|
|
self.dylib = try dlopen(path.filePath, mode: [])
|
|
#else
|
|
self.dylib = try dlopen(path.filePath, mode: [.lazy, .local, .first])
|
|
#endif
|
|
self.api = try sourcekitd_api_functions_t(self.dylib)
|
|
self.keys = sourcekitd_api_keys(api: self.api)
|
|
self.requests = sourcekitd_api_requests(api: self.api)
|
|
self.values = sourcekitd_api_values(api: self.api)
|
|
|
|
self.api.initialize()
|
|
self.api.set_notification_handler { [weak self] rawResponse in
|
|
guard let self, let rawResponse else { return }
|
|
let response = SKDResponse(rawResponse, sourcekitd: self)
|
|
self.notificationHandlingQueue.async {
|
|
let handlers = await self.notificationHandlers.compactMap(\.value)
|
|
|
|
for handler in handlers {
|
|
handler.notification(response)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.api.set_notification_handler(nil)
|
|
self.api.shutdown()
|
|
Task.detached(priority: .background) { [dylib, path] in
|
|
orLog("Closing dylib \(path)") { try dylib.close() }
|
|
}
|
|
}
|
|
|
|
/// Adds a new notification handler (referenced weakly).
|
|
package func addNotificationHandler(_ handler: SKDNotificationHandler) {
|
|
notificationHandlers.removeAll(where: { $0.value == nil })
|
|
notificationHandlers.append(.init(handler))
|
|
}
|
|
|
|
/// Removes a previously registered notification handler.
|
|
package func removeNotificationHandler(_ handler: SKDNotificationHandler) {
|
|
notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler })
|
|
}
|
|
|
|
package nonisolated func log(request: SKDRequestDictionary) {
|
|
logger.info(
|
|
"""
|
|
Sending sourcekitd request:
|
|
\(request.forLogging)
|
|
"""
|
|
)
|
|
}
|
|
|
|
package nonisolated func log(response: SKDResponse) {
|
|
logger.log(
|
|
level: (response.error == nil || response.error == .requestCancelled) ? .debug : .error,
|
|
"""
|
|
Received sourcekitd response:
|
|
\(response.forLogging)
|
|
"""
|
|
)
|
|
}
|
|
|
|
package nonisolated func log(crashedRequest req: SKDRequestDictionary, fileContents: String?) {
|
|
let log = """
|
|
Request:
|
|
\(req.description)
|
|
|
|
File contents:
|
|
\(fileContents ?? "<nil>")
|
|
"""
|
|
let chunks = splitLongMultilineMessage(message: log)
|
|
for (index, chunk) in chunks.enumerated() {
|
|
logger.fault(
|
|
"""
|
|
sourcekitd crashed (\(index + 1)/\(chunks.count))
|
|
\(chunk)
|
|
"""
|
|
)
|
|
}
|
|
}
|
|
|
|
package nonisolated func logRequestCancellation(request: SKDRequestDictionary) {
|
|
// We don't need to log which request has been cancelled because we can associate the cancellation log message with
|
|
// the send message via the log
|
|
logger.info(
|
|
"""
|
|
Cancelling sourcekitd request:
|
|
\(request.forLogging)
|
|
"""
|
|
)
|
|
}
|
|
}
|
|
|
|
struct WeakSKDNotificationHandler: Sendable {
|
|
weak private(set) var value: SKDNotificationHandler?
|
|
init(_ value: SKDNotificationHandler) {
|
|
self.value = value
|
|
}
|
|
}
|