mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-02-26 05:18:46 +00:00

It seems to have been added back in 761e42fa3dd72 for Clang to use it, however it seems to have never been used for that purpose, so it is probably fine to remove it. Differential Revision: https://reviews.llvm.org/D122330
225 lines
8.3 KiB
Python
Executable File
225 lines
8.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#===----------------------------------------------------------------------===##
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
#
|
|
#===----------------------------------------------------------------------===##
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
|
|
def is_config_header(h):
|
|
return os.path.basename(h) in ['__config', '__undef_macros', 'version']
|
|
|
|
|
|
def is_experimental_header(h):
|
|
return ('experimental/' in h) or ('ext/' in h)
|
|
|
|
|
|
def is_support_header(h):
|
|
return '__support/' in h
|
|
|
|
|
|
class FileEntry:
|
|
def __init__(self, includes, individual_linecount):
|
|
self.includes = includes
|
|
self.individual_linecount = individual_linecount
|
|
self.cumulative_linecount = None # documentation: this gets filled in later
|
|
self.is_graph_root = None # documentation: this gets filled in later
|
|
|
|
|
|
def list_all_roots_under(root):
|
|
result = []
|
|
for root, _, files in os.walk(root):
|
|
for fname in files:
|
|
if os.path.basename(root).startswith('__') or fname.startswith('__'):
|
|
pass
|
|
elif ('.' in fname and not fname.endswith('.h')):
|
|
pass
|
|
else:
|
|
result.append(root + '/' + fname)
|
|
return result
|
|
|
|
|
|
def build_file_entry(fname, options):
|
|
assert os.path.exists(fname)
|
|
|
|
def locate_header_file(h, paths):
|
|
for p in paths:
|
|
fullname = p + '/' + h
|
|
if os.path.exists(fullname):
|
|
return fullname
|
|
if options.error_on_file_not_found:
|
|
raise RuntimeError('Header not found: %s, included by %s' % (h, fname))
|
|
return None
|
|
|
|
local_includes = []
|
|
system_includes = []
|
|
linecount = 0
|
|
with open(fname, 'r', encoding='utf-8') as f:
|
|
for line in f.readlines():
|
|
linecount += 1
|
|
m = re.match(r'\s*#\s*include\s+"([^"]*)"', line)
|
|
if m is not None:
|
|
local_includes.append(m.group(1))
|
|
m = re.match(r'\s*#\s*include\s+<([^>]*)>', line)
|
|
if m is not None:
|
|
system_includes.append(m.group(1))
|
|
|
|
fully_qualified_includes = [
|
|
locate_header_file(h, options.search_dirs)
|
|
for h in system_includes
|
|
] + [
|
|
locate_header_file(h, os.path.dirname(fname))
|
|
for h in local_includes
|
|
]
|
|
|
|
return FileEntry(
|
|
# If file-not-found wasn't an error, then skip non-found files
|
|
includes = [h for h in fully_qualified_includes if h is not None],
|
|
individual_linecount = linecount,
|
|
)
|
|
|
|
|
|
def transitive_closure_of_includes(graph, h1):
|
|
visited = set()
|
|
def explore(graph, h1):
|
|
if h1 not in visited:
|
|
visited.add(h1)
|
|
for h2 in graph[h1].includes:
|
|
explore(graph, h2)
|
|
explore(graph, h1)
|
|
return visited
|
|
|
|
|
|
def transitively_includes(graph, h1, h2):
|
|
return (h1 != h2) and (h2 in transitive_closure_of_includes(graph, h1))
|
|
|
|
|
|
def build_graph(roots, options):
|
|
original_roots = list(roots)
|
|
graph = {}
|
|
while roots:
|
|
frontier = roots
|
|
roots = []
|
|
for fname in frontier:
|
|
if fname not in graph:
|
|
graph[fname] = build_file_entry(fname, options)
|
|
graph[fname].is_graph_root = (fname in original_roots)
|
|
roots += graph[fname].includes
|
|
for fname, entry in graph.items():
|
|
entry.cumulative_linecount = sum(graph[h].individual_linecount for h in transitive_closure_of_includes(graph, fname))
|
|
return graph
|
|
|
|
|
|
def get_friendly_id(fname):
|
|
i = fname.index('include/')
|
|
assert(i >= 0)
|
|
result = fname[i+8:]
|
|
return result
|
|
|
|
|
|
def get_graphviz(graph, options):
|
|
|
|
def get_decorators(fname, entry):
|
|
result = ''
|
|
if entry.is_graph_root:
|
|
result += ' [style=bold]'
|
|
if options.show_individual_line_counts and options.show_cumulative_line_counts:
|
|
result += ' [label="%s\\n%d indiv, %d cumul"]' % (
|
|
get_friendly_id(fname), entry.individual_linecount, entry.cumulative_linecount
|
|
)
|
|
elif options.show_individual_line_counts:
|
|
result += ' [label="%s\\n%d indiv"]' % (get_friendly_id(fname), entry.individual_linecount)
|
|
elif options.show_cumulative_line_counts:
|
|
result += ' [label="%s\\n%d cumul"]' % (get_friendly_id(fname), entry.cumulative_linecount)
|
|
return result
|
|
|
|
result = ''
|
|
result += 'strict digraph {\n'
|
|
result += ' rankdir=LR;\n'
|
|
result += ' layout=dot;\n\n'
|
|
for fname, entry in graph.items():
|
|
result += ' "%s"%s;\n' % (get_friendly_id(fname), get_decorators(fname, entry))
|
|
for h in entry.includes:
|
|
if any(transitively_includes(graph, i, h) for i in entry.includes) and not options.show_transitive_edges:
|
|
continue
|
|
result += ' "%s" -> "%s";\n' % (get_friendly_id(fname), get_friendly_id(h))
|
|
result += '}\n'
|
|
return result
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(
|
|
description='Produce a dependency graph of libc++ headers, in GraphViz dot format.\n' +
|
|
'For example, ./graph_header_deps.py | dot -Tpng > graph.png',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
parser.add_argument('--root', default=None, metavar='FILE', help='File or directory to be the root of the dependency graph')
|
|
parser.add_argument('-I', dest='search_dirs', default=[], action='append', metavar='DIR', help='Path(s) to search for local includes')
|
|
parser.add_argument('--show-transitive-edges', action='store_true', help='Show edges to headers that are transitively included anyway')
|
|
parser.add_argument('--show-config-headers', action='store_true', help='Show universally included headers, such as __config')
|
|
parser.add_argument('--show-experimental-headers', action='store_true', help='Show headers in the experimental/ and ext/ directories')
|
|
parser.add_argument('--show-support-headers', action='store_true', help='Show headers in the __support/ directory')
|
|
parser.add_argument('--show-individual-line-counts', action='store_true', help='Include an individual line count in each node')
|
|
parser.add_argument('--show-cumulative-line-counts', action='store_true', help='Include a total line count in each node')
|
|
parser.add_argument('--error-on-file-not-found', action='store_true', help="Don't ignore failure to open an #included file")
|
|
|
|
options = parser.parse_args()
|
|
|
|
if options.root is None:
|
|
curr_dir = os.path.dirname(os.path.abspath(__file__))
|
|
options.root = os.path.join(curr_dir, '../include')
|
|
|
|
if options.search_dirs == [] and os.path.isdir(options.root):
|
|
options.search_dirs = [options.root]
|
|
|
|
options.root = os.path.abspath(options.root)
|
|
options.search_dirs = [os.path.abspath(p) for p in options.search_dirs]
|
|
|
|
if os.path.isdir(options.root):
|
|
roots = list_all_roots_under(options.root)
|
|
elif os.path.isfile(options.root):
|
|
roots = [options.root]
|
|
else:
|
|
raise RuntimeError('--root seems to be invalid')
|
|
|
|
graph = build_graph(roots, options)
|
|
|
|
# Eliminate certain kinds of "visual noise" headers, if asked for.
|
|
def should_keep(fname):
|
|
return all([
|
|
options.show_config_headers or not is_config_header(fname),
|
|
options.show_experimental_headers or not is_experimental_header(fname),
|
|
options.show_support_headers or not is_support_header(fname),
|
|
])
|
|
|
|
for fname in list(graph.keys()):
|
|
if should_keep(fname):
|
|
graph[fname].includes = [h for h in graph[fname].includes if should_keep(h)]
|
|
else:
|
|
del graph[fname]
|
|
|
|
# Look for cycles.
|
|
no_cycles_detected = True
|
|
for fname, entry in graph.items():
|
|
for h in entry.includes:
|
|
if h == fname:
|
|
sys.stderr.write('Cycle detected: %s includes itself\n' % (
|
|
get_friendly_id(fname)
|
|
))
|
|
no_cycles_detected = False
|
|
elif transitively_includes(graph, h, fname):
|
|
sys.stderr.write('Cycle detected between %s and %s\n' % (
|
|
get_friendly_id(fname), get_friendly_id(h)
|
|
))
|
|
no_cycles_detected = False
|
|
assert no_cycles_detected
|
|
|
|
print(get_graphviz(graph, options))
|