From b9a75365c8d4385f4a92f7881e08f8e79af74053 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Mon, 25 Mar 2019 15:27:47 +0900 Subject: [PATCH 1/9] Implement the `CompilationDatabase` class with the `CompileCommand` struct. - The TranslationUnit class can be initialized using the compile command. - Test cases are added. --- Sources/Clang/CompilationDatabase.swift | 123 ++++++++++++++++++++++++ Sources/Clang/TranslationUnit.swift | 21 +++- Tests/ClangTests/ClangTests.swift | 101 ++++++++++++++++++- 3 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 Sources/Clang/CompilationDatabase.swift diff --git a/Sources/Clang/CompilationDatabase.swift b/Sources/Clang/CompilationDatabase.swift new file mode 100644 index 0000000..66a7e47 --- /dev/null +++ b/Sources/Clang/CompilationDatabase.swift @@ -0,0 +1,123 @@ +#if SWIFT_PACKAGE +import cclang +#endif + +import Foundation + +/// Error code for Compilation Database +/// +/// - noError: no error. +/// - canNotLoadDatabase: failed to load database. +public enum CompilationDatabaseError: Error { + + case canNotLoadDatabase + + init?(clang: CXCompilationDatabase_Error) { + switch clang { + case CXCompilationDatabase_CanNotLoadDatabase: + self = .canNotLoadDatabase + default: + return nil + } + } +} + +/// Contains the results of a search in the compilation database. +public struct CompileCommand: Equatable { + + // the working directory where the CompileCommand was executed from. + let directory: String + + // the filename associated with the CompileCommand. + let filename: String + + // the array of argument value in the compiler invocations. + let arguments: [String] + + fileprivate init(command: CXCompileCommand) { + // get directory and filename + self.directory = clang_CompileCommand_getDirectory(command).asSwift() + self.filename = clang_CompileCommand_getFilename(command).asSwift() + + // get arguments + let args = clang_CompileCommand_getNumArgs(command) + self.arguments = (0 ..< args).map { i in + return clang_CompileCommand_getArg(command, i).asSwift() + } + + // MARK: - unsupported api by cclang yet? + // let mappedSourcesCount = clang_CompileCommand_getNumMappedSources(command) + // (0 ..< mappedSourcesCount).forEach { i in + // let path = clang_CompileCommand_getMappedSourcePath(command, UInt32(i)).asSwift() + // let content = clang_CompileCommand_getMappedSourceContent(command, UInt32(i)).asSwift() + // } + } +} + +/// A compilation database holds all information used to compile files in a project. +public class CompilationDatabase { + let db: CXCompilationDatabase + private let owned: Bool + + public init(directory: String) throws { + var err = CXCompilationDatabase_NoError + + // initialize compilation db + self.db = clang_CompilationDatabase_fromDirectory(directory, &err) + if let error = CompilationDatabaseError(clang: err) { + throw error + } + + self.owned = true + } + + /// the array of all compile command in the compilation database. + public lazy private(set) var compileCommands: [CompileCommand] = { + guard let commands = clang_CompilationDatabase_getAllCompileCommands(self.db) else { + return [] + } + // the compileCommands needs to be disposed. + defer { + clang_CompileCommands_dispose(commands) + } + + let count = clang_CompileCommands_getSize(commands) + return (0 ..< count).map { i in + // get compile command + guard let cmd = clang_CompileCommands_getCommand(commands, UInt32(i)) else { + fatalError("Failed to get compile command for an index \(i)") + } + return CompileCommand(command: cmd) + } + }() + + + /// Returns the array of compile command for a file. + /// + /// - Parameter filename: a filename containing directory. + /// - Returns: the array of compile command. + public func compileCommands(forFile filename: String) -> [CompileCommand] { + guard let commands = clang_CompilationDatabase_getCompileCommands(self.db, filename) else { + fatalError("failed to load compileCommands for \(filename).") + } + // the compileCommands needs to be disposed. + defer { + clang_CompileCommands_dispose(commands) + } + + let size = clang_CompileCommands_getSize(commands) + + return (0 ..< size).map { i in + guard let cmd = clang_CompileCommands_getCommand(commands, UInt32(i)) else { + fatalError("Failed to get compile command for an index \(i)") + } + return CompileCommand(command: cmd) + } + } + + deinit { + if self.owned { + clang_CompilationDatabase_dispose(self.db) + } + } +} diff --git a/Sources/Clang/TranslationUnit.swift b/Sources/Clang/TranslationUnit.swift index ba4d73b..ace52e0 100644 --- a/Sources/Clang/TranslationUnit.swift +++ b/Sources/Clang/TranslationUnit.swift @@ -219,7 +219,26 @@ public class TranslationUnit { index: index, commandLineArgs: args, options: options) - } + } + + /// Creates a `TranslationUnit` using the CompileCommand. + /// + /// - Parameters: + /// - command: The compile command initialized by the CompilationDatabase. + /// (load data from the `compile_commands.json` file generated by CMake.) + /// - index: The index (optional, will use a default index if not + /// provided) + /// - options: Options for how to handle the parsed file + /// - throws: `ClangError` if the translation unit could not be created + /// successfully. + public convenience init(compileCommand command: CompileCommand, + index: Index = Index(), + options: TranslationUnitOptions = []) throws { + try self.init(filename: command.filename, + index: index, + commandLineArgs: command.arguments, + options: options) + } /// Creates a `TranslationUnit` from an AST file generated by `-emit-ast`. /// diff --git a/Tests/ClangTests/ClangTests.swift b/Tests/ClangTests/ClangTests.swift index 8cab2d0..dfb800d 100644 --- a/Tests/ClangTests/ClangTests.swift +++ b/Tests/ClangTests/ClangTests.swift @@ -168,7 +168,7 @@ class ClangTests: XCTestCase { XCTFail("\(error)") } } - + func testParsingWithUnsavedFile() { do { let filename = "input_tests/unsaved-file.c" @@ -248,6 +248,101 @@ class ClangTests: XCTestCase { XCTFail("\(error)") } } + + // URL for a "test_input" folder. + var inputTestUrl: URL { + let projectRoot = URL(fileURLWithPath: #file).deletingLastPathComponent() + return projectRoot.appendingPathComponent("../../input_tests", isDirectory: true).standardized + } + + // URL for a "test_input/build" folder. + var buildUrl: URL { + return inputTestUrl.appendingPathComponent("build", isDirectory: true) + } + + func testInitCompilationDB() { + do { + let db = try CompilationDatabase(directory: inputTestUrl.path + "/build") + XCTAssertNotNil(db) + XCTAssertEqual(db.compileCommands.count, 7) + + } catch { + XCTFail("\(error)") + } + } + + func testCompileCommand() { + do { + // intialize CompilationDatabase. + let db = try CompilationDatabase(directory: buildUrl.path) + XCTAssertNotNil(db) + + // test first compileCommand + let cmd = db.compileCommands[0] + XCTAssertEqual(cmd.directory, buildUrl.path) + XCTAssertGreaterThan(cmd.arguments.count, 0) + + // test all compileCommands + let filenames = db.compileCommands.map { URL(fileURLWithPath: $0.filename) } + + let expectation: Set = [ + inputTestUrl.appendingPathComponent("inclusion.c"), + inputTestUrl.appendingPathComponent("index-action.c"), + inputTestUrl.appendingPathComponent("init-ast.c"), + inputTestUrl.appendingPathComponent("is-from-main-file.c"), + inputTestUrl.appendingPathComponent("locations.c"), + inputTestUrl.appendingPathComponent("reparse.c"), + inputTestUrl.appendingPathComponent("unsaved-file.c"), + ] + XCTAssertEqual(Set(filenames), expectation) + } catch { + XCTFail("\(error)") + } + } + + func testCompileCommandForFile() { + do { + // intialize CompilationDatabase. + let db = try CompilationDatabase(directory: buildUrl.path) + XCTAssertNotNil(db) + + let inclusionFile = inputTestUrl.appendingPathComponent("inclusion.c") + + // test compileCommand for file `inclusion.c` + let cmds = db.compileCommands(forFile: inclusionFile.path) + XCTAssertEqual(cmds.count, 1) + XCTAssertEqual(cmds[0].filename, inclusionFile.path) + XCTAssertEqual(cmds[0].directory, buildUrl.path) + XCTAssertGreaterThan(cmds[0].arguments.count, 0) + } catch { + XCTFail("\(error)") + } + } + + func testInitTranslationUnitUsingCompileCommand() { + do { + // intialize CompilationDatabase. + let filename = inputTestUrl.path + "/locations.c" + let db = try CompilationDatabase(directory: buildUrl.path) + + // get first compile command and initialize TranslationUnit using it. + let cmd = db.compileCommands(forFile: filename).first! + let unit = try TranslationUnit(compileCommand: cmd) + + // verify. + let file = unit.getFile(for: unit.spelling)! + let start = SourceLocation(translationUnit: unit, file: file, offset: 19) + let end = SourceLocation(translationUnit: unit, file: file, offset: 59) + let range = SourceRange(start: start, end: end) + + XCTAssertEqual( + unit.tokens(in: range).map { $0.spelling(in: unit) }, + ["int", "a", "=", "1", ";", "int", "b", "=", "1", ";", "int", "c", "=", + "a", "+", "b", ";"] + ) + } catch { + } + } static var allTests : [(String, (ClangTests) -> () throws -> Void)] { return [ @@ -262,6 +357,10 @@ class ClangTests: XCTestCase { ("testIsFromMainFile", testIsFromMainFile), ("testVisitInclusion", testVisitInclusion), ("testGetFile", testGetFile), + ("testInitCompilationDB", testInitCompilationDB), + ("testCompileCommand", testCompileCommand), + ("testCompileCommandForFile", testCompileCommandForFile), + ("testInitTranslationUnitUsingCompileCommand", testInitTranslationUnitUsingCompileCommand) ] } } From d629dbb34962ea5a525fcd365d96361af62ab210 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Mon, 25 Mar 2019 16:38:27 +0900 Subject: [PATCH 2/9] Change test file build directory to `.build/build.input_tests`. --- Tests/ClangTests/ClangTests.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Tests/ClangTests/ClangTests.swift b/Tests/ClangTests/ClangTests.swift index dfb800d..b797495 100644 --- a/Tests/ClangTests/ClangTests.swift +++ b/Tests/ClangTests/ClangTests.swift @@ -249,20 +249,24 @@ class ClangTests: XCTestCase { } } - // URL for a "test_input" folder. + // ${projectRoot}/ folder URL. + var projectRoot: URL { + return URL(fileURLWithPath: #file).appendingPathComponent("../../../", isDirectory: true).standardized + } + + // ${projectRoot}/input_tests folder URL. var inputTestUrl: URL { - let projectRoot = URL(fileURLWithPath: #file).deletingLastPathComponent() - return projectRoot.appendingPathComponent("../../input_tests", isDirectory: true).standardized + return projectRoot.appendingPathComponent("input_tests", isDirectory: true) } - // URL for a "test_input/build" folder. + // ${projectRoot}/.build/build.input_tests folder URL var buildUrl: URL { - return inputTestUrl.appendingPathComponent("build", isDirectory: true) + return projectRoot.appendingPathComponent(".build/build.input_tests", isDirectory: true) } func testInitCompilationDB() { do { - let db = try CompilationDatabase(directory: inputTestUrl.path + "/build") + let db = try CompilationDatabase(directory: buildUrl.path) XCTAssertNotNil(db) XCTAssertEqual(db.compileCommands.count, 7) From 086b531be9b0ce9a117bd390c0ae9627184fb891 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Mon, 25 Mar 2019 16:39:23 +0900 Subject: [PATCH 3/9] Add to check `compile_commands.json` file existence. --- Sources/Clang/CompilationDatabase.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/Clang/CompilationDatabase.swift b/Sources/Clang/CompilationDatabase.swift index 66a7e47..0afb121 100644 --- a/Sources/Clang/CompilationDatabase.swift +++ b/Sources/Clang/CompilationDatabase.swift @@ -62,6 +62,13 @@ public class CompilationDatabase { public init(directory: String) throws { var err = CXCompilationDatabase_NoError + // check `compile_commands.json` file existence in directory folder. + let cmdFile = URL(fileURLWithPath: directory, isDirectory: true) + .appendingPathComponent("compile_commands.json").path + guard FileManager.default.fileExists(atPath: cmdFile) else { + throw CompilationDatabaseError.canNotLoadDatabase + } + // initialize compilation db self.db = clang_CompilationDatabase_fromDirectory(directory, &err) if let error = CompilationDatabaseError(clang: err) { From 6f85c56f3ae9e6a44d5f22a3f5c1de878ae46967 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Mon, 25 Mar 2019 16:52:54 +0900 Subject: [PATCH 4/9] Add make script with CMakeLists.txt file to generate the `compile_commands.json` file. --- input_tests/CMakeLists.txt | 24 +++++++++ utils/make-compile_commands.swift | 88 +++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 input_tests/CMakeLists.txt create mode 100644 utils/make-compile_commands.swift diff --git a/input_tests/CMakeLists.txt b/input_tests/CMakeLists.txt new file mode 100644 index 0000000..22b5906 --- /dev/null +++ b/input_tests/CMakeLists.txt @@ -0,0 +1,24 @@ +project(InputTests) +cmake_minimum_required(VERSION 3.3) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_library(InputTests + inclusion.c index-action.c init-ast.c is-from-main-file.c locations.c reparse.c unsaved-file.c + inclusion-header.h +) + +target_include_directories(InputTests +PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +PRIVATE +) + +# target_compile_features(InputTests +# PUBLIC +# PRIVATE +# ) +# target_link_libraries(InputTests +# PUBLIC +# PRIVATE +# ) \ No newline at end of file diff --git a/utils/make-compile_commands.swift b/utils/make-compile_commands.swift new file mode 100644 index 0000000..5d3b245 --- /dev/null +++ b/utils/make-compile_commands.swift @@ -0,0 +1,88 @@ +#!/usr/bin/env swift +import Foundation + +#if os(Linux) + typealias Process = Task +#elseif os(macOS) +#endif + +/// Runs the specified program at the provided path. +/// - parameter path: The full path of the executable you +/// wish to run. +/// - parameter args: The arguments you wish to pass to the +/// process. +/// - returns: The standard output of the process, or nil if it was empty. +func run(_ path: String, args: [String] = []) -> String? { + print("Running \(path) \(args.joined(separator: " "))...") + let pipe = Pipe() + let process = Process() + process.launchPath = path + process.arguments = args + process.standardOutput = pipe + process.launch() + process.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + guard let result = String(data: data, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines), + !result.isEmpty else { return nil } + return result +} + +/// Finds the location of the provided binary on your system. +func which(_ name: String) -> String? { + return run("/usr/bin/which", args: [name]) +} + +extension String: Error { + /// Replaces all occurrences of characters in the provided set with + /// the provided string. + func replacing(charactersIn characterSet: CharacterSet, + with separator: String) -> String { + let components = self.components(separatedBy: characterSet) + return components.joined(separator: separator) + } +} + +func build() throws { + let projectRoot = URL(fileURLWithPath: #file) + .deletingLastPathComponent() + .deletingLastPathComponent() + + // ${project_root}/.build/build.input_tests url + let buildURL = projectRoot.appendingPathComponent(".build/build.input_tests", isDirectory: true) + // print(buildURL.path) + + let sourceURL = projectRoot.appendingPathComponent("input_tests", isDirectory: true) + // print(sourceURL.path) + + // make `${projectRoot}/.build.input_tests` folder if it doesn't exist. + if !FileManager.default.fileExists(atPath: buildURL.path) { + try FileManager.default.createDirectory(at: buildURL, withIntermediateDirectories: true) + } + + // run `CMake ../` command at `input_tests/build` folder. + // -S = Explicitly specify a source directory. + // -B = Explicitly specify a build directory. + guard let cmake = which("cmake") else { return } + let args: [String] = [ + "-S", sourceURL.path, + "-B", buildURL.path + ] + + // run `cmake -S ${sourcePath} -B {buildPath}` command. + _ = run(cmake, args: args) + print("\nThe `compile_commands.json` is generated at \(buildURL.path)") +} + +do { + try build() +} catch { +#if os(Linux) + // FIXME: Printing the thrown error that here crashes on Linux. + print("Unexpected error occured while writing the config file. Check permissions and try again.") +#else + print("error: \(error)") +#endif + exit(-1) +} From 48aa592bda0cd931a593e5e5e2a57932776ee5b3 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Mon, 25 Mar 2019 18:02:24 +0900 Subject: [PATCH 5/9] add utils/make-compile_commands.swift script to travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5df3edb..f7147d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ matrix: - export PATH=/usr/local/opt/llvm/bin:"${PATH}" - brew install llvm - sudo swift utils/make-pkgconfig.swift + - sudo swift utils/make-compile_commands.swift script: - swift test notifications: From d2fb1baec12a21ae62b64421559fbb1e79a458f7 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Mon, 25 Mar 2019 21:13:33 +0900 Subject: [PATCH 6/9] fix cmake build error. --- .travis.yml | 2 +- utils/make-compile_commands.swift | 53 +++++++++++++++++-------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7147d2..c893d82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: - export PATH=/usr/local/opt/llvm/bin:"${PATH}" - brew install llvm - sudo swift utils/make-pkgconfig.swift - - sudo swift utils/make-compile_commands.swift + - swift utils/make-compile_commands.swift script: - swift test notifications: diff --git a/utils/make-compile_commands.swift b/utils/make-compile_commands.swift index 5d3b245..d5dacc2 100644 --- a/utils/make-compile_commands.swift +++ b/utils/make-compile_commands.swift @@ -6,19 +6,29 @@ import Foundation #elseif os(macOS) #endif -/// Runs the specified program at the provided path. -/// - parameter path: The full path of the executable you -/// wish to run. -/// - parameter args: The arguments you wish to pass to the -/// process. -/// - returns: The standard output of the process, or nil if it was empty. -func run(_ path: String, args: [String] = []) -> String? { - print("Running \(path) \(args.joined(separator: " "))...") + /// Runs the specified program at the provided path. + /// + /// - Parameters: + /// - exec: The full path of the executable binary. + /// - dir: The process working directory. If this is nil, the current directory will be used. + /// - args: The arguments you wish to pass to the process. + /// - Returns: The standard output of the process, or nil if it was empty. +func run(exec: String, at dir: URL? = nil, args: [String] = []) -> String? { let pipe = Pipe() let process = Process() - process.launchPath = path + + process.executableURL = URL(fileURLWithPath: exec) process.arguments = args process.standardOutput = pipe + + if let dir = dir { + print("Running \(dir.path) \(exec) \(args.joined(separator: " "))...") + process.currentDirectoryURL = dir + } else { + print("Running \(args.joined(separator: " "))...") + } + + process.launch() process.waitUntilExit() @@ -31,7 +41,7 @@ func run(_ path: String, args: [String] = []) -> String? { /// Finds the location of the provided binary on your system. func which(_ name: String) -> String? { - return run("/usr/bin/which", args: [name]) + return run(exec: "/usr/bin/which", args: [name]) } extension String: Error { @@ -51,28 +61,23 @@ func build() throws { // ${project_root}/.build/build.input_tests url let buildURL = projectRoot.appendingPathComponent(".build/build.input_tests", isDirectory: true) - // print(buildURL.path) - let sourceURL = projectRoot.appendingPathComponent("input_tests", isDirectory: true) - // print(sourceURL.path) + + print("project root \(projectRoot.path)") + print("build folder \(buildURL.path)") + // print(sourceURL.path) // make `${projectRoot}/.build.input_tests` folder if it doesn't exist. if !FileManager.default.fileExists(atPath: buildURL.path) { try FileManager.default.createDirectory(at: buildURL, withIntermediateDirectories: true) } - // run `CMake ../` command at `input_tests/build` folder. - // -S = Explicitly specify a source directory. - // -B = Explicitly specify a build directory. + // get `cmake` command path. guard let cmake = which("cmake") else { return } - let args: [String] = [ - "-S", sourceURL.path, - "-B", buildURL.path - ] - - // run `cmake -S ${sourcePath} -B {buildPath}` command. - _ = run(cmake, args: args) - print("\nThe `compile_commands.json` is generated at \(buildURL.path)") + + // run `cd {buildPath}; cmake ${sourcePath}` command. + let results = run(exec: cmake, at: buildURL, args: [sourceURL.path]) + print(results!) } do { From b205b8154d3733ff2b1432305c33f864d60a4b61 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Tue, 26 Mar 2019 12:57:03 +0900 Subject: [PATCH 7/9] mark variables of CompileCommand as public. --- Sources/Clang/CompilationDatabase.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Clang/CompilationDatabase.swift b/Sources/Clang/CompilationDatabase.swift index 0afb121..00eb7f3 100644 --- a/Sources/Clang/CompilationDatabase.swift +++ b/Sources/Clang/CompilationDatabase.swift @@ -26,13 +26,13 @@ public enum CompilationDatabaseError: Error { public struct CompileCommand: Equatable { // the working directory where the CompileCommand was executed from. - let directory: String + public let directory: String // the filename associated with the CompileCommand. - let filename: String + public let filename: String // the array of argument value in the compiler invocations. - let arguments: [String] + public let arguments: [String] fileprivate init(command: CXCompileCommand) { // get directory and filename From 3b378fcc3205e59446ebe9c138457b501285d127 Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Tue, 26 Mar 2019 13:27:28 +0900 Subject: [PATCH 8/9] add missing try-catch fail case. --- Tests/ClangTests/ClangTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/ClangTests/ClangTests.swift b/Tests/ClangTests/ClangTests.swift index b797495..7d9b520 100644 --- a/Tests/ClangTests/ClangTests.swift +++ b/Tests/ClangTests/ClangTests.swift @@ -345,6 +345,7 @@ class ClangTests: XCTestCase { "a", "+", "b", ";"] ) } catch { + XCTFail("\(error)") } } From 1ae72d8f915f073090c8bb787741a5e915531e4c Mon Sep 17 00:00:00 2001 From: JuHwang Kim Date: Tue, 26 Mar 2019 14:38:27 +0900 Subject: [PATCH 9/9] Fix TranslationUnit initializer using the CompileCommand. --- Sources/Clang/TranslationUnit.swift | 44 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/Sources/Clang/TranslationUnit.swift b/Sources/Clang/TranslationUnit.swift index ace52e0..d8816d2 100644 --- a/Sources/Clang/TranslationUnit.swift +++ b/Sources/Clang/TranslationUnit.swift @@ -221,24 +221,34 @@ public class TranslationUnit { options: options) } - /// Creates a `TranslationUnit` using the CompileCommand. - /// - /// - Parameters: - /// - command: The compile command initialized by the CompilationDatabase. - /// (load data from the `compile_commands.json` file generated by CMake.) - /// - index: The index (optional, will use a default index if not - /// provided) - /// - options: Options for how to handle the parsed file - /// - throws: `ClangError` if the translation unit could not be created - /// successfully. - public convenience init(compileCommand command: CompileCommand, - index: Index = Index(), - options: TranslationUnitOptions = []) throws { - try self.init(filename: command.filename, - index: index, - commandLineArgs: command.arguments, - options: options) + /// Creates a `TranslationUnit` using the CompileCommand. + /// the name of the source file is expected to reside in the command line arguments. + /// + /// - Parameters: + /// - command: The compile command initialized by the CompilationDatabase. + /// (load data from the `compile_commands.json` file generated by CMake.) + /// - index: The index (optional, will use a default index if not + /// provided) + /// - options: Options for how to handle the parsed file + /// - throws: `ClangError` if the translation unit could not be created + /// successfully. + public init(compileCommand command: CompileCommand, + index: Index = Index(), + options: TranslationUnitOptions = [], + unsavedFiles: [UnsavedFile] = []) throws { + self.clang = try command.arguments.withUnsafeCStringBuffer { argC in + var cxUnsavedFiles = unsavedFiles.map { $0.clang } + let unit: CXTranslationUnit? = clang_createTranslationUnitFromSourceFile(index.clang, + nil, + Int32(argC.count), argC.baseAddress, + UInt32(cxUnsavedFiles.count), &cxUnsavedFiles) + guard unit != nil else { + throw ClangError.astRead + } + return unit! } + self.owned = true + } /// Creates a `TranslationUnit` from an AST file generated by `-emit-ast`. ///