cindex/Python: Add TranslationUnit.get_includes, patch by Andrew Sutton!

llvm-svn: 96106
This commit is contained in:
Daniel Dunbar
2010-02-13 18:33:18 +00:00
parent 2037d4e4ea
commit 43813bf023
7 changed files with 152 additions and 0 deletions

View 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]

View File

@@ -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()

View File

@@ -0,0 +1,6 @@
#ifndef HEADER1
#define HEADER1
#include "header3.h"
#endif

View File

@@ -0,0 +1,6 @@
#ifndef HEADER2
#define HEADER2
#include "header3.h"
#endif

View File

@@ -0,0 +1,3 @@
// Not a guarded header!
void f();

View File

@@ -0,0 +1,5 @@
#include "header1.h"
#include "header2.h"
#include "header1.h"
int main() { }

View 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])