]> granicus.if.org Git - clang/commitdiff
cindex/Python: Add TranslationUnit.get_includes, patch by Andrew Sutton!
authorDaniel Dunbar <daniel@zuster.org>
Sat, 13 Feb 2010 18:33:18 +0000 (18:33 +0000)
committerDaniel Dunbar <daniel@zuster.org>
Sat, 13 Feb 2010 18:33:18 +0000 (18:33 +0000)
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@96106 91177308-0d34-0410-b5e6-96231b3b80d8

bindings/python/clang/cindex.py
bindings/python/examples/cindex/cindex-includes.py [new file with mode: 0644]
bindings/python/tests/cindex/INPUTS/header1.h [new file with mode: 0644]
bindings/python/tests/cindex/INPUTS/header2.h [new file with mode: 0644]
bindings/python/tests/cindex/INPUTS/header3.h [new file with mode: 0644]
bindings/python/tests/cindex/INPUTS/include.cpp [new file with mode: 0644]
bindings/python/tests/cindex/test_translation_unit.py

index b6383fb228664e78968e5f8825664871e71b189f..c37c69b79b389a01c2778641d2b04570e0ef180b 100644 (file)
@@ -719,6 +719,25 @@ class TranslationUnit(ClangObject):
         """Get the original translation unit source file name."""
         return TranslationUnit_spelling(self)
 
+    def get_includes(self):
+        """
+        Return an iterable sequence of FileInclusion objects that describe the
+        sequence of inclusions in a translation unit. The first object in
+        this sequence is always the input file. Note that this method will not
+        recursively iterate over header files included through precompiled
+        headers.
+        """
+        def visitor(fobj, lptr, depth, includes):
+            loc = lptr.contents
+            includes.append(FileInclusion(loc.file, File(fobj), loc, depth))
+
+        # Automatically adapt CIndex/ctype pointers to python objects
+        includes = []
+        TranslationUnit_includes(self,
+                                 TranslationUnit_includes_callback(visitor),
+                                 includes)
+        return iter(includes)
+
 class File(ClangObject):
     """
     The File class represents a particular source file that is part of a
@@ -735,6 +754,26 @@ class File(ClangObject):
         """Return the last modification time of the file."""
         return File_time(self)
 
+class FileInclusion(object):
+    """
+    The FileInclusion class represents the inclusion of one source file by
+    another via a '#include' directive or as the input file for the translation
+    unit. This class provides information about the included file, the including
+    file, the location of the '#include' directive and the depth of the included
+    file in the stack. Note that the input file has depth 0.
+    """
+
+    def __init__(self, src, tgt, loc, depth):
+        self.source = src
+        self.include = tgt
+        self.location = loc
+        self.depth = depth
+
+    @property
+    def is_input_file(self):
+        """True if the included file is the input file."""
+        return self.depth == 0
+
 # Additional Functions and Types
 
 # String Functions
@@ -870,6 +909,15 @@ TranslationUnit_spelling.errcheck = _CXString.from_result
 TranslationUnit_dispose = lib.clang_disposeTranslationUnit
 TranslationUnit_dispose.argtypes = [TranslationUnit]
 
+TranslationUnit_includes_callback = CFUNCTYPE(None,
+                                              c_object_p,
+                                              POINTER(SourceLocation),
+                                              c_uint, py_object)
+TranslationUnit_includes = lib.clang_getInclusions
+TranslationUnit_includes.argtypes = [TranslationUnit,
+                                     TranslationUnit_includes_callback,
+                                     py_object]
+
 # File Functions
 File_name = lib.clang_getFileName
 File_name.argtypes = [File]
diff --git a/bindings/python/examples/cindex/cindex-includes.py b/bindings/python/examples/cindex/cindex-includes.py
new file mode 100644 (file)
index 0000000..2220552
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+#===- cindex-includes.py - cindex/Python Inclusion Graph -----*- python -*--===#
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+"""
+A simple command line tool for dumping a Graphviz description (dot) that
+describes include dependencies.
+"""
+
+def main():
+    import sys
+    from clang.cindex import Index
+
+    # FIXME: Allow the user to pass command line options to clang so that
+    # we can use -D and -U.
+    from optparse import OptionParser, OptionGroup
+
+    parser = OptionParser("usage: %prog [options] {filename} [clang-args*]")
+    parser.disable_interspersed_args()
+    (opts, args) = parser.parse_args()
+    if len(args) == 0:
+        parser.error('invalid number arguments')
+
+    # FIXME: Add an output file option
+    out = sys.stdout
+
+    input_path = args.pop(0)
+
+
+    index = Index.create()
+    tu = index.parse(input_path, args)
+    if not tu:
+        parser.error("unable to load input")
+
+    # A helper function for generating the node name.
+    def name(f):
+        return "\"" + f.name + "\""
+
+    # Generate the include graph
+    out.write("digraph G {\n")
+    for i in tu.get_includes():
+        line = "  ";
+        if i.is_input_file:
+            # Always write the input file as a node just in case it doesn't
+            # actually include anything. This would generate a 1 node graph.
+            line += name(i.include)
+        else:
+            line += name(i.source) + "->" + name(i.include)
+        line += "\n";
+        out.write(line)
+    out.write("}\n")
+
+if __name__ == '__main__':
+    main()
+
diff --git a/bindings/python/tests/cindex/INPUTS/header1.h b/bindings/python/tests/cindex/INPUTS/header1.h
new file mode 100644 (file)
index 0000000..b4eacbe
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef HEADER1
+#define HEADER1
+
+#include "header3.h"
+
+#endif
diff --git a/bindings/python/tests/cindex/INPUTS/header2.h b/bindings/python/tests/cindex/INPUTS/header2.h
new file mode 100644 (file)
index 0000000..c4eddc0
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef HEADER2
+#define HEADER2
+
+#include "header3.h"
+
+#endif
diff --git a/bindings/python/tests/cindex/INPUTS/header3.h b/bindings/python/tests/cindex/INPUTS/header3.h
new file mode 100644 (file)
index 0000000..6dca764
--- /dev/null
@@ -0,0 +1,3 @@
+// Not a guarded header!
+
+void f();
diff --git a/bindings/python/tests/cindex/INPUTS/include.cpp b/bindings/python/tests/cindex/INPUTS/include.cpp
new file mode 100644 (file)
index 0000000..60cfdaa
--- /dev/null
@@ -0,0 +1,5 @@
+#include "header1.h"
+#include "header2.h"
+#include "header1.h"
+
+int main() { }
index ec12e689d9fbc2604c47fde907b435be89685a4a..3c05c3f06af4415c3a70d39ccfa1d81314f308e3 100644 (file)
@@ -49,3 +49,25 @@ def test_unsaved_files_2():
             ('fake.c', StringIO.StringIO('int x;'))])
     spellings = [c.spelling for c in tu.cursor.get_children()]
     assert spellings[-1] == 'x'
+
+
+def test_includes():
+    def eq(expected, actual):
+        if not actual.is_input_file:
+            return expected[0] == actual.source.name and \
+                   expected[1] == actual.include.name
+        else:
+            return expected[1] == actual.include.name
+
+    src = os.path.join(kInputsDir, 'include.cpp')
+    h1 = os.path.join(kInputsDir, "header1.h")
+    h2 = os.path.join(kInputsDir, "header2.h")
+    h3 = os.path.join(kInputsDir, "header3.h")
+    inc = [(None, src), (src, h1), (h1, h3), (src, h2), (h2, h3)]
+
+    index = Index.create()
+    tu = index.parse(src)
+    for i in zip(inc, tu.get_includes()):
+        assert eq(i[0], i[1])
+
+