From e36d674fae03ad83785ce6c4ca6d1294a733be23 Mon Sep 17 00:00:00 2001 From: Ankit Aggarwal Date: Sat, 15 Jun 2019 13:30:48 -0700 Subject: [PATCH] Implement test discovery on platforms without an Objective-C runtime (Linux, etc.) This is a WIP PR for implementing test discovery on linux using indexing data. The basic idea is to leverage the IndexStore library to get the test methods and then generate a LinuxMain.swift file during the build process. This PR "works" but there are a number of serious hacks that require resolving before we can merge it. --- Sources/Basic/Path.swift | 8 + Sources/Build/BuildDelegate.swift | 188 ++++++- Sources/Build/BuildPlan.swift | 98 +++- Sources/Build/ToolProtocol.swift | 16 +- Sources/Build/llbuild.swift | 40 +- Sources/Commands/Options.swift | 3 + Sources/Commands/SwiftTool.swift | 22 +- Sources/PackageModel/Target.swift | 13 + Sources/SPMUtility/IndexStore.swift | 240 ++++++++ Sources/SPMUtility/dlopen.swift | 116 ++++ Sources/clibc/include/indexstore_functions.h | 561 +++++++++++++++++++ Sources/clibc/include/module.modulemap | 1 + 12 files changed, 1276 insertions(+), 30 deletions(-) create mode 100644 Sources/SPMUtility/IndexStore.swift create mode 100644 Sources/SPMUtility/dlopen.swift create mode 100644 Sources/clibc/include/indexstore_functions.h diff --git a/Sources/Basic/Path.swift b/Sources/Basic/Path.swift index 1f6a528ed65..06f50abcb6c 100644 --- a/Sources/Basic/Path.swift +++ b/Sources/Basic/Path.swift @@ -121,6 +121,14 @@ public struct AbsolutePath: Hashable { return _impl.basename } + /// Returns the basename without the extension. + public var basenameWithoutExt: String { + if let ext = self.extension { + return String(basename.dropLast(ext.count + 1)) + } + return basename + } + /// Suffix (including leading `.` character) if any. Note that a basename /// that starts with a `.` character is not considered a suffix, nor is a /// trailing `.` character. diff --git a/Sources/Build/BuildDelegate.swift b/Sources/Build/BuildDelegate.swift index 0247002687c..bd183c701ee 100644 --- a/Sources/Build/BuildDelegate.swift +++ b/Sources/Build/BuildDelegate.swift @@ -186,6 +186,183 @@ extension SPMLLBuild.Diagnostic: DiagnosticDataConvertible { } } +class CustomLLBuildCommand: ExternalCommand { + let ctx: BuildExecutionContext + + required init(_ ctx: BuildExecutionContext) { + self.ctx = ctx + } + + func getSignature(_ command: SPMLLBuild.Command) -> [UInt8] { + return [] + } + + func execute(_ command: SPMLLBuild.Command) -> Bool { + fatalError("subclass responsibility") + } +} + +final class TestDiscoveryCommand: CustomLLBuildCommand { + + private func write( + tests: [IndexStore.TestCaseClass], + forModule module: String, + to path: AbsolutePath + ) throws { + let stream = try LocalFileOutputByteStream(path) + + stream <<< "import XCTest" <<< "\n" + stream <<< "@testable import " <<< module <<< "\n" + + for klass in tests { + stream <<< "\n" + stream <<< "fileprivate extension " <<< klass.name <<< " {" <<< "\n" + stream <<< indent(4) <<< "static let __allTests__\(klass.name) = [" <<< "\n" + for method in klass.methods { + let method = method.hasSuffix("()") ? String(method.dropLast(2)) : method + stream <<< indent(8) <<< "(\"\(method)\", \(method))," <<< "\n" + } + stream <<< indent(4) <<< "]" <<< "\n" + stream <<< "}" <<< "\n" + } + + stream <<< """ + func __allTests_\(module)() -> [XCTestCaseEntry] { + return [\n + """ + + for klass in tests { + stream <<< indent(8) <<< "testCase(\(klass.name).__allTests__\(klass.name)),\n" + } + + stream <<< """ + ] + } + """ + + stream.flush() + } + + private func execute(with tool: ToolProtocol) throws { + assert(tool is TestDiscoveryTool, "Unexpected tool \(tool)") + + let index = ctx.buildParameters.indexStore + let api = try ctx.indexStoreAPI.dematerialize() + let store = try IndexStore.open(store: index, api: api) + + // FIXME: We can speed this up by having one llbuild command per object file. + let tests = try tool.inputs.flatMap { + try store.listTests(inObjectFile: AbsolutePath($0)) + } + + let outputs = tool.outputs.compactMap{ try? AbsolutePath(validating: $0) } + let testsByModule = Dictionary(grouping: tests, by: { $0.module }) + + func isMainFile(_ path: AbsolutePath) -> Bool { + return path.basename == "main.swift" + } + + // Write one file for each test module. + // + // We could write everything in one file but that can easily run into type conflicts due + // in complex packages with large number of test targets. + for file in outputs { + if isMainFile(file) { continue } + + // FIXME: This is relying on implementation detail of the output but passing the + // the context all the way through is not worth it right now. + let module = file.basenameWithoutExt + + guard let tests = testsByModule[module] else { + // This module has no tests so just write an empty file for it. + try localFileSystem.writeFileContents(file, bytes: "") + continue + } + try write(tests: tests, forModule: module, to: file) + } + + // Write the main file. + let mainFile = outputs.first(where: isMainFile)! + let stream = try LocalFileOutputByteStream(mainFile) + + stream <<< "import XCTest" <<< "\n\n" + stream <<< "var tests = [XCTestCaseEntry]()" <<< "\n" + for module in testsByModule.keys { + stream <<< "tests += __allTests_\(module)()" <<< "\n" + } + stream <<< "\n" + stream <<< "XCTMain(tests)" <<< "\n" + + stream.flush() + } + + private func indent(_ spaces: Int) -> ByteStreamable { + return Format.asRepeating(string: " ", count: spaces) + } + + override func execute(_ command: SPMLLBuild.Command) -> Bool { + guard let tool = ctx.buildTimeCmdToolMap[command.name] else { + print("command \(command.name) not registered") + return false + } + do { + try execute(with: tool) + } catch { + // FIXME: Shouldn't use "print" here. + print("error:", error) + return false + } + return true + } +} + +private final class InProcessTool: Tool { + let ctx: BuildExecutionContext + + init(_ ctx: BuildExecutionContext) { + self.ctx = ctx + } + + func createCommand(_ name: String) -> ExternalCommand { + // FIXME: This should be able to dynamically look up the right command. + switch ctx.buildTimeCmdToolMap[name] { + case is TestDiscoveryTool: + return TestDiscoveryCommand(ctx) + default: + fatalError("Unhandled command \(name)") + } + } +} + +/// The context available during build execution. +public final class BuildExecutionContext { + + /// Mapping of command-name to its tool. + let buildTimeCmdToolMap: [String: ToolProtocol] + + var indexStoreAPI: Result { + indexStoreAPICache.getValue(self) + } + + let buildParameters: BuildParameters + + public init(_ plan: BuildPlan, buildTimeCmdToolMap: [String: ToolProtocol]) { + self.buildParameters = plan.buildParameters + self.buildTimeCmdToolMap = buildTimeCmdToolMap + } + + // MARK:- Private + + private var indexStoreAPICache = LazyCache(createIndexStoreAPI) + private func createIndexStoreAPI() -> Result { + Result { + let ext = buildParameters.triple.dynamicLibraryExtension + let indexStoreLib = buildParameters.toolchain.toolchainLibDir.appending(component: "libIndexStore" + ext) + return try IndexStoreAPI(dylib: indexStoreLib) + } + } +} + private let newLineByte: UInt8 = 10 public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParserDelegate { private let diagnostics: DiagnosticsEngine @@ -201,7 +378,10 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser /// Target name keyed by llbuild command name. private let targetNames: [String: String] + let buildExecutionContext: BuildExecutionContext + public init( + bctx: BuildExecutionContext, plan: BuildPlan, diagnostics: DiagnosticsEngine, outputStream: OutputByteStream, @@ -212,6 +392,7 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser // https://forums.swift.org/t/allow-self-x-in-class-convenience-initializers/15924 self.outputStream = outputStream as? ThreadSafeOutputByteStream ?? ThreadSafeOutputByteStream(outputStream) self.progressAnimation = progressAnimation + self.buildExecutionContext = bctx let buildConfig = plan.buildParameters.configuration.dirname @@ -231,7 +412,12 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser } public func lookupTool(_ name: String) -> Tool? { - return nil + switch name { + case TestDiscoveryTool.name: + return InProcessTool(buildExecutionContext) + default: + return nil + } } public func hadCommandFailure() { diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index b5a336ebd60..d0f0720ed35 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -128,6 +128,9 @@ public struct BuildParameters { /// Whether to enable code coverage. public let enableCodeCoverage: Bool + /// Whether to enable test discovery on platforms without Objective-C runtime. + public let enableTestDiscovery: Bool + /// Whether to enable generation of `.swiftinterface` files alongside /// `.swiftmodule`s. public let enableParseableModuleInterfaces: Bool @@ -156,7 +159,8 @@ public struct BuildParameters { sanitizers: EnabledSanitizers = EnabledSanitizers(), enableCodeCoverage: Bool = false, indexStoreMode: IndexStoreMode = .auto, - enableParseableModuleInterfaces: Bool = false + enableParseableModuleInterfaces: Bool = false, + enableTestDiscovery: Bool = false ) { self.dataPath = dataPath self.configuration = configuration @@ -170,6 +174,7 @@ public struct BuildParameters { self.enableCodeCoverage = enableCodeCoverage self.indexStoreMode = indexStoreMode self.enableParseableModuleInterfaces = enableParseableModuleInterfaces + self.enableTestDiscovery = enableTestDiscovery } /// Returns the compiler arguments for the index store, if enabled. @@ -469,13 +474,22 @@ public final class SwiftTargetBuildDescription { /// If this target is a test target. public let isTestTarget: Bool + /// True if this is the test discovery target. + public let testDiscoveryTarget: Bool + /// Create a new target description with target and build parameters. - init(target: ResolvedTarget, buildParameters: BuildParameters, isTestTarget: Bool? = nil) { + init( + target: ResolvedTarget, + buildParameters: BuildParameters, + isTestTarget: Bool? = nil, + testDiscoveryTarget: Bool = false + ) { assert(target.underlyingTarget is SwiftTarget, "underlying target type mismatch \(target)") self.target = target self.buildParameters = buildParameters // Unless mentioned explicitly, use the target type to determine if this is a test target. self.isTestTarget = isTestTarget ?? (target.type == .test) + self.testDiscoveryTarget = testDiscoveryTarget } /// The arguments needed to compile this target. @@ -868,6 +882,65 @@ public class BuildPlan { /// Diagnostics Engine for emitting diagnostics. let diagnostics: DiagnosticsEngine + private static func planLinuxMain( + _ buildParameters: BuildParameters, + _ graph: PackageGraph + ) throws -> (ResolvedTarget, SwiftTargetBuildDescription)? { + guard buildParameters.triple.isLinux() else { + return nil + } + + // Currently, there can be only one test product in a package graph. + guard let testProduct = graph.allProducts.first(where: { $0.type == .test }) else { + return nil + } + + if !buildParameters.enableTestDiscovery { + guard let linuxMainTarget = testProduct.linuxMainTarget else { + throw Error.missingLinuxMain + } + + let desc = SwiftTargetBuildDescription( + target: linuxMainTarget, + buildParameters: buildParameters, + isTestTarget: true + ) + return (linuxMainTarget, desc) + } + + // We'll generate sources containing the test names as part of the build process. + let derivedTestListDir = buildParameters.buildPath.appending(components: "testlist.derived") + let mainFile = derivedTestListDir.appending(component: "main.swift") + + var paths: [AbsolutePath] = [] + paths.append(mainFile) + let testTargets = graph.rootPackages.flatMap{ $0.targets }.filter{ $0.type == .test } + for testTarget in testTargets { + let path = derivedTestListDir.appending(components: testTarget.name + ".swift") + paths.append(path) + } + + let src = Sources(paths: paths, root: derivedTestListDir) + + let swiftTarget = SwiftTarget( + testDiscoverySrc: src, + name: testProduct.name, + dependencies: testProduct.underlyingProduct.targets) + let linuxMainTarget = ResolvedTarget( + target: swiftTarget, + dependencies: testProduct.targets.map(ResolvedTarget.Dependency.target) + ) + + let target = SwiftTargetBuildDescription( + target: linuxMainTarget, + buildParameters: buildParameters, + isTestTarget: true, + testDiscoveryTarget: true + ) + + return (linuxMainTarget, target) + } + /// Create a build plan with build parameters and a package graph. public init( buildParameters: BuildParameters, @@ -921,19 +994,10 @@ public class BuildPlan { throw Diagnostics.fatalError } - if buildParameters.triple.isLinux() { - // FIXME: Create a target for LinuxMain file on linux. - // This will go away once it is possible to auto detect tests. - let testProducts = graph.allProducts.filter({ $0.type == .test }) - - for product in testProducts { - guard let linuxMainTarget = product.linuxMainTarget else { - throw Error.missingLinuxMain - } - let target = SwiftTargetBuildDescription( - target: linuxMainTarget, buildParameters: buildParameters, isTestTarget: true) - targetMap[linuxMainTarget] = .swift(target) - } + // Plan the linux main target. + if let result = try Self.planLinuxMain(buildParameters, graph) { + targetMap[result.0] = .swift(result.1) + self.linuxMainTarget = result.0 } var productMap: [ResolvedProduct: ProductBuildDescription] = [:] @@ -953,6 +1017,8 @@ public class BuildPlan { try plan() } + private var linuxMainTarget: ResolvedTarget? + static func validateDeploymentVersionOfProductDependency( _ product: ResolvedProduct, forTarget target: ResolvedTarget, @@ -1094,7 +1160,7 @@ public class BuildPlan { if buildParameters.triple.isLinux() { if product.type == .test { - product.linuxMainTarget.map({ staticTargets.append($0) }) + linuxMainTarget.map({ staticTargets.append($0) }) } } diff --git a/Sources/Build/ToolProtocol.swift b/Sources/Build/ToolProtocol.swift index 07ad19b7861..aa7715de700 100644 --- a/Sources/Build/ToolProtocol.swift +++ b/Sources/Build/ToolProtocol.swift @@ -14,7 +14,7 @@ import SPMUtility import class Foundation.ProcessInfo /// Describes a tool which can be understood by llbuild's BuildSystem library. -protocol ToolProtocol { +public protocol ToolProtocol { /// The list of inputs to declare. var inputs: [String] { get } @@ -38,6 +38,20 @@ struct PhonyTool: ToolProtocol { } } +struct TestDiscoveryTool: ToolProtocol { + + static let name: String = "test-discovery-tool" + + let inputs: [String] + let outputs: [String] + + func append(to stream: OutputByteStream) { + stream <<< " tool: \(Self.name)\n" + stream <<< " inputs: " <<< Format.asJSON(inputs) <<< "\n" + stream <<< " outputs: " <<< Format.asJSON(outputs) <<< "\n" + } +} + struct ShellTool: ToolProtocol { let description: String let inputs: [String] diff --git a/Sources/Build/llbuild.swift b/Sources/Build/llbuild.swift index dd5b7861a68..5f2c589983f 100644 --- a/Sources/Build/llbuild.swift +++ b/Sources/Build/llbuild.swift @@ -14,7 +14,7 @@ import PackageModel import PackageGraph /// llbuild manifest file generator for a build plan. -public struct LLBuildManifestGenerator { +public final class LLBuildManifestGenerator { /// The name of the llbuild target that builds all products and targets (excluding tests). public static let llbuildMainTargetName = "main" @@ -49,7 +49,7 @@ public struct LLBuildManifestGenerator { } /// All commands. - private(set) var allCommands = SortedArray(areInIncreasingOrder: <) + var allCommands = SortedArray(areInIncreasingOrder: <) /// Other targets. private var otherTargets: [Target] = [] @@ -101,6 +101,8 @@ public struct LLBuildManifestGenerator { } } + addTestFileGeneration(&targets) + // Create command for all products in the plan. for (product, description) in plan.productMap { // Only build products by default if they are reachabe from a root target. @@ -134,6 +136,40 @@ public struct LLBuildManifestGenerator { try localFileSystem.writeFileContents(path, bytes: stream.bytes) } + private func addTestFileGeneration(_ targets: inout Targets) { + var testTargets: [SwiftTargetBuildDescription] = [] + var _testDiscoveryTarget: SwiftTargetBuildDescription? + + for target in plan.targets { + switch target { + case .swift(let target) where target.isTestTarget: + if target.testDiscoveryTarget { + _testDiscoveryTarget = target + } else { + testTargets.append(target) + } + default: + break + } + } + + // Nothing to do if we don't have the test discovery target. + guard let testDiscoveryTarget = _testDiscoveryTarget else { + return + } + + let objectFiles = testTargets.flatMap{ $0.objects }.map{ $0.pathString }.sorted() + let outputs = testDiscoveryTarget.target.sources.paths + let tool = TestDiscoveryTool(inputs: objectFiles, outputs: outputs.map{ $0.pathString }) + + let cmdName = outputs.first{ $0.basename == "main.swift" }!.pathString + targets.allCommands.insert(Command(name: cmdName, tool: tool)) + buildTimeCmdToolMap[cmdName] = tool + } + + /// Map of command -> tool that is used during the build for in-process tools. + public private(set) var buildTimeCmdToolMap: [String: ToolProtocol] = [:] + /// Create a llbuild target for a product description. private func createProductTarget(_ buildProduct: ProductBuildDescription) -> Target { let tool: ToolProtocol diff --git a/Sources/Commands/Options.swift b/Sources/Commands/Options.swift index 7d812f820d0..93ee81b2d86 100644 --- a/Sources/Commands/Options.swift +++ b/Sources/Commands/Options.swift @@ -77,5 +77,8 @@ public class ToolOptions { /// The number of jobs for llbuild to start (aka the number of schedulerLanes) public var jobs: UInt32? = nil + /// Whether to enable test discovery on platforms without Objective-C runtime. + public var enableTestDiscovery: Bool = false + public required init() {} } diff --git a/Sources/Commands/SwiftTool.swift b/Sources/Commands/SwiftTool.swift index 8b3d3e78c21..28c61d76565 100644 --- a/Sources/Commands/SwiftTool.swift +++ b/Sources/Commands/SwiftTool.swift @@ -362,6 +362,11 @@ public class SwiftTool { usage: "The number of jobs to spawn in parallel during the build process"), to: { $0.jobs = UInt32($1) }) + binder.bind( + option: parser.add(option: "--enable-test-discovery", kind: Bool.self, + usage: "Enable test discovery on platforms without Objective-C runtime"), + to: { $0.enableTestDiscovery = $1 }) + // Let subclasses bind arguments. type(of: self).defineArguments(parser: parser, binder: binder) @@ -583,13 +588,6 @@ public class SwiftTool { return try subset.llbuildTargetName(for: loadPackageGraph(), diagnostics: diagnostics, config: buildParameters.configuration.dirname) } } - - func build(plan: BuildPlan, parameters: BuildParameters, subset: BuildSubset) throws { - guard let llbuildTargetName = try computeLLBuildTargetName(for: subset, buildParameters: parameters) else { - return - } - try runLLBuild(plan: plan, manifest: parameters.llbuildManifest, llbuildTarget: llbuildTargetName) - } /// Build a subset of products and targets using swift-build-tool. func build(plan: BuildPlan, subset: BuildSubset) throws { @@ -602,9 +600,11 @@ public class SwiftTool { let llbuild = LLBuildManifestGenerator(plan, client: "basic") try llbuild.generateManifest(at: manifest) + let bctx = BuildExecutionContext(plan, buildTimeCmdToolMap: llbuild.buildTimeCmdToolMap) + // Run llbuild. assert(localFileSystem.isFile(manifest), "llbuild manifest not present: \(manifest)") - try runLLBuild(plan: plan, manifest: manifest, llbuildTarget: llbuildTargetName) + try runLLBuild(plan: plan, bctx: bctx, manifest: manifest, llbuildTarget: llbuildTargetName) // Create backwards-compatibilty symlink to old build path. let oldBuildPath = buildPath.appending(component: options.configuration.dirname) @@ -614,13 +614,14 @@ public class SwiftTool { try createSymlink(oldBuildPath, pointingAt: plan.buildParameters.buildPath, relative: true) } - func runLLBuild(plan: BuildPlan, manifest: AbsolutePath, llbuildTarget: String) throws { + private func runLLBuild(plan: BuildPlan, bctx: BuildExecutionContext, manifest: AbsolutePath, llbuildTarget: String) throws { // Setup the build delegate. let isVerbose = verbosity != .concise let progressAnimation: ProgressAnimationProtocol = isVerbose ? MultiLineNinjaProgressAnimation(stream: stdoutStream) : NinjaProgressAnimation(stream: stdoutStream) let buildDelegate = BuildDelegate( + bctx: bctx, plan: plan, diagnostics: diagnostics, outputStream: stdoutStream, @@ -658,7 +659,8 @@ public class SwiftTool { sanitizers: options.sanitizers, enableCodeCoverage: options.shouldEnableCodeCoverage, indexStoreMode: options.indexStoreMode, - enableParseableModuleInterfaces: options.shouldEnableParseableModuleInterfaces + enableParseableModuleInterfaces: options.shouldEnableParseableModuleInterfaces, + enableTestDiscovery: options.enableTestDiscovery ) }) }() diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index ca9cbcdb584..7f346d33bc0 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -79,6 +79,19 @@ public class SwiftTarget: Target { /// The file name of linux main file. public static let linuxMainBasename = "LinuxMain.swift" + public init(testDiscoverySrc: Sources, name: String, dependencies: [Target]) { + self.swiftVersion = .v5 + + super.init( + name: name, + platforms: [], + type: .executable, + sources: testDiscoverySrc, + dependencies: dependencies, + buildSettings: .init() + ) + } + /// Create an executable Swift target from linux main test manifest file. init(linuxMain: AbsolutePath, name: String, dependencies: [Target]) { // Look for the first swift test target and use the same swift version diff --git a/Sources/SPMUtility/IndexStore.swift b/Sources/SPMUtility/IndexStore.swift new file mode 100644 index 00000000000..99814fba409 --- /dev/null +++ b/Sources/SPMUtility/IndexStore.swift @@ -0,0 +1,240 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2019 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +import Basic +import clibc + +public final class IndexStore { + + public struct TestCaseClass { + public var name: String + public var module: String + public var methods: [String] + } + + let api: IndexStoreAPI + + var fn: indexstore_functions_t { + return api.fn + } + + let store: indexstore_t + + private init(store: indexstore_t, api: IndexStoreAPI) { + self.store = store + self.api = api + } + + static public func open(store path: AbsolutePath, api: IndexStoreAPI) throws -> IndexStore { + if let store = try api.call({ api.fn.store_create(path.pathString, &$0) }) { + return IndexStore(store: store, api: api) + } + throw StringError("Unable to open store at \(path)") + } + + public func listTests(inObjectFile object: AbsolutePath) throws -> [TestCaseClass] { + // Get the records of this object file. + let unitReader = try api.call{ fn.unit_reader_create(store, unitName(object: object), &$0) } + let records = try getRecords(unitReader: unitReader) + + // Get the test classes. + let testCaseClasses = try records.flatMap{ try self.getTestCaseClasses(forRecord: $0) } + + // Fill the module name and return. + let module = fn.unit_reader_get_module_name(unitReader).str + return testCaseClasses.map { + var c = $0 + c.module = module + return c + } + } + + private func getTestCaseClasses(forRecord record: String) throws -> [TestCaseClass] { + let recordReader = try api.call{ fn.record_reader_create(store, record, &$0) } + + class TestCaseBuilder { + var classToMethods: [String: Set] = [:] + + func add(klass: String, method: String) { + classToMethods[klass, default: []].insert(method) + } + + func build() -> [TestCaseClass] { + return classToMethods.map { + TestCaseClass(name: $0.key, module: "", methods: $0.value.sorted()) + } + } + } + + let builder = Ref(TestCaseBuilder(), api: api) + + let ctx = unsafeBitCast(Unmanaged.passUnretained(builder), to: UnsafeMutableRawPointer.self) + _ = fn.record_reader_occurrences_apply_f(recordReader, ctx) { ctx , occ -> Bool in + let builder = Unmanaged>.fromOpaque(ctx!).takeUnretainedValue() + let fn = builder.api.fn + + // Get the symbol. + let sym = fn.occurrence_get_symbol(occ) + + // We only care about symbols that are marked unit tests and are instance methods. + if fn.symbol_get_properties(sym) != UInt64(INDEXSTORE_SYMBOL_PROPERTY_UNITTEST.rawValue) { + return true + } + if fn.symbol_get_kind(sym) != INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD { + return true + } + + let className = Ref("", api: builder.api) + let ctx = unsafeBitCast(Unmanaged.passUnretained(className), to: UnsafeMutableRawPointer.self) + + _ = fn.occurrence_relations_apply_f(occ!, ctx) { ctx, relation in + guard let relation = relation else { return true } + let className = Unmanaged>.fromOpaque(ctx!).takeUnretainedValue() + let fn = className.api.fn + + // Look for the class. + if fn.symbol_relation_get_roles(relation) != UInt64(INDEXSTORE_SYMBOL_ROLE_REL_CHILDOF.rawValue) { + return true + } + + let sym = fn.symbol_relation_get_symbol(relation) + className.instance = fn.symbol_get_name(sym).str + return true + } + + if !className.instance.isEmpty { + let testMethod = fn.symbol_get_name(sym).str + builder.instance.add(klass: className.instance, method: testMethod) + } + + return true + } + + return builder.instance.build() + } + + private func getRecords(unitReader: indexstore_unit_reader_t?) throws -> [String] { + let builder = Ref([String](), api: api) + + let ctx = unsafeBitCast(Unmanaged.passUnretained(builder), to: UnsafeMutableRawPointer.self) + _ = fn.unit_reader_dependencies_apply_f(unitReader, ctx) { ctx , unit -> Bool in + let store = Unmanaged>.fromOpaque(ctx!).takeUnretainedValue() + let fn = store.api.fn + if fn.unit_dependency_get_kind(unit) == INDEXSTORE_UNIT_DEPENDENCY_RECORD { + store.instance.append(fn.unit_dependency_get_name(unit).str) + } + return true + } + + return builder.instance + } + + private func unitName(object: AbsolutePath) -> String { + let initialSize = 64 + var buf = UnsafeMutablePointer.allocate(capacity: initialSize) + let len = fn.store_get_unit_name_from_output_path(store, object.pathString, buf, initialSize) + + if len + 1 > initialSize { + buf.deallocate() + buf = UnsafeMutablePointer.allocate(capacity: len + 1) + _ = fn.store_get_unit_name_from_output_path(store, object.pathString, buf, len + 1) + } + + defer { + buf.deallocate() + } + + return String(cString: buf) + } +} + +private class Ref { + let api: IndexStoreAPI + var instance: T + init(_ instance: T, api: IndexStoreAPI) { + self.instance = instance + self.api = api + } +} + +public final class IndexStoreAPI { + + /// The path of the index store dylib. + private let path: AbsolutePath + + /// Handle of the dynamic library. + private let dylib: DLHandle + + /// The index store API functions. + fileprivate let fn: indexstore_functions_t + + fileprivate func call(_ fn: (inout indexstore_error_t?) -> T) throws -> T { + var error: indexstore_error_t? = nil + let ret = fn(&error) + + if let error = error { + if let desc = self.fn.error_get_description(error) { + throw StringError(String(cString: desc)) + } + throw StringError("Unable to get description for error: \(error)") + } + + return ret + } + + public init(dylib path: AbsolutePath) throws { + self.path = path + self.dylib = try dlopen(path.pathString, mode: [.lazy, .local, .first, .deepBind]) + + func dlsym_required(_ handle: DLHandle, symbol: String) throws -> T { + guard let sym: T = dlsym(handle, symbol: symbol) else { + throw StringError("Missing required symbol: \(symbol)") + } + return sym + } + + var api = indexstore_functions_t() + api.store_create = try dlsym_required(dylib, symbol: "indexstore_store_create") + api.store_get_unit_name_from_output_path = try dlsym_required(dylib, symbol: "indexstore_store_get_unit_name_from_output_path") + api.unit_reader_create = try dlsym_required(dylib, symbol: "indexstore_unit_reader_create") + api.error_get_description = try dlsym_required(dylib, symbol: "indexstore_error_get_description") + api.unit_reader_dependencies_apply_f = try dlsym_required(dylib, symbol: "indexstore_unit_reader_dependencies_apply_f") + api.unit_reader_get_module_name = try dlsym_required(dylib, symbol: "indexstore_unit_reader_get_module_name") + api.unit_dependency_get_kind = try dlsym_required(dylib, symbol: "indexstore_unit_dependency_get_kind") + api.unit_dependency_get_name = try dlsym_required(dylib, symbol: "indexstore_unit_dependency_get_name") + api.record_reader_create = try dlsym_required(dylib, symbol: "indexstore_record_reader_create") + api.symbol_get_name = try dlsym_required(dylib, symbol: "indexstore_symbol_get_name") + api.symbol_get_properties = try dlsym_required(dylib, symbol: "indexstore_symbol_get_properties") + api.symbol_get_kind = try dlsym_required(dylib, symbol: "indexstore_symbol_get_kind") + api.record_reader_occurrences_apply_f = try dlsym_required(dylib, symbol: "indexstore_record_reader_occurrences_apply_f") + api.occurrence_get_symbol = try dlsym_required(dylib, symbol: "indexstore_occurrence_get_symbol") + api.occurrence_relations_apply_f = try dlsym_required(dylib, symbol: "indexstore_occurrence_relations_apply_f") + api.symbol_relation_get_symbol = try dlsym_required(dylib, symbol: "indexstore_symbol_relation_get_symbol") + api.symbol_relation_get_roles = try dlsym_required(dylib, symbol: "indexstore_symbol_relation_get_roles") + + self.fn = api + } + + deinit { + // FIXME: is it safe to dlclose() indexstore? If so, do that here. For now, let the handle leak. + dylib.leak() + } +} + +extension indexstore_string_ref_t { + fileprivate var str: String { + return String( + bytesNoCopy: UnsafeMutableRawPointer(mutating: data), + length: length, + encoding: .utf8, + freeWhenDone: false + )! + } +} diff --git a/Sources/SPMUtility/dlopen.swift b/Sources/SPMUtility/dlopen.swift new file mode 100644 index 00000000000..a36b00522a2 --- /dev/null +++ b/Sources/SPMUtility/dlopen.swift @@ -0,0 +1,116 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2019 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import SPMLibc + +public final class DLHandle { + #if os(Windows) + typealias Handle = HMODULE + #else + typealias Handle = UnsafeMutableRawPointer + #endif + var rawValue: Handle? = nil + + init(rawValue: Handle) { + self.rawValue = rawValue + } + + deinit { + precondition(rawValue == nil, "DLHandle must be closed or explicitly leaked before destroying") + } + + public func close() throws { + if let handle = rawValue { + #if os(Windows) + guard FreeLibrary(handle) != 0 else { + throw DLError.close("Failed to FreeLibrary: \(GetLastError())") + } + #else + guard dlclose(handle) == 0 else { + throw DLError.close(dlerror() ?? "unknown error") + } + #endif + } + rawValue = nil + } + + public func leak() { + rawValue = nil + } +} + +public struct DLOpenFlags: RawRepresentable, OptionSet { + + #if !os(Windows) + public static let lazy: DLOpenFlags = DLOpenFlags(rawValue: RTLD_LAZY) + public static let now: DLOpenFlags = DLOpenFlags(rawValue: RTLD_NOW) + public static let local: DLOpenFlags = DLOpenFlags(rawValue: RTLD_LOCAL) + public static let global: DLOpenFlags = DLOpenFlags(rawValue: RTLD_GLOBAL) + + // Platform-specific flags. + #if os(macOS) + public static let first: DLOpenFlags = DLOpenFlags(rawValue: RTLD_FIRST) + public static let deepBind: DLOpenFlags = DLOpenFlags(rawValue: 0) + #else + public static let first: DLOpenFlags = DLOpenFlags(rawValue: 0) + public static let deepBind: DLOpenFlags = DLOpenFlags(rawValue: RTLD_DEEPBIND) + #endif + #endif + + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } +} + +public enum DLError: Swift.Error { + case `open`(String) + case close(String) +} + +public func dlopen(_ path: String?, mode: DLOpenFlags) throws -> DLHandle { + #if os(Windows) + guard let handle = path?.withCString(encodedAs: UTF16.self, LoadLibraryW) else { + throw DLError.open("LoadLibraryW failed: \(GetLastError())") + } + #else + guard let handle = SPMLibc.dlopen(path, mode.rawValue) else { + throw DLError.open(dlerror() ?? "unknown error") + } + #endif + return DLHandle(rawValue: handle) +} + +public func dlsym(_ handle: DLHandle, symbol: String) -> T? { + #if os(Windows) + guard let ptr = GetProcAddress(handle.rawValue!, symbol) else { + return nil + } + #else + guard let ptr = dlsym(handle.rawValue!, symbol) else { + return nil + } + #endif + return unsafeBitCast(ptr, to: T.self) +} + +public func dlclose(_ handle: DLHandle) throws { + try handle.close() +} + +#if !os(Windows) +public func dlerror() -> String? { + if let err: UnsafeMutablePointer = dlerror() { + return String(cString: err) + } + return nil +} +#endif diff --git a/Sources/clibc/include/indexstore_functions.h b/Sources/clibc/include/indexstore_functions.h new file mode 100644 index 00000000000..cc2ca2acbe1 --- /dev/null +++ b/Sources/clibc/include/indexstore_functions.h @@ -0,0 +1,561 @@ +/*===-- indexstore/indexstore_functions.h - Index Store C API ------- C -*-===*\ +|* *| +|* The LLVM Compiler Infrastructure *| +|* *| +|* This file is distributed under the University of Illinois Open Source *| +|* License. See LICENSE.TXT for details. *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* Shim version of indexstore.h suitable for using with dlopen. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef INDEXSTOREDB_INDEXSTORE_INDEXSTORE_FUNCTIONS_H +#define INDEXSTOREDB_INDEXSTORE_INDEXSTORE_FUNCTIONS_H + +#include +#include +#include +#include + +/** + * \brief The version constants for the Index Store C API. + * INDEXSTORE_VERSION_MINOR should increase when there are API additions. + * INDEXSTORE_VERSION_MAJOR is intended for "major" source/ABI breaking changes. + */ +#define INDEXSTORE_VERSION_MAJOR 0 +#define INDEXSTORE_VERSION_MINOR 11 + +#define INDEXSTORE_VERSION_ENCODE(major, minor) ( \ + ((major) * 10000) \ + + ((minor) * 1)) + +#define INDEXSTORE_VERSION INDEXSTORE_VERSION_ENCODE( \ + INDEXSTORE_VERSION_MAJOR, \ + INDEXSTORE_VERSION_MINOR ) + +#define INDEXSTORE_VERSION_STRINGIZE_(major, minor) \ + #major"."#minor +#define INDEXSTORE_VERSION_STRINGIZE(major, minor) \ + INDEXSTORE_VERSION_STRINGIZE_(major, minor) + +#define INDEXSTORE_VERSION_STRING INDEXSTORE_VERSION_STRINGIZE( \ + INDEXSTORE_VERSION_MAJOR, \ + INDEXSTORE_VERSION_MINOR) + +// Workaround a modules issue with time_t on linux. +#if __has_include() +#include +#endif + +#ifdef __cplusplus +# define INDEXSTORE_BEGIN_DECLS extern "C" { +# define INDEXSTORE_END_DECLS } +#else +# define INDEXSTORE_BEGIN_DECLS +# define INDEXSTORE_END_DECLS +#endif + +#ifndef INDEXSTORE_PUBLIC +# if defined (_MSC_VER) +# define INDEXSTORE_PUBLIC __declspec(dllimport) +# else +# define INDEXSTORE_PUBLIC +# endif +#endif + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +// FIXME: we need a runtime check as well since the library may have been built +// without blocks support. +#if __has_feature(blocks) && defined(__APPLE__) +# define INDEXSTORE_HAS_BLOCKS 1 +#else +# define INDEXSTORE_HAS_BLOCKS 0 +#endif + +INDEXSTORE_BEGIN_DECLS + +typedef void *indexstore_error_t; + +typedef struct { + const char *data; + size_t length; +} indexstore_string_ref_t; + +typedef void *indexstore_t; + +typedef void *indexstore_unit_event_notification_t; +typedef void *indexstore_unit_event_t; + +typedef enum { + INDEXSTORE_UNIT_EVENT_ADDED = 1, + INDEXSTORE_UNIT_EVENT_REMOVED = 2, + INDEXSTORE_UNIT_EVENT_MODIFIED = 3, + INDEXSTORE_UNIT_EVENT_DIRECTORY_DELETED = 4, +} indexstore_unit_event_kind_t; + +typedef struct { + /// If true, \c indexstore_store_start_unit_event_listening will block until + /// the initial set of units is passed to the unit event handler, otherwise + /// the function will return and the initial set will be passed asynchronously. + bool wait_initial_sync; +} indexstore_unit_event_listen_options_t; + +typedef void *indexstore_symbol_t; + +typedef enum { + INDEXSTORE_SYMBOL_KIND_UNKNOWN = 0, + INDEXSTORE_SYMBOL_KIND_MODULE = 1, + INDEXSTORE_SYMBOL_KIND_NAMESPACE = 2, + INDEXSTORE_SYMBOL_KIND_NAMESPACEALIAS = 3, + INDEXSTORE_SYMBOL_KIND_MACRO = 4, + INDEXSTORE_SYMBOL_KIND_ENUM = 5, + INDEXSTORE_SYMBOL_KIND_STRUCT = 6, + INDEXSTORE_SYMBOL_KIND_CLASS = 7, + INDEXSTORE_SYMBOL_KIND_PROTOCOL = 8, + INDEXSTORE_SYMBOL_KIND_EXTENSION = 9, + INDEXSTORE_SYMBOL_KIND_UNION = 10, + INDEXSTORE_SYMBOL_KIND_TYPEALIAS = 11, + INDEXSTORE_SYMBOL_KIND_FUNCTION = 12, + INDEXSTORE_SYMBOL_KIND_VARIABLE = 13, + INDEXSTORE_SYMBOL_KIND_FIELD = 14, + INDEXSTORE_SYMBOL_KIND_ENUMCONSTANT = 15, + INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD = 16, + INDEXSTORE_SYMBOL_KIND_CLASSMETHOD = 17, + INDEXSTORE_SYMBOL_KIND_STATICMETHOD = 18, + INDEXSTORE_SYMBOL_KIND_INSTANCEPROPERTY = 19, + INDEXSTORE_SYMBOL_KIND_CLASSPROPERTY = 20, + INDEXSTORE_SYMBOL_KIND_STATICPROPERTY = 21, + INDEXSTORE_SYMBOL_KIND_CONSTRUCTOR = 22, + INDEXSTORE_SYMBOL_KIND_DESTRUCTOR = 23, + INDEXSTORE_SYMBOL_KIND_CONVERSIONFUNCTION = 24, + INDEXSTORE_SYMBOL_KIND_PARAMETER = 25, + INDEXSTORE_SYMBOL_KIND_USING = 26, + + INDEXSTORE_SYMBOL_KIND_COMMENTTAG = 1000, +} indexstore_symbol_kind_t; + +typedef enum { + INDEXSTORE_SYMBOL_SUBKIND_NONE = 0, + INDEXSTORE_SYMBOL_SUBKIND_CXXCOPYCONSTRUCTOR = 1, + INDEXSTORE_SYMBOL_SUBKIND_CXXMOVECONSTRUCTOR = 2, + INDEXSTORE_SYMBOL_SUBKIND_ACCESSORGETTER = 3, + INDEXSTORE_SYMBOL_SUBKIND_ACCESSORSETTER = 4, + INDEXSTORE_SYMBOL_SUBKIND_USINGTYPENAME = 5, + INDEXSTORE_SYMBOL_SUBKIND_USINGVALUE = 6, + + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORWILLSET = 1000, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORDIDSET = 1001, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORADDRESSOR = 1002, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORMUTABLEADDRESSOR = 1003, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFSTRUCT = 1004, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFCLASS = 1005, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFENUM = 1006, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTEXTENSIONOFPROTOCOL = 1007, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTPREFIXOPERATOR = 1008, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTPOSTFIXOPERATOR = 1009, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTINFIXOPERATOR = 1010, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTSUBSCRIPT = 1011, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTASSOCIATEDTYPE = 1012, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTGENERICTYPEPARAM = 1013, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORREAD = 1014, + INDEXSTORE_SYMBOL_SUBKIND_SWIFTACCESSORMODIFY = 1015, +} indexstore_symbol_subkind_t; + +typedef enum { + INDEXSTORE_SYMBOL_PROPERTY_GENERIC = 1 << 0, + INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_PARTIAL_SPECIALIZATION = 1 << 1, + INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_SPECIALIZATION = 1 << 2, + INDEXSTORE_SYMBOL_PROPERTY_UNITTEST = 1 << 3, + INDEXSTORE_SYMBOL_PROPERTY_IBANNOTATED = 1 << 4, + INDEXSTORE_SYMBOL_PROPERTY_IBOUTLETCOLLECTION = 1 << 5, + INDEXSTORE_SYMBOL_PROPERTY_GKINSPECTABLE = 1 << 6, + INDEXSTORE_SYMBOL_PROPERTY_LOCAL = 1 << 7, + INDEXSTORE_SYMBOL_PROPERTY_PROTOCOL_INTERFACE = 1 << 8, +} indexstore_symbol_property_t; + +typedef enum { + INDEXSTORE_SYMBOL_LANG_C = 0, + INDEXSTORE_SYMBOL_LANG_OBJC = 1, + INDEXSTORE_SYMBOL_LANG_CXX = 2, + + INDEXSTORE_SYMBOL_LANG_SWIFT = 100, +} indexstore_symbol_language_t; + +typedef enum { + INDEXSTORE_SYMBOL_ROLE_DECLARATION = 1 << 0, + INDEXSTORE_SYMBOL_ROLE_DEFINITION = 1 << 1, + INDEXSTORE_SYMBOL_ROLE_REFERENCE = 1 << 2, + INDEXSTORE_SYMBOL_ROLE_READ = 1 << 3, + INDEXSTORE_SYMBOL_ROLE_WRITE = 1 << 4, + INDEXSTORE_SYMBOL_ROLE_CALL = 1 << 5, + INDEXSTORE_SYMBOL_ROLE_DYNAMIC = 1 << 6, + INDEXSTORE_SYMBOL_ROLE_ADDRESSOF = 1 << 7, + INDEXSTORE_SYMBOL_ROLE_IMPLICIT = 1 << 8, + INDEXSTORE_SYMBOL_ROLE_UNDEFINITION = 1 << 19, + + // Relation roles. + INDEXSTORE_SYMBOL_ROLE_REL_CHILDOF = 1 << 9, + INDEXSTORE_SYMBOL_ROLE_REL_BASEOF = 1 << 10, + INDEXSTORE_SYMBOL_ROLE_REL_OVERRIDEOF = 1 << 11, + INDEXSTORE_SYMBOL_ROLE_REL_RECEIVEDBY = 1 << 12, + INDEXSTORE_SYMBOL_ROLE_REL_CALLEDBY = 1 << 13, + INDEXSTORE_SYMBOL_ROLE_REL_EXTENDEDBY = 1 << 14, + INDEXSTORE_SYMBOL_ROLE_REL_ACCESSOROF = 1 << 15, + INDEXSTORE_SYMBOL_ROLE_REL_CONTAINEDBY = 1 << 16, + INDEXSTORE_SYMBOL_ROLE_REL_IBTYPEOF = 1 << 17, + INDEXSTORE_SYMBOL_ROLE_REL_SPECIALIZATIONOF = 1 << 18, +} indexstore_symbol_role_t; + +typedef void *indexstore_unit_dependency_t; +typedef void *indexstore_unit_include_t; + +typedef enum { + INDEXSTORE_UNIT_DEPENDENCY_UNIT = 1, + INDEXSTORE_UNIT_DEPENDENCY_RECORD = 2, + INDEXSTORE_UNIT_DEPENDENCY_FILE = 3, +} indexstore_unit_dependency_kind_t; + +typedef void *indexstore_symbol_relation_t; + +typedef void *indexstore_occurrence_t; + +typedef void *indexstore_record_reader_t; + +typedef void *indexstore_unit_reader_t; + +#if INDEXSTORE_HAS_BLOCKS +typedef void (^indexstore_unit_event_handler_t)(indexstore_unit_event_notification_t); +#endif + +typedef struct { + INDEXSTORE_PUBLIC const char * + (*error_get_description)(indexstore_error_t); + + INDEXSTORE_PUBLIC void + (*error_dispose)(indexstore_error_t); + + INDEXSTORE_PUBLIC unsigned + (*format_version)(void); + + INDEXSTORE_PUBLIC indexstore_t + (*store_create)(const char *store_path, indexstore_error_t *error); + + INDEXSTORE_PUBLIC void + (*store_dispose)(indexstore_t); + + #if INDEXSTORE_HAS_BLOCKS + INDEXSTORE_PUBLIC bool + (*store_units_apply)(indexstore_t, unsigned sorted, + bool(^applier)(indexstore_string_ref_t unit_name)); + #endif + + INDEXSTORE_PUBLIC bool + (*store_units_apply_f)(indexstore_t, unsigned sorted, + void *context, + bool(*applier)(void *context, indexstore_string_ref_t unit_name)); + + INDEXSTORE_PUBLIC size_t + (*unit_event_notification_get_events_count)(indexstore_unit_event_notification_t); + + INDEXSTORE_PUBLIC indexstore_unit_event_t + (*unit_event_notification_get_event)(indexstore_unit_event_notification_t, size_t index); + + INDEXSTORE_PUBLIC bool + (*unit_event_notification_is_initial)(indexstore_unit_event_notification_t); + + INDEXSTORE_PUBLIC indexstore_unit_event_kind_t + (*unit_event_get_kind)(indexstore_unit_event_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_event_get_unit_name)(indexstore_unit_event_t); + + INDEXSTORE_PUBLIC struct timespec + (*unit_event_get_modification_time)(indexstore_unit_event_t); + + + #if INDEXSTORE_HAS_BLOCKS + INDEXSTORE_PUBLIC void + (*store_set_unit_event_handler)(indexstore_t, + indexstore_unit_event_handler_t handler); + #endif + + INDEXSTORE_PUBLIC void + (*store_set_unit_event_handler_f)(indexstore_t, void *context, + void(*handler)(void *context, indexstore_unit_event_notification_t), + void(*finalizer)(void *context)); + + INDEXSTORE_PUBLIC bool + (*store_start_unit_event_listening)(indexstore_t, + indexstore_unit_event_listen_options_t *, + size_t listen_options_struct_size, + indexstore_error_t *error); + + INDEXSTORE_PUBLIC void + (*store_stop_unit_event_listening)(indexstore_t); + + INDEXSTORE_PUBLIC void + (*store_discard_unit)(indexstore_t, const char *unit_name); + + INDEXSTORE_PUBLIC void + (*store_discard_record)(indexstore_t, const char *record_name); + + INDEXSTORE_PUBLIC void + (*store_purge_stale_data)(indexstore_t); + + /// Determines the unit name from the \c output_path and writes it out in the + /// \c name_buf buffer. It doesn't write more than \c buf_size. + /// \returns the length of the name. If this is larger than \c buf_size, the + /// caller should call the function again with a buffer of the appropriate size. + INDEXSTORE_PUBLIC size_t + (*store_get_unit_name_from_output_path)(indexstore_t store, + const char *output_path, + char *name_buf, + size_t buf_size); + + /// \returns true if an error occurred, false otherwise. + INDEXSTORE_PUBLIC bool + (*store_get_unit_modification_time)(indexstore_t store, + const char *unit_name, + int64_t *seconds, + int64_t *nanoseconds, + indexstore_error_t *error); + + + INDEXSTORE_PUBLIC indexstore_symbol_language_t + (*symbol_get_language)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC indexstore_symbol_kind_t + (*symbol_get_kind)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC indexstore_symbol_subkind_t + (*symbol_get_subkind)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC uint64_t + (*symbol_get_properties)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC uint64_t + (*symbol_get_roles)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC uint64_t + (*symbol_get_related_roles)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*symbol_get_name)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*symbol_get_usr)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*symbol_get_codegen_name)(indexstore_symbol_t); + + INDEXSTORE_PUBLIC uint64_t + (*symbol_relation_get_roles)(indexstore_symbol_relation_t); + + INDEXSTORE_PUBLIC indexstore_symbol_t + (*symbol_relation_get_symbol)(indexstore_symbol_relation_t); + + INDEXSTORE_PUBLIC indexstore_symbol_t + (*occurrence_get_symbol)(indexstore_occurrence_t); + + #if INDEXSTORE_HAS_BLOCKS + INDEXSTORE_PUBLIC bool + (*occurrence_relations_apply)(indexstore_occurrence_t, + bool(^applier)(indexstore_symbol_relation_t symbol_rel)); + #endif + + INDEXSTORE_PUBLIC bool + (*occurrence_relations_apply_f)(indexstore_occurrence_t, + void *context, + bool(*applier)(void *context, indexstore_symbol_relation_t symbol_rel)); + + INDEXSTORE_PUBLIC uint64_t + (*occurrence_get_roles)(indexstore_occurrence_t); + + INDEXSTORE_PUBLIC void + (*occurrence_get_line_col)(indexstore_occurrence_t, + unsigned *line, unsigned *column); + + INDEXSTORE_PUBLIC indexstore_record_reader_t + (*record_reader_create)(indexstore_t store, const char *record_name, + indexstore_error_t *error); + + INDEXSTORE_PUBLIC void + (*record_reader_dispose)(indexstore_record_reader_t); + + #if INDEXSTORE_HAS_BLOCKS + /// Goes through the symbol data and passes symbols to \c receiver, for the + /// symbol data that \c filter returns true on. + /// + /// This allows allocating memory only for the record symbols that the caller is + /// interested in. + INDEXSTORE_PUBLIC bool + (*record_reader_search_symbols)(indexstore_record_reader_t, + bool(^filter)(indexstore_symbol_t symbol, bool *stop), + void(^receiver)(indexstore_symbol_t symbol)); + + /// \param nocache if true, avoids allocating memory for the symbols. + /// Useful when the caller does not intend to keep \c indexstore_record_reader_t + /// for more queries. + INDEXSTORE_PUBLIC bool + (*record_reader_symbols_apply)(indexstore_record_reader_t, + bool nocache, + bool(^applier)(indexstore_symbol_t symbol)); + + INDEXSTORE_PUBLIC bool + (*record_reader_occurrences_apply)(indexstore_record_reader_t, + bool(^applier)(indexstore_occurrence_t occur)); + + INDEXSTORE_PUBLIC bool + (*record_reader_occurrences_in_line_range_apply)(indexstore_record_reader_t, + unsigned line_start, + unsigned line_count, + bool(^applier)(indexstore_occurrence_t occur)); + + /// \param symbols if non-zero \c symbols_count, indicates the list of symbols + /// that we want to get occurrences for. An empty array indicates that we want + /// occurrences for all symbols. + /// \param related_symbols Same as \c symbols but for related symbols. + INDEXSTORE_PUBLIC bool + (*record_reader_occurrences_of_symbols_apply)(indexstore_record_reader_t, + indexstore_symbol_t *symbols, size_t symbols_count, + indexstore_symbol_t *related_symbols, size_t related_symbols_count, + bool(^applier)(indexstore_occurrence_t occur)); + #endif + + INDEXSTORE_PUBLIC bool + (*record_reader_search_symbols_f)(indexstore_record_reader_t, + void *filter_ctx, + bool(*filter)(void *filter_ctx, indexstore_symbol_t symbol, bool *stop), + void *receiver_ctx, + void(*receiver)(void *receiver_ctx, indexstore_symbol_t symbol)); + + INDEXSTORE_PUBLIC bool + (*record_reader_symbols_apply_f)(indexstore_record_reader_t, + bool nocache, + void *context, + bool(*applier)(void *context, indexstore_symbol_t symbol)); + + INDEXSTORE_PUBLIC bool + (*record_reader_occurrences_apply_f)(indexstore_record_reader_t, + void *context, + bool(*applier)(void *context, indexstore_occurrence_t occur)); + + INDEXSTORE_PUBLIC bool + (*record_reader_occurrences_in_line_range_apply_f)(indexstore_record_reader_t, + unsigned line_start, + unsigned line_count, + void *context, + bool(*applier)(void *context, indexstore_occurrence_t occur)); + + INDEXSTORE_PUBLIC bool + (*record_reader_occurrences_of_symbols_apply_f)(indexstore_record_reader_t, + indexstore_symbol_t *symbols, size_t symbols_count, + indexstore_symbol_t *related_symbols, size_t related_symbols_count, + void *context, + bool(*applier)(void *context, indexstore_occurrence_t occur)); + + + INDEXSTORE_PUBLIC indexstore_unit_reader_t + (*unit_reader_create)(indexstore_t store, const char *unit_name, + indexstore_error_t *error); + + void + (*unit_reader_dispose)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_provider_identifier)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_provider_version)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC void + (*unit_reader_get_modification_time)(indexstore_unit_reader_t, + int64_t *seconds, + int64_t *nanoseconds); + + INDEXSTORE_PUBLIC bool + (*unit_reader_is_system_unit)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC bool + (*unit_reader_is_module_unit)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC bool + (*unit_reader_is_debug_compilation)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC bool + (*unit_reader_has_main_file)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_main_file)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_module_name)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_working_dir)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_output_file)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_sysroot_path)(indexstore_unit_reader_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_reader_get_target)(indexstore_unit_reader_t); + + + INDEXSTORE_PUBLIC indexstore_unit_dependency_kind_t + (*unit_dependency_get_kind)(indexstore_unit_dependency_t); + + INDEXSTORE_PUBLIC bool + (*unit_dependency_is_system)(indexstore_unit_dependency_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_dependency_get_filepath)(indexstore_unit_dependency_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_dependency_get_modulename)(indexstore_unit_dependency_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_dependency_get_name)(indexstore_unit_dependency_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_include_get_source_path)(indexstore_unit_include_t); + + INDEXSTORE_PUBLIC indexstore_string_ref_t + (*unit_include_get_target_path)(indexstore_unit_include_t); + + INDEXSTORE_PUBLIC unsigned + (*unit_include_get_source_line)(indexstore_unit_include_t); + + #if INDEXSTORE_HAS_BLOCKS + INDEXSTORE_PUBLIC bool + (*unit_reader_dependencies_apply)(indexstore_unit_reader_t, + bool(^applier)(indexstore_unit_dependency_t)); + + INDEXSTORE_PUBLIC bool + (*unit_reader_includes_apply)(indexstore_unit_reader_t, + bool(^applier)(indexstore_unit_include_t)); + #endif + + INDEXSTORE_PUBLIC bool + (*unit_reader_dependencies_apply_f)(indexstore_unit_reader_t, + void *context, + bool(*applier)(void *context, indexstore_unit_dependency_t)); + + INDEXSTORE_PUBLIC bool + (*unit_reader_includes_apply_f)(indexstore_unit_reader_t, + void *context, + bool(*applier)(void *context, indexstore_unit_include_t)); + +} indexstore_functions_t; + + +INDEXSTORE_END_DECLS + +#endif diff --git a/Sources/clibc/include/module.modulemap b/Sources/clibc/include/module.modulemap index b276387c7a1..2f908650dde 100644 --- a/Sources/clibc/include/module.modulemap +++ b/Sources/clibc/include/module.modulemap @@ -1,4 +1,5 @@ module clibc { header "clibc.h" + header "indexstore_functions.h" export * }