1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
|
//===--- IndexStoreDB.swift -----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//
@_implementationOnly
import CIndexStoreDB
import Foundation
// For `strdup`
#if canImport(Glibc)
import Glibc
#elseif os(Windows)
import ucrt
#elseif canImport(Bionic)
import Bionic
#else
import Darwin.POSIX
#endif
public struct PathMapping {
/// Path prefix to be replaced, typically the canonical or hermetic path.
let original: String
/// Replacement path prefix, typically the path on the local machine.
let replacement: String
public init(original: String, replacement: String) {
self.original = original
self.replacement = replacement
}
}
public enum SymbolProviderKind: Sendable {
case clang
case swift
init(_ cKind: indexstoredb_symbol_provider_kind_t) {
switch cKind {
case INDEXSTOREDB_SYMBOL_PROVIDER_KIND_SWIFT:
self = .swift
case INDEXSTOREDB_SYMBOL_PROVIDER_KIND_CLANG:
self = .clang
default:
preconditionFailure("Unknown enum case in indexstoredb_symbol_provider_kind_t")
}
}
}
/// IndexStoreDB index.
public final class IndexStoreDB {
let delegate: IndexDelegate?
let impl: UnsafeMutableRawPointer // indexstoredb_index_t
/// Create or open an IndexStoreDB at the givin `databasePath`.
///
/// * Parameters:
/// * storePath: Path to the index store.
/// * databasePath: Path to the index database (or where it will be created).
/// * library: The index store library to use.
/// * delegate: The delegate to receive index events.
/// * wait: If `true`, wait for the database to be populated from the
/// (current) contents of the index store at `storePath` before returning.
/// * readonly: If `true`, read an existing database, but do not create or modify.
/// * enableOutOfDateFileWatching: If `true`, enables the mechanism for detecting out-of-date units and sending notications via a delegate event.
/// Note that this mechanism uses additional CPU & memory resources.
/// * listenToUnitEvents: Only `true` is supported outside unit tests. Setting to `false`
/// disables reading or updating from the index store unless `pollForUnitChangesAndWait()`
/// is called.
/// * prefixMappings: Path mappings to use (if supported) to remap paths in the index data to paths on the local machine.
public init(
storePath: String,
databasePath: String,
library: IndexStoreLibrary?,
delegate: IndexDelegate? = nil,
useExplicitOutputUnits: Bool = false,
waitUntilDoneInitializing wait: Bool = false,
readonly: Bool = false,
enableOutOfDateFileWatching: Bool = false,
listenToUnitEvents: Bool = true,
prefixMappings: [PathMapping] = []
) throws {
self.delegate = delegate
let libProviderFunc: indexstore_library_provider_t = { (cpath: UnsafePointer<Int8>) -> indexstoredb_indexstore_library_t? in
return library?.library
}
let delegateFunc = { [weak delegate] (event: indexstoredb_delegate_event_t) -> () in
delegate?.handleEvent(event)
}
let options = indexstoredb_creation_options_create()
defer { indexstoredb_creation_options_dispose(options) }
indexstoredb_creation_options_use_explicit_output_units(options, useExplicitOutputUnits)
indexstoredb_creation_options_wait(options, wait)
indexstoredb_creation_options_readonly(options, readonly)
indexstoredb_creation_options_enable_out_of_date_file_watching(options, enableOutOfDateFileWatching)
indexstoredb_creation_options_listen_to_unit_events(options, listenToUnitEvents)
for mapping in prefixMappings {
mapping.original.withCString { origCStr in
mapping.replacement.withCString { remappedCStr in
indexstoredb_creation_options_add_prefix_mapping(options, origCStr, remappedCStr)
}
}
}
var error: indexstoredb_error_t? = nil
guard let index = indexstoredb_index_create(
storePath, databasePath,
libProviderFunc, delegateFunc,
options, &error
) else {
defer { indexstoredb_error_dispose(error) }
throw IndexStoreDBError.create(error?.description ?? "unknown")
}
impl = index
}
/// Wraps an existing `indexstoredb_index_t`.
///
/// * Parameters:
/// * cIndex: An existing `indexstoredb_index_t` object.
/// * delegate: The delegate to receive index events.
public init(
cIndex: UnsafeMutableRawPointer/*indexstoredb_index_t*/,
delegate: IndexDelegate? = nil)
{
self.delegate = delegate
self.impl = cIndex
indexstoredb_index_add_delegate(cIndex) { [weak delegate] event in
delegate?.handleEvent(event)
}
}
deinit {
indexstoredb_release(impl)
}
/// *For Testing* Poll for any changes to units and wait until they have been registered.
public func pollForUnitChangesAndWait(isInitialScan: Bool = false) {
indexstoredb_index_poll_for_unit_changes_and_wait(impl, isInitialScan)
}
/// Add output filepaths for the set of unit files that index data should be loaded from.
/// Only has an effect if `useExplicitOutputUnits` was set to true at initialization.
public func addUnitOutFilePaths(_ paths: [String], waitForProcessing: Bool) {
let cPaths: [UnsafePointer<CChar>] = paths.map { UnsafePointer($0.withCString(strdup)!) }
defer { for cPath in cPaths { free(UnsafeMutablePointer(mutating: cPath)) } }
return indexstoredb_index_add_unit_out_file_paths(impl, cPaths, cPaths.count, waitForProcessing)
}
/// Remove output filepaths for the set of unit files that index data should be loaded from.
/// Only has an effect if `useExplicitOutputUnits` was set to true at initialization.
public func removeUnitOutFilePaths(_ paths: [String], waitForProcessing: Bool) {
let cPaths: [UnsafePointer<CChar>] = paths.map { UnsafePointer($0.withCString(strdup)!) }
defer { for cPath in cPaths { free(UnsafeMutablePointer(mutating: cPath)) } }
return indexstoredb_index_remove_unit_out_file_paths(impl, cPaths, cPaths.count, waitForProcessing)
}
/// Invoke `body` with every occurrance of `usr` in one of the specified roles.
///
/// Stop iteration if `body` returns `false`.
/// - Returns: `false` if iteration was terminated by `body` returning `true` or `true` if iteration finished.
@discardableResult
public func forEachSymbolOccurrence(byUSR usr: String, roles: SymbolRole, _ body: (SymbolOccurrence) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_symbol_occurrences_by_usr(impl, usr, roles.rawValue) { occur in
return body(SymbolOccurrence(occur))
}
}
}
/// Returns all occurrences of `usr` in one of the specified roles.
public func occurrences(ofUSR usr: String, roles: SymbolRole) -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
forEachSymbolOccurrence(byUSR: usr, roles: roles) { occur in
result.append(occur)
return true
}
return result
}
@discardableResult
public func forEachRelatedSymbolOccurrence(byUSR usr: String, roles: SymbolRole, _ body: (SymbolOccurrence) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_related_symbol_occurrences_by_usr(impl, usr, roles.rawValue) {
occur in
return body(SymbolOccurrence(occur))
}
}
}
public func occurrences(relatedToUSR usr: String, roles: SymbolRole) -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
forEachRelatedSymbolOccurrence(byUSR: usr, roles: roles) { occur in
result.append(occur)
return true
}
return result
}
@discardableResult public func forEachCanonicalSymbolOccurrence(byName: String, body: (SymbolOccurrence) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_canonical_symbol_occurences_by_name(impl, byName) { occur in
return body(SymbolOccurrence(occur))
}
}
}
public func canonicalOccurrences(ofName name: String) -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
forEachCanonicalSymbolOccurrence(byName: name) { occur in
result.append(occur)
return true
}
return result
}
@discardableResult public func forEachCanonicalSymbolOccurrence(
containing pattern: String,
anchorStart: Bool,
anchorEnd: Bool,
subsequence: Bool,
ignoreCase: Bool,
body: (SymbolOccurrence) -> Bool
) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_canonical_symbol_occurences_containing_pattern(
impl,
pattern,
anchorStart,
anchorEnd,
subsequence,
ignoreCase
) { occur in
body(SymbolOccurrence(occur))
}
}
}
public func canonicalOccurrences(
containing pattern: String,
anchorStart: Bool,
anchorEnd: Bool,
subsequence: Bool,
ignoreCase: Bool
) -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
forEachCanonicalSymbolOccurrence(
containing: pattern,
anchorStart: anchorStart,
anchorEnd: anchorEnd,
subsequence: subsequence,
ignoreCase: ignoreCase)
{ occur in
result.append(occur)
return true
}
return result
}
@discardableResult
public func forEachMainFileContainingFile(
path: String, crossLanguage: Bool, body: (String) -> Bool
) -> Bool {
let fromSwift = path.hasSuffix(".swift")
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_units_containing_file(impl, path) { unit in
let mainFileStr = String(cString: indexstoredb_unit_info_main_file_path(unit))
let toSwift = mainFileStr.hasSuffix(".swift")
if !crossLanguage && fromSwift != toSwift {
return true // continue
}
return body(mainFileStr)
}
}
}
public func mainFilesContainingFile(path: String, crossLanguage: Bool = false) -> [String] {
var result: [String] = []
forEachMainFileContainingFile(path: path, crossLanguage: crossLanguage) { mainFile in
result.append(mainFile)
return true
}
return result
}
@discardableResult
public func forEachUnitNameContainingFile(path: String, body: (String) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_units_containing_file(impl, path) { unit in
let unitName = String(cString: indexstoredb_unit_info_unit_name(unit))
return body(unitName)
}
}
}
public func unitNamesContainingFile(path: String) -> [String] {
var result: [String] = []
forEachUnitNameContainingFile(path: path) { unitName in
result.append(unitName)
return true
}
return result
}
@discardableResult
public func foreachFileIncludedByFile(path: String, body: (String) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_files_included_by_file(impl, path) { targetPath, line in
let targetPathStr = String(cString: targetPath)
return body(targetPathStr)
}
}
}
public func filesIncludedByFile(path: String) -> [String] {
var result: [String] = []
foreachFileIncludedByFile(path: path) { targetPath in
result.append(targetPath)
return true
}
return result
}
@discardableResult
public func foreachFileIncludingFile(path: String, body: (String) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_files_including_file(impl, path) { sourcePath, line in
let sourcePathStr = String(cString: sourcePath)
return body(sourcePathStr)
}
}
}
public func filesIncludingFile(path: String) -> [String] {
var result: [String] = []
foreachFileIncludingFile(path: path) { targetPath in
result.append(targetPath)
return true
}
return result
}
/// A recorded header `#include` from a unit file.
public struct UnitIncludeEntry: Equatable {
/// The path where the `#include` was added.
public let sourcePath: String
/// The path that the `#include` resolved to.
public let targetPath: String
/// the line where the `#include` was added.
public let line: Int
public init(sourcePath: String, targetPath: String, line: Int) {
self.sourcePath = sourcePath
self.targetPath = targetPath
self.line = line
}
}
/// Iterates over recorded `#include`s of a unit.
@discardableResult
public func forEachIncludeOfUnit(unitName: String, body: (UnitIncludeEntry) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_includes_of_unit(impl, unitName) { sourcePath, targetPath, line in
let sourcePathStr = String(cString: sourcePath)
let targetPathStr = String(cString: targetPath)
return body(
UnitIncludeEntry(sourcePath: sourcePathStr, targetPath: targetPathStr, line: line))
}
}
}
/// Returns the recorded `#include`s of a unit.
public func includesOfUnit(unitName: String) -> [UnitIncludeEntry] {
var result: [UnitIncludeEntry] = []
forEachIncludeOfUnit(unitName: unitName) { entry in
result.append(entry)
return true
}
return result
}
/// Iterates over the name of every symbol in the index.
///
/// - Parameter body: A closure to be called for each symbol. The closure should return true to
/// continue iterating.
@discardableResult
public func forEachSymbolName(body: (String) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_symbol_names(impl) { name in
body(String(cString: name))
}
}
}
/// Returns an array with every symbol name in the index.
public func allSymbolNames() -> [String] {
var result: [String] = []
forEachSymbolName { name in
result.append(name)
return true
}
return result
}
public func symbols(inFilePath path: String) -> [Symbol] {
var result: [Symbol] = []
forEachSymbol(inFilePath: path) { sym in
result.append(sym)
return true
}
return result
}
public func symbolOccurrences(inFilePath path: String) -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
forEachSymbolOccurrence(inFilePath: path) { occur in
result.append(occur)
return true
}
return result
}
@discardableResult
func forEachSymbol(inFilePath filePath: String, body: (Symbol) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_symbols_contained_in_file_path(impl, filePath) { symbol in
return body(Symbol(symbol))
}
}
}
@discardableResult
func forEachSymbolOccurrence(inFilePath filePath: String, body: (SymbolOccurrence) -> Bool) -> Bool {
return withoutActuallyEscaping(body) { body in
return indexstoredb_index_symbol_occurrences_in_file_path(impl, filePath) { occur in
return body(SymbolOccurrence(occur))
}
}
}
/// Returns all unit test symbol in unit files that reference one of the main files in `mainFilePaths`.
public func unitTests(referencedByMainFiles mainFilePaths: [String]) -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
let cMainFiles: [UnsafePointer<CChar>] = mainFilePaths.map { UnsafePointer($0.withCString(strdup)!) }
defer { for cPath in cMainFiles { free(UnsafeMutablePointer(mutating: cPath)) } }
indexstoredb_index_unit_tests_referenced_by_main_files(impl, cMainFiles, cMainFiles.count) { symbol in
result.append(SymbolOccurrence(symbol))
return true
}
return result
}
/// Returns all unit test symbols in the index.
public func unitTests() -> [SymbolOccurrence] {
var result: [SymbolOccurrence] = []
indexstoredb_index_unit_tests(impl) { symbol in
result.append(SymbolOccurrence(symbol))
return true
}
return result
}
/// Returns the latest modification date of a unit that contains the given source file.
///
/// If no unit containing the given source file exists, returns `nil`.
public func dateOfLatestUnitFor(filePath: String) -> Date? {
let timestamp = filePath.withCString { filePathCString in
indexstoredb_timestamp_of_latest_unit_for_file(impl, filePathCString)
}
if timestamp == 0 {
return nil
}
return Date(timeIntervalSince1970: Double(timestamp) / 1_000_000_000)
}
}
public protocol IndexStoreLibraryProvider {
func library(forStorePath: String) -> IndexStoreLibrary?
}
public class IndexStoreLibrary {
let library: UnsafeMutableRawPointer // indexstoredb_indexstore_library_t
public var version: Version {
return Version(encoded: Int(indexstoredb_store_version(library)))
}
public var formatVersion: Int {
return Int(indexstoredb_format_version(library))
}
public struct Version: Comparable {
public let major: Int
public let minor: Int
public init(major: Int, minor: Int) {
self.major = major
self.minor = minor
}
public init(encoded: Int) {
self.init(major: encoded / 10000, minor: encoded % 10000)
}
public static func < (lhs: Version, rhs: Version) -> Bool {
return (lhs.major, lhs.minor) < (rhs.major, rhs.minor)
}
}
public init(dylibPath: String) throws {
var error: indexstoredb_error_t? = nil
guard let lib = indexstoredb_load_indexstore_library(dylibPath, &error) else {
defer { indexstoredb_error_dispose(error) }
throw IndexStoreDBError.loadIndexStore(error?.description ?? "unknown")
}
self.library = lib
}
deinit {
indexstoredb_release(library)
}
}
|