ATLAS Offline Software
Loading...
Searching...
No Matches
apydep.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3#
4# Created: Oct 2020, Frank Winklmeier
5#
6"""
7Extract Python dependencies between packages and create DOT graph.
8Both `import` and `include` dependencies are considered.
9"""
10
11import ast
12import sys
13import os
14import argparse
15import pygraphviz
16from collections import defaultdict
17
18class DependencyFinder(ast.NodeVisitor):
19 """Walk an AST collecting import/include statements."""
20
21 def __init__(self):
22 self.imports = set()
23 self.includes = set()
24
25 def visit_Import(self, node):
26 """import XYZ"""
27 self.imports.update(alias.name.split('.',1)[0] for alias in node.names)
28
29 def visit_ImportFrom(self, node):
30 """from XYZ import ABC"""
31 if node.level==0: # ignore relative imports
32 self.imports.add(node.module.split('.',1)[0])
33
34 def visit_Call(self, node):
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.Constant):
38 self.includes.add(node.args[0].value.split('/',1)[0])
39
40
41def get_dependencies(filename, print_error=False):
42 """Get all the imports/includes in a file."""
43
44 try:
45 tree = ast.parse(open(filename,'rb').read(), filename=filename)
46 except Exception as e:
47 if print_error:
48 print(e, file=sys.stderr)
49 return DependencyFinder()
50
51 finder = DependencyFinder()
52 try:
53 finder.visit(tree)
54 except Exception as e:
55 if print_error:
56 print(e, f'({os.path.basename(filename)})', file=sys.stderr)
57
58 return finder
59
60
61def walk_tree(path='./', print_error=False, filterFnc=None):
62 """Walk the source tree and extract python dependencies, filtered by FilterFnc"""
63
64 pkg = 'UNKNOWN'
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)
69
70 if (filterFnc and not filterFnc(pkg)):
71 continue
72
73 for f in filter(lambda p : os.path.splitext(p)[1]=='.py', files):
74 d = get_dependencies(os.path.join(root,f), print_error)
75 deps[pkg]['import'].update(d.imports)
76 deps[pkg]['include'].update(d.includes)
77
78 return deps
79
80
81def make_graph(deps, filterFnc=None):
82 """Save the dependencies as dot graph, nodes filtered by filterFnc"""
83
84 graph = pygraphviz.AGraph(name='AthPyGraph', directed=True)
85 for a in deps:
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)))),
89 label = t)
90 return graph
91
92
93def main():
94 parser = argparse.ArgumentParser(description=__doc__)
95
96 parser.add_argument('path', metavar='DIRECTORY', nargs='?', default='./',
97 help='root of source tree [%(default)s]')
98
99 parser.add_argument('-o', '--output', metavar='FILE', type=str,
100 help='output file for DOT graph')
101
102 parser.add_argument('-p', '--packages', metavar='FILE', type=str,
103 help='path to packages.txt file [from release]')
104
105 parser.add_argument('-a', '--all', action='store_true',
106 help='include non-athena dependencies')
107
108 parser.add_argument('-v', '--verbose', action='store_true',
109 help='print parse errors')
110
111 args = parser.parse_args()
112
113 packages = None
114 if not args.all:
115 package_file = args.packages or os.path.join(os.environ['AtlasArea'],'InstallArea',
116 os.environ['BINARY_TAG'],'packages.txt')
117
118 try:
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'")
123
124 # By default only show athena packages:
125 filterFnc = None if args.all else lambda p : p in packages
126
127 # Walk source tree and create DOT graph:
128 g = make_graph(walk_tree(args.path, args.verbose, filterFnc), filterFnc)
129
130 if args.output:
131 g.write(args.output)
132 else:
133 print(g)
134
135if __name__ == "__main__":
136 sys.exit(main())
void print(char *figname, TCanvas *c1)
visit_Call(self, node)
Definition apydep.py:34
visit_Import(self, node)
Definition apydep.py:25
visit_ImportFrom(self, node)
Definition apydep.py:29
STL class.
bool add(const std::string &hname, TKey *tobj)
Definition fastadd.cxx:55
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
walk_tree(path='./', print_error=False, filterFnc=None)
Definition apydep.py:61
make_graph(deps, filterFnc=None)
Definition apydep.py:81
get_dependencies(filename, print_error=False)
Definition apydep.py:41
main()
Definition apydep.py:93
IovVectorMap_t read(const Folder &theFolder, const SelectionCriterion &choice, const unsigned int limit=10)