From 910ff3f7f0a781fbb88c95bc957d1dcf63af91fa Mon Sep 17 00:00:00 2001 From: "Arnaud A. de Grandmaison" Date: Sat, 30 Jun 2012 11:28:04 +0000 Subject: [PATCH] [cindex.py] add CompilationDatabase support git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@159485 91177308-0d34-0410-b5e6-96231b3b80d8 --- bindings/python/clang/cindex.py | 153 ++++++++++++++++++ .../tests/cindex/INPUTS/compile_commands.json | 17 ++ bindings/python/tests/cindex/test_cdb.py | 81 ++++++++++ 3 files changed, 251 insertions(+) create mode 100644 bindings/python/tests/cindex/INPUTS/compile_commands.json create mode 100644 bindings/python/tests/cindex/test_cdb.py diff --git a/bindings/python/clang/cindex.py b/bindings/python/clang/cindex.py index 02cec2d796..a3da4217de 100644 --- a/bindings/python/clang/cindex.py +++ b/bindings/python/clang/cindex.py @@ -2096,6 +2096,120 @@ class FileInclusion(object): """True if the included file is the input file.""" return self.depth == 0 +class CompilationDatabaseError(Exception): + """Represents an error that occurred when working with a CompilationDatabase + + Each error is associated to an enumerated value, accessible under + e.cdb_error. Consumers can compare the value with one of the ERROR_ + constants in this class. + """ + + # An unknown error occured + ERROR_UNKNOWN = 0 + + # The database could not be loaded + ERROR_CANNOTLOADDATABASE = 1 + + def __init__(self, enumeration, message): + assert isinstance(enumeration, int) + + if enumeration > 1: + raise Exception("Encountered undefined CompilationDatabase error " + "constant: %d. Please file a bug to have this " + "value supported." % enumeration) + + self.cdb_error = enumeration + Exception.__init__(self, 'Error %d: %s' % (enumeration, message)) + +class CompileCommand(object): + """Represents the compile command used to build a file""" + def __init__(self, cmd, ccmds): + self.cmd = cmd + # Keep a reference to the originating CompileCommands + # to prevent garbage collection + self.ccmds = ccmds + + @property + def directory(self): + """Get the working directory for this CompileCommand""" + return CompileCommand_getDirectory(self.cmd).spelling + + @property + def arguments(self): + """ + Get an iterable object providing each argument in the + command line for the compiler invocation as a _CXString. + + Invariants : + - the first argument is the compiler executable + - the last argument is the file being compiled + """ + length = CompileCommand_getNumArgs(self.cmd) + for i in xrange(length): + yield CompileCommand_getArg(self.cmd, i) + +class CompileCommands(object): + """ + CompileCommands is an iterable object containing all CompileCommand + that can be used for building a specific file. + """ + def __init__(self, ccmds): + self.ccmds = ccmds + + def __del__(self): + CompileCommands_dispose(self.ccmds) + + def __len__(self): + return int(CompileCommands_getSize(self.ccmds)) + + def __getitem__(self, i): + cc = CompileCommands_getCommand(self.ccmds, i) + if cc is None: + raise IndexError + return CompileCommand(cc, self) + + @staticmethod + def from_result(res, fn, args): + if not res: + return None + return CompileCommands(res) + +class CompilationDatabase(ClangObject): + """ + The CompilationDatabase is a wrapper class around + clang::tooling::CompilationDatabase + + It enables querying how a specific source file can be built. + """ + + def __del__(self): + CompilationDatabase_dispose(self) + + @staticmethod + def from_result(res, fn, args): + if not res: + raise CompilationDatabaseError(0, + "CompilationDatabase loading failed") + return CompilationDatabase(res) + + @staticmethod + def fromDirectory(buildDir): + """Builds a CompilationDatabase from the database found in buildDir""" + errorCode = c_uint() + try: + cdb = CompilationDatabase_fromDirectory(buildDir, byref(errorCode)) + except CompilationDatabaseError as e: + raise CompilationDatabaseError(int(errorCode.value), + "CompilationDatabase loading failed") + return cdb + + def getCompileCommands(self, filename): + """ + Get an iterable object providing all the CompileCommands available to + build filename. Raise KeyError if filename is not found in the database. + """ + return CompilationDatabase_getCompileCommands(self, filename) + # Additional Functions and Types # String Functions @@ -2463,9 +2577,48 @@ _clang_getCompletionPriority = lib.clang_getCompletionPriority _clang_getCompletionPriority.argtypes = [c_void_p] _clang_getCompletionPriority.restype = c_int +# Compilation Database +CompilationDatabase_fromDirectory = lib.clang_tooling_CompilationDatabase_fromDirectory +CompilationDatabase_fromDirectory.argtypes = [c_char_p, POINTER(c_uint)] +CompilationDatabase_fromDirectory.restype = c_object_p +CompilationDatabase_fromDirectory.errcheck = CompilationDatabase.from_result + +CompilationDatabase_dispose = lib.clang_tooling_CompilationDatabase_dispose +CompilationDatabase_dispose.argtypes = [c_object_p] + +CompilationDatabase_getCompileCommands = lib.clang_tooling_CompilationDatabase_getCompileCommands +CompilationDatabase_getCompileCommands.argtypes = [c_object_p, c_char_p] +CompilationDatabase_getCompileCommands.restype = c_object_p +CompilationDatabase_getCompileCommands.errcheck = CompileCommands.from_result + +CompileCommands_dispose = lib.clang_tooling_CompileCommands_dispose +CompileCommands_dispose.argtypes = [c_object_p] + +CompileCommands_getSize = lib.clang_tooling_CompileCommands_getSize +CompileCommands_getSize.argtypes = [c_object_p] +CompileCommands_getSize.restype = c_uint + +CompileCommands_getCommand = lib.clang_tooling_CompileCommands_getCommand +CompileCommands_getCommand.argtypes = [c_object_p, c_uint] +CompileCommands_getCommand.restype = c_object_p + +CompileCommand_getDirectory = lib.clang_tooling_CompileCommand_getDirectory +CompileCommand_getDirectory.argtypes = [c_object_p] +CompileCommand_getDirectory.restype = _CXString + +CompileCommand_getNumArgs = lib.clang_tooling_CompileCommand_getNumArgs +CompileCommand_getNumArgs.argtypes = [c_object_p] +CompileCommand_getNumArgs.restype = c_uint + +CompileCommand_getArg = lib.clang_tooling_CompileCommand_getArg +CompileCommand_getArg.argtypes = [c_object_p, c_uint] +CompileCommand_getArg.restype = _CXString __all__ = [ 'CodeCompletionResults', + 'CompilationDatabase', + 'CompileCommands', + 'CompileCommand', 'CursorKind', 'Cursor', 'Diagnostic', diff --git a/bindings/python/tests/cindex/INPUTS/compile_commands.json b/bindings/python/tests/cindex/INPUTS/compile_commands.json new file mode 100644 index 0000000000..944150bf7b --- /dev/null +++ b/bindings/python/tests/cindex/INPUTS/compile_commands.json @@ -0,0 +1,17 @@ +[ +{ + "directory": "/home/john.doe/MyProject", + "command": "clang++ -o project.o -c /home/john.doe/MyProject/project.cpp", + "file": "/home/john.doe/MyProject/project.cpp" +}, +{ + "directory": "/home/john.doe/MyProjectA", + "command": "clang++ -o project2.o -c /home/john.doe/MyProject/project2.cpp", + "file": "/home/john.doe/MyProject/project2.cpp" +}, +{ + "directory": "/home/john.doe/MyProjectB", + "command": "clang++ -DFEATURE=1 -o project2-feature.o -c /home/john.doe/MyProject/project2.cpp", + "file": "/home/john.doe/MyProject/project2.cpp" +} +] diff --git a/bindings/python/tests/cindex/test_cdb.py b/bindings/python/tests/cindex/test_cdb.py new file mode 100644 index 0000000000..84ac1f87d5 --- /dev/null +++ b/bindings/python/tests/cindex/test_cdb.py @@ -0,0 +1,81 @@ +from clang.cindex import CompilationDatabase +from clang.cindex import CompilationDatabaseError +from clang.cindex import CompileCommands +from clang.cindex import CompileCommand +import os +import gc + +kInputsDir = os.path.join(os.path.dirname(__file__), 'INPUTS') + +def test_create_fail(): + """Check we fail loading a database with an assertion""" + path = os.path.dirname(__file__) + try: + cdb = CompilationDatabase.fromDirectory(path) + except CompilationDatabaseError as e: + assert e.cdb_error == CompilationDatabaseError.ERROR_CANNOTLOADDATABASE + else: + assert False + +def test_create(): + """Check we can load a compilation database""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + +def test_lookup_fail(): + """Check an assertion is raised when file lookup failed""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + assert cdb.getCompileCommands('file_do_not_exist.cpp') == None + +def test_lookup_succeed(): + """Check we get some results if the file exists in the db""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + cmds = cdb.getCompileCommands('/home/john.doe/MyProject/project.cpp') + assert len(cmds) != 0 + +def test_1_compilecommand(): + """Check file with single compile command""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + cmds = cdb.getCompileCommands('/home/john.doe/MyProject/project.cpp') + assert len(cmds) == 1 + assert cmds[0].directory == '/home/john.doe/MyProject' + expected = [ 'clang++', '-o', 'project.o', '-c', + '/home/john.doe/MyProject/project.cpp'] + for arg, exp in zip(cmds[0].arguments, expected): + assert arg.spelling == exp + +def test_2_compilecommand(): + """Check file with 2 compile commands""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + cmds = cdb.getCompileCommands('/home/john.doe/MyProject/project2.cpp') + assert len(cmds) == 2 + expected = [ + { 'wd': '/home/john.doe/MyProjectA', + 'line': ['clang++', '-o', 'project2.o', '-c', + '/home/john.doe/MyProject/project2.cpp']}, + { 'wd': '/home/john.doe/MyProjectB', + 'line': ['clang++', '-DFEATURE=1', '-o', 'project2-feature.o', '-c', + '/home/john.doe/MyProject/project2.cpp']} + ] + for i in range(len(cmds)): + assert cmds[i].directory == expected[i]['wd'] + for arg, exp in zip(cmds[i].arguments, expected[i]['line']): + assert arg.spelling == exp + +def test_compilationDB_references(): + """Ensure CompilationsCommands are independent of the database""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + cmds = cdb.getCompileCommands('/home/john.doe/MyProject/project.cpp') + del cdb + gc.collect() + workingdir = cmds[0].directory + +def test_compilationCommands_references(): + """Ensure CompilationsCommand keeps a reference to CompilationCommands""" + cdb = CompilationDatabase.fromDirectory(kInputsDir) + cmds = cdb.getCompileCommands('/home/john.doe/MyProject/project.cpp') + del cdb + cmd0 = cmds[0] + del cmds + gc.collect() + workingdir = cmd0.directory + -- 2.40.0