blob: 4e8e930948f1ff433307f5400fa2298a021ae374 [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
Guido van Rossumec758ea1991-06-04 20:36:54 +00002
3# pdeps
4#
5# Find dependencies between a bunch of Python modules.
6#
7# Usage:
Tim Peters70c43782001-01-17 08:48:39 +00008# pdeps file1.py file2.py ...
Guido van Rossumec758ea1991-06-04 20:36:54 +00009#
10# Output:
11# Four tables separated by lines like '--- Closure ---':
12# 1) Direct dependencies, listing which module imports which other modules
13# 2) The inverse of (1)
14# 3) Indirect dependencies, or the closure of the above
15# 4) The inverse of (3)
16#
17# To do:
18# - command line options to select output type
19# - option to automatically scan the Python library for referenced modules
20# - option to limit output to particular modules
21
22
23import sys
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000024import re
Guido van Rossum15f27fb1992-12-10 00:00:58 +000025import os
Guido van Rossumec758ea1991-06-04 20:36:54 +000026
27
28# Main program
29#
30def main():
Tim Peters70c43782001-01-17 08:48:39 +000031 args = sys.argv[1:]
32 if not args:
Collin Winter6afaeb72007-08-03 17:06:41 +000033 print('usage: pdeps file.py file.py ...')
Tim Peters70c43782001-01-17 08:48:39 +000034 return 2
35 #
36 table = {}
37 for arg in args:
38 process(arg, table)
39 #
Collin Winter6afaeb72007-08-03 17:06:41 +000040 print('--- Uses ---')
Tim Peters70c43782001-01-17 08:48:39 +000041 printresults(table)
42 #
Collin Winter6afaeb72007-08-03 17:06:41 +000043 print('--- Used By ---')
Tim Peters70c43782001-01-17 08:48:39 +000044 inv = inverse(table)
45 printresults(inv)
46 #
Collin Winter6afaeb72007-08-03 17:06:41 +000047 print('--- Closure of Uses ---')
Tim Peters70c43782001-01-17 08:48:39 +000048 reach = closure(table)
49 printresults(reach)
50 #
Collin Winter6afaeb72007-08-03 17:06:41 +000051 print('--- Closure of Used By ---')
Tim Peters70c43782001-01-17 08:48:39 +000052 invreach = inverse(reach)
53 printresults(invreach)
54 #
55 return 0
Guido van Rossumec758ea1991-06-04 20:36:54 +000056
57
58# Compiled regular expressions to search for import statements
59#
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000060m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+')
61m_from = re.compile('^[ \t]*import[ \t]+([^#]+)')
Guido van Rossumec758ea1991-06-04 20:36:54 +000062
63
64# Collect data from one file
65#
66def process(filename, table):
Serhiy Storchaka172bb392019-03-30 08:33:02 +020067 with open(filename) as fp:
68 mod = os.path.basename(filename)
69 if mod[-3:] == '.py':
70 mod = mod[:-3]
71 table[mod] = list = []
72 while 1:
73 line = fp.readline()
74 if not line: break
75 while line[-1:] == '\\':
76 nextline = fp.readline()
77 if not nextline: break
78 line = line[:-1] + nextline
79 m_found = m_import.match(line) or m_from.match(line)
80 if m_found:
81 (a, b), (a1, b1) = m_found.regs[:2]
82 else: continue
83 words = line[a1:b1].split(',')
84 # print '#', line, words
85 for word in words:
86 word = word.strip()
87 if word not in list:
88 list.append(word)
Guido van Rossumec758ea1991-06-04 20:36:54 +000089
90
91# Compute closure (this is in fact totally general)
92#
93def closure(table):
Georg Brandl8efadf52008-05-16 15:23:30 +000094 modules = list(table.keys())
Tim Peters70c43782001-01-17 08:48:39 +000095 #
96 # Initialize reach with a copy of table
97 #
98 reach = {}
99 for mod in modules:
100 reach[mod] = table[mod][:]
101 #
102 # Iterate until no more change
103 #
104 change = 1
105 while change:
106 change = 0
107 for mod in modules:
108 for mo in reach[mod]:
109 if mo in modules:
110 for m in reach[mo]:
111 if m not in reach[mod]:
112 reach[mod].append(m)
113 change = 1
114 #
115 return reach
Guido van Rossumec758ea1991-06-04 20:36:54 +0000116
117
118# Invert a table (this is again totally general).
119# All keys of the original table are made keys of the inverse,
120# so there may be empty lists in the inverse.
121#
122def inverse(table):
Tim Peters70c43782001-01-17 08:48:39 +0000123 inv = {}
124 for key in table.keys():
R David Murrayd3af6342012-04-05 22:59:13 -0400125 if key not in inv:
Tim Peters70c43782001-01-17 08:48:39 +0000126 inv[key] = []
127 for item in table[key]:
128 store(inv, item, key)
129 return inv
Guido van Rossumec758ea1991-06-04 20:36:54 +0000130
131
132# Store "item" in "dict" under "key".
133# The dictionary maps keys to lists of items.
134# If there is no list for the key yet, it is created.
135#
136def store(dict, key, item):
Georg Brandl8efadf52008-05-16 15:23:30 +0000137 if key in dict:
Tim Peters70c43782001-01-17 08:48:39 +0000138 dict[key].append(item)
139 else:
140 dict[key] = [item]
Guido van Rossumec758ea1991-06-04 20:36:54 +0000141
142
143# Tabulate results neatly
144#
145def printresults(table):
Georg Brandl8efadf52008-05-16 15:23:30 +0000146 modules = sorted(table.keys())
Tim Peters70c43782001-01-17 08:48:39 +0000147 maxlen = 0
148 for mod in modules: maxlen = max(maxlen, len(mod))
Tim Peters70c43782001-01-17 08:48:39 +0000149 for mod in modules:
Georg Brandl8efadf52008-05-16 15:23:30 +0000150 list = sorted(table[mod])
Collin Winter6afaeb72007-08-03 17:06:41 +0000151 print(mod.ljust(maxlen), ':', end=' ')
Tim Peters70c43782001-01-17 08:48:39 +0000152 if mod in list:
Collin Winter6afaeb72007-08-03 17:06:41 +0000153 print('(*)', end=' ')
Tim Peters70c43782001-01-17 08:48:39 +0000154 for ref in list:
Collin Winter6afaeb72007-08-03 17:06:41 +0000155 print(ref, end=' ')
156 print()
Guido van Rossumec758ea1991-06-04 20:36:54 +0000157
158
159# Call main and honor exit status
Andrew M. Kuchlinge236b382004-08-09 17:27:55 +0000160if __name__ == '__main__':
161 try:
162 sys.exit(main())
163 except KeyboardInterrupt:
164 sys.exit(1)