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 Exception
as e:
48 print(e, file=sys.stderr)
54 except Exception
as e:
56 print(e, f
'({os.path.basename(filename)})', file=sys.stderr)
61 def walk_tree(path='./', print_error=False, filterFnc=None):
62 """Walk the source tree and extract python dependencies, filtered by FilterFnc"""
65 deps = defaultdict(
lambda : defaultdict(set))
66 for root, dirs, files
in os.walk(path):
67 if 'CMakeLists.txt' in files:
68 pkg = os.path.basename(root)
70 if (filterFnc
and not filterFnc(pkg)):
73 for f
in filter(
lambda p : os.path.splitext(p)[1]==
'.py', files):
75 deps[pkg][
'import'].
update(d.imports)
76 deps[pkg][
'include'].
update(d.includes)
82 """Save the dependencies as dot graph, nodes filtered by filterFnc"""
84 graph = pygraphviz.AGraph(name=
'AthPyGraph', directed=
True)
86 for t
in [
'import',
'include']:
87 graph.add_edges_from(((a,b)
for b
in deps[a][t]
88 if a!=b
and (filterFnc
is None or (filterFnc(a)
and filterFnc(b)))),
94 parser = argparse.ArgumentParser(description=__doc__)
96 parser.add_argument(
'path', metavar=
'DIRECTORY', nargs=
'?', default=
'./',
97 help=
'root of source tree [%(default)s]')
99 parser.add_argument(
'-o',
'--output', metavar=
'FILE', type=str,
100 help=
'output file for DOT graph')
102 parser.add_argument(
'-p',
'--packages', metavar=
'FILE', type=str,
103 help=
'path to packages.txt file [from release]')
105 parser.add_argument(
'-a',
'--all', action=
'store_true',
106 help=
'include non-athena dependencies')
108 parser.add_argument(
'-v',
'--verbose', action=
'store_true',
109 help=
'print parse errors')
111 args = parser.parse_args()
115 package_file = args.packages
or os.path.join(os.environ[
'AtlasArea'],
'InstallArea',
116 os.environ[
'BINARY_TAG'],
'packages.txt')
119 with open(package_file)
as f:
120 packages =
set(line.rstrip().
split(
'/')[-1]
for line
in f
if not line.startswith(
'#'))
121 except FileNotFoundError:
122 parser.error(f
"Cannot read '{package_file}'. Specify via '-p/--packages' or run with '-a/--all'")
125 filterFnc =
None if args.all
else lambda p : p
in packages
135 if __name__ ==
"__main__":