7 Extract Python dependencies between packages and create DOT graph.
8 Both `import` and `include` dependencies are considered.
16 from collections
import defaultdict
19 """Walk an AST collecting import/include statements."""
27 self.
imports.
update(alias.name.split(
'.',1)[0]
for alias
in node.names)
30 """from XYZ import ABC"""
35 """"include(XYZ/ABC.py)"""
36 if isinstance(node.func, ast.Name)
and node.func.id==
'include' and node.args:
37 if isinstance(node.args[0], ast.Str):
42 """Get all the imports/includes in a file."""
45 tree = ast.parse(
open(filename,
'rb').
read(), filename=filename)
46 except SyntaxError
as e:
48 print(e, file=sys.stderr)
57 def walk_tree(path='./', print_error=False, filterFnc=None):
58 """Walk the source tree and extract python dependencies, filtered by FilterFnc"""
61 deps = defaultdict(
lambda : defaultdict(set))
62 for root, dirs, files
in os.walk(path):
63 if 'CMakeLists.txt' in files:
64 pkg = os.path.basename(root)
66 if (filterFnc
and not filterFnc(pkg)):
69 for f
in filter(
lambda p : os.path.splitext(p)[1]==
'.py', files):
71 deps[pkg][
'import'].
update(d.imports)
72 deps[pkg][
'include'].
update(d.includes)
78 """Save the dependencies as dot graph, nodes filtered by filterFnc"""
80 graph = pygraphviz.AGraph(name=
'AthPyGraph', directed=
True)
82 for t
in [
'import',
'include']:
83 graph.add_edges_from(((a,b)
for b
in deps[a][t]
84 if a!=b
and (filterFnc
is None or (filterFnc(a)
and filterFnc(b)))),
90 parser = argparse.ArgumentParser(description=__doc__)
92 parser.add_argument(
'path', metavar=
'DIRECTORY', nargs=
'?', default=
'./',
93 help=
'root of source tree [%(default)s]')
95 parser.add_argument(
'-o',
'--output', metavar=
'FILE', type=str,
96 help=
'output file for DOT graph')
98 parser.add_argument(
'-p',
'--packages', metavar=
'FILE', type=str,
99 help=
'path to packages.txt file [from release]')
101 parser.add_argument(
'-a',
'--all', action=
'store_true',
102 help=
'include non-athena dependencies')
104 parser.add_argument(
'-v',
'--verbose', action=
'store_true',
105 help=
'print parse errors')
107 args = parser.parse_args()
111 package_file = args.packages
or os.path.join(os.environ[
'AtlasArea'],
'InstallArea',
112 os.environ[
'BINARY_TAG'],
'packages.txt')
115 with open(package_file)
as f:
116 packages =
set(line.rstrip().
split(
'/')[-1]
for line
in f
if not line.startswith(
'#'))
117 except FileNotFoundError:
118 parser.error(f
"Cannot read '{package_file}'. Specify via '-p/--packages' or run with '-a/--all'")
121 filterFnc =
None if args.all
else lambda p : p
in packages
131 if __name__ ==
"__main__":