//===----------------------------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// import SKSupport import Foundation import struct TSCBasic.AbsolutePath /// 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. public final class SourceKitDImpl: SourceKitD { /// The path to the sourcekitd dylib. public let path: AbsolutePath /// The handle to the dylib. let dylib: DLHandle /// The sourcekitd API functions. public let api: sourcekitd_functions_t /// Convenience for accessing known keys. public let keys: sourcekitd_keys /// Convenience for accessing known keys. public let requests: sourcekitd_requests /// Convenience for accessing known keys. public let values: sourcekitd_values /// Lock protecting private state. let lock: NSLock = NSLock() /// List of notification handlers that will be called for each notification. private var _notificationHandlers: [WeakSKDNotificationHandler] = [] var notificationHandlers: [SKDNotificationHandler] { lock.withLock { _notificationHandlers.compactMap { $0.value } } } public static func getOrCreate(dylibPath: AbsolutePath) throws -> SourceKitD { try SourceKitDRegistry.shared .getOrAdd(dylibPath, create: { try SourceKitDImpl(dylib: dylibPath) }) } init(dylib path: AbsolutePath) throws { self.path = path #if os(Windows) self.dylib = try dlopen(path.pathString, mode: []) #else self.dylib = try dlopen(path.pathString, mode: [.lazy, .local, .first]) #endif self.api = try sourcekitd_functions_t(self.dylib) self.keys = sourcekitd_keys(api: self.api) self.requests = sourcekitd_requests(api: self.api) self.values = sourcekitd_values(api: self.api) self.api.initialize() self.api.set_notification_handler { [weak self] rawResponse in guard let self = self else { return } let handlers = self.lock.withLock { self._notificationHandlers.compactMap(\.value) } let response = SKDResponse(rawResponse, sourcekitd: self) for handler in handlers { handler.notification(response) } } } deinit { self.api.set_notification_handler(nil) self.api.shutdown() // FIXME: is it safe to dlclose() sourcekitd? If so, do that here. For now, let the handle leak. dylib.leak() } /// Adds a new notification handler (referenced weakly). public func addNotificationHandler(_ handler: SKDNotificationHandler) { lock.withLock { _notificationHandlers.removeAll(where: { $0.value == nil }) _notificationHandlers.append(.init(handler)) } } /// Removes a previously registered notification handler. public func removeNotificationHandler(_ handler: SKDNotificationHandler) { lock.withLock { _notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler}) } } } struct WeakSKDNotificationHandler { weak private(set) var value: SKDNotificationHandler? init(_ value: SKDNotificationHandler) { self.value = value } }