Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.6
// swift-tools-version: 6.0

import Foundation
import PackageDescription
Expand All @@ -25,6 +25,7 @@ var dependencies: [Package.Dependency] {

let package = Package(
name: "IndexStoreDB",
platforms: [.macOS(.v14)],
products: [
.library(
name: "IndexStoreDB",
Expand All @@ -37,10 +38,36 @@ let package = Package(
targets: ["ISDBTestSupport"]),
.executable(
name: "tibs",
targets: ["tibs"])
targets: ["tibs"]),
.library(
name: "IndexStore",
targets: ["IndexStore"]),
],
dependencies: dependencies,
targets: [
// MARK: Swift interface to read a raw index store

.target(
name: "IndexStore",
dependencies: ["IndexStoreDB_CIndexStoreDB"],
swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
.enableUpcomingFeature("InferIsolatedConformances"),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableExperimentalFeature("Lifetimes"),
.swiftLanguageMode(.v6)
]
),

.testTarget(
name: "IndexStoreTests",
dependencies: [
"IndexStore",
"ISDBTibs",
]
),

// MARK: Swift interface

Expand Down Expand Up @@ -153,6 +180,6 @@ let package = Package(
"Windows/Watchdog.inc",
]),
],

swiftLanguageModes: [.v5],
cxxLanguageStandard: .cxx17
)
2 changes: 1 addition & 1 deletion Sources/ISDBTibs/Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extension Process {
}

/// Runs a subprocess and returns its output as a String if it has a zero exit.
static func tibs_checkNonZeroExit(
package static func tibs_checkNonZeroExit(
arguments: [String],
environment: [String: String]? = nil
) throws -> String {
Expand Down
54 changes: 54 additions & 0 deletions Sources/IndexStore/IndexStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//

public import IndexStoreDB_CIndexStoreDB

/// An entire index store, ie. a directory containing the unit and record files.
public final class IndexStore: Sendable {
@usableFromInline nonisolated(unsafe) let store: indexstore_t
@usableFromInline let library: IndexStoreLibrary

init(at path: String, library: IndexStoreLibrary) throws {
self.library = library
store = try path.withCString { path in
try library.capturingError { error in
library.api.store_create(path, &error)
}
}
}

@inlinable
deinit {
library.api.store_dispose(store)
}

@inlinable
public func unitNames(sorted: Bool) -> IndexStoreSequence<IndexStoreStringRef> {
return IndexStoreSequence { body in
_ = iterateWithClosureAsContextToCFunctionPointer { context, handleResult in
self.library.api.store_units_apply_f(self.store, sorted ? 1 : 0, context, handleResult)
} handleResult: { result in
return body(IndexStoreStringRef(result))
}
}
}

@inlinable
public func unit(named unitName: IndexStoreStringRef) throws -> IndexStoreUnit {
return try IndexStoreUnit(store: self, unitName: unitName, library: self.library)
}

@inlinable
public func record(named recordName: IndexStoreStringRef) throws -> IndexStoreRecord {
return try IndexStoreRecord(store: self, recordName: recordName, library: self.library)
}
}
49 changes: 49 additions & 0 deletions Sources/IndexStore/IndexStoreError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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 Foundation
import IndexStoreDB_CIndexStoreDB

/// An error thrown by the libIndexStore dynamic library.
public final class IndexStoreError: Error, CustomStringConvertible, Sendable {
private nonisolated(unsafe) let error: indexstore_error_t?
private let library: IndexStoreLibrary

fileprivate init(takingOwnershipOf error: indexstore_error_t?, library: IndexStoreLibrary) {
self.error = error
self.library = library
}

deinit {
if let error {
library.api.error_dispose(error)
}
}

public var description: String {
guard let descriptionPointer = library.api.error_get_description(error) else {
return "Unknown error"
}
return String(cString: descriptionPointer)
}
}

extension IndexStoreLibrary {
func capturingError<T>(from body: (_ error: inout indexstore_error_t?) -> T?) throws -> T {
var error: indexstore_error_t? = nil
let result = body(&error)
guard let result else {
throw IndexStoreError(takingOwnershipOf: error, library: self)
}
return result
}
}
27 changes: 27 additions & 0 deletions Sources/IndexStore/IndexStoreLanguage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//

public import IndexStoreDB_CIndexStoreDB

public struct IndexStoreLanguage: Hashable, Sendable {
private let rawValue: indexstore_symbol_language_t

public static let c = IndexStoreLanguage(INDEXSTORE_SYMBOL_LANG_C)
public static let objectiveC = IndexStoreLanguage(INDEXSTORE_SYMBOL_LANG_OBJC)
public static let cxx = IndexStoreLanguage(INDEXSTORE_SYMBOL_LANG_CXX)
public static let swift = IndexStoreLanguage(INDEXSTORE_SYMBOL_LANG_SWIFT)

@usableFromInline
init(_ rawValue: indexstore_symbol_language_t) {
self.rawValue = rawValue
}
}
69 changes: 69 additions & 0 deletions Sources/IndexStore/IndexStoreLibrary.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//

public import Foundation
public import IndexStoreDB_CIndexStoreDB

fileprivate actor IndexStoreLibraryRegistry {
static let shared = IndexStoreLibraryRegistry()

var libraries: [URL: IndexStoreLibrary] = [:]

func library(withDylibUrl dylibUrl: URL) throws -> IndexStoreLibrary {
if let existingLibrary = libraries[dylibUrl] {
return existingLibrary
}
let library = try IndexStoreLibrary(dylibPath: dylibUrl)
libraries[dylibUrl] = library
return library
}
}

/// Represent a loaded `libIndexStore` dynamic library. This object hasn't opened a specific index store yet, it
/// represents the loaded library and the functions within it themselves.
public struct IndexStoreLibrary: Sendable {
struct DlopenFailedError: Error, CustomStringConvertible {
let path: URL
let error: String

var description: String {
"dlopen '\(path)' failed: \(error)"
}
}

@usableFromInline
let api: indexstore_functions_t

public static func at(dylibPath: URL) async throws -> IndexStoreLibrary {
return try await IndexStoreLibraryRegistry.shared.library(withDylibUrl: dylibPath)
}

/// `dlopen` the `libIndexStore` dynamic library located at `dylibPath` and load all known functions from it
fileprivate init(dylibPath: URL) throws {
#if os(Windows)
let dlopenModes: DLOpenFlags = []
#else
let dlopenModes: DLOpenFlags = [.lazy, .local, .first]
#endif
// We never dlclose the dylib. That way we do not have to track which types are still using this `IndexStoreLibrary`.
// Since we only open dylibs by path ones (through `IndexStoreLibraryRegistry`), this seems fine because most
// processes dealing with an index store will do so for the remainder of their execution time anyway.
let dlHandle = try dylibPath.withUnsafeFileSystemRepresentation { filePath in
try dlopen(String(cString: filePath!), mode: dlopenModes)
}
self.api = try indexstore_functions_t(dlHandle: dlHandle)
}

public func indexStore(at path: String) throws -> IndexStore {
return try IndexStore(at: path, library: self)
}
}
116 changes: 116 additions & 0 deletions Sources/IndexStore/IndexStoreOccurrence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//

public import IndexStoreDB_CIndexStoreDB

/// The occurrence of a symbol in a source file.
public struct IndexStoreOccurrence: ~Escapable, Sendable {
@usableFromInline nonisolated(unsafe) let occurrence: indexstore_occurrence_t
@usableFromInline let library: IndexStoreLibrary

@usableFromInline @_lifetime(borrow occurrence, borrow library)
init(occurrence: indexstore_occurrence_t, library: borrowing IndexStoreLibrary) {
self.occurrence = occurrence
self.library = library
}

// swift-format-ignore: UseSingleLinePropertyGetter, https://github.com/swiftlang/swift-format/issues/1102
@inlinable
public var symbol: IndexStoreSymbol {
@_lifetime(borrow self)
get {
let symbol = IndexStoreSymbol(symbol: library.api.occurrence_get_symbol(occurrence)!, library: library)
return _overrideLifetime(symbol, borrowing: self)
}
}

// swift-format-ignore: UseSingleLinePropertyGetter, https://github.com/swiftlang/swift-format/issues/1102
@inlinable
public var relations: RelationsSequence {
@_lifetime(borrow self)
get {
return RelationsSequence(self)
}
}

@inlinable
public var roles: IndexStoreSymbolRoles {
IndexStoreSymbolRoles(rawValue: self.library.api.occurrence_get_roles(occurrence))
}

@inlinable
public var position: (line: Int, column: Int) {
var line: UInt32 = 0
var column: UInt32 = 0
self.library.api.occurrence_get_line_col(occurrence, &line, &column)

return (Int(line), Int(column))
}

/// Specialized version of `IndexStoreSequence` that can have a lifetime dependence on a `IndexStoreOccurrence`.
///
/// There currently doesn't seem to be a way to model this using the closure-taking `IndexStoreSequence`.
public struct RelationsSequence: ~Escapable {
@usableFromInline let producer: IndexStoreOccurrence

@usableFromInline @_lifetime(borrow producer)
init(_ producer: borrowing IndexStoreOccurrence) {
self.producer = producer
}

@inlinable
public func forEach<Error>(
_ body: (IndexStoreSymbolRelation) throws(Error) -> IterationContinuationBehavior
) throws(Error) {
var caughtError: Error? = nil
_ = iterateWithClosureAsContextToCFunctionPointer { context, handleResult in
producer.library.api.occurrence_relations_apply_f(producer.occurrence, context, handleResult)
} handleResult: { (result: indexstore_symbol_relation_t?) in
do {
return try body(IndexStoreSymbolRelation(relation: result!, library: producer.library))
} catch let error as Error {
caughtError = error
return .stop
} catch {
// Should never happen because `body` can only throw errors of the generic argument type `Error`.
preconditionFailure("Caught error of unexpected type \(type(of: error))")
}
}
if let caughtError {
throw caughtError
}
}

@inlinable
public func map<Result, Error>(
_ transform: (IndexStoreSymbolRelation) throws(Error) -> Result
) throws(Error) -> [Result] {
return try self.compactMap { (value) throws(Error) in
try transform(value)
}
}

@inlinable
public func compactMap<Result, Error>(
_ transform: (IndexStoreSymbolRelation) throws(Error) -> Result?
) throws(Error) -> [Result] {
var result: [Result] = []
try self.forEach { (value) throws(Error) in
if let transformed = try transform(value) {
result.append(transformed)
}
return .continue
}
return result
}
}
}
Loading