##// END OF EJS Templates
deps: also report unseen known violations
Mads Kiilerich -
r8600:c7663810 default
parent child Browse files
Show More
@@ -1,293 +1,295 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2
2
3
3
4 import re
4 import re
5 import sys
5 import sys
6
6
7
7
8 ignored_modules = set('''
8 ignored_modules = set('''
9 argparse
9 argparse
10 base64
10 base64
11 bcrypt
11 bcrypt
12 binascii
12 binascii
13 bleach
13 bleach
14 calendar
14 calendar
15 celery
15 celery
16 celery
16 celery
17 chardet
17 chardet
18 click
18 click
19 collections
19 collections
20 configparser
20 configparser
21 copy
21 copy
22 csv
22 csv
23 ctypes
23 ctypes
24 datetime
24 datetime
25 dateutil
25 dateutil
26 decimal
26 decimal
27 decorator
27 decorator
28 difflib
28 difflib
29 distutils
29 distutils
30 docutils
30 docutils
31 email
31 email
32 errno
32 errno
33 fileinput
33 fileinput
34 functools
34 functools
35 getpass
35 getpass
36 grp
36 grp
37 hashlib
37 hashlib
38 hmac
38 hmac
39 html
39 html
40 http
40 http
41 imp
41 imp
42 importlib
42 importlib
43 inspect
43 inspect
44 io
44 io
45 ipaddr
45 ipaddr
46 IPython
46 IPython
47 isapi_wsgi
47 isapi_wsgi
48 itertools
48 itertools
49 json
49 json
50 kajiki
50 kajiki
51 ldap
51 ldap
52 logging
52 logging
53 mako
53 mako
54 markdown
54 markdown
55 mimetypes
55 mimetypes
56 mock
56 mock
57 msvcrt
57 msvcrt
58 multiprocessing
58 multiprocessing
59 operator
59 operator
60 os
60 os
61 paginate
61 paginate
62 paginate_sqlalchemy
62 paginate_sqlalchemy
63 pam
63 pam
64 paste
64 paste
65 pkg_resources
65 pkg_resources
66 platform
66 platform
67 posixpath
67 posixpath
68 pprint
68 pprint
69 pwd
69 pwd
70 pyflakes
70 pyflakes
71 pytest
71 pytest
72 pytest_localserver
72 pytest_localserver
73 random
73 random
74 re
74 re
75 routes
75 routes
76 setuptools
76 setuptools
77 shlex
77 shlex
78 shutil
78 shutil
79 smtplib
79 smtplib
80 socket
80 socket
81 ssl
81 ssl
82 stat
82 stat
83 string
83 string
84 struct
84 struct
85 subprocess
85 subprocess
86 sys
86 sys
87 tarfile
87 tarfile
88 tempfile
88 tempfile
89 textwrap
89 textwrap
90 tgext
90 tgext
91 threading
91 threading
92 time
92 time
93 traceback
93 traceback
94 traitlets
94 traitlets
95 types
95 types
96 urllib
96 urllib
97 urlobject
97 urlobject
98 uuid
98 uuid
99 warnings
99 warnings
100 webhelpers2
100 webhelpers2
101 webob
101 webob
102 webtest
102 webtest
103 whoosh
103 whoosh
104 win32traceutil
104 win32traceutil
105 zipfile
105 zipfile
106 '''.split())
106 '''.split())
107
107
108 top_modules = set('''
108 top_modules = set('''
109 kallithea.alembic
109 kallithea.alembic
110 kallithea.bin
110 kallithea.bin
111 kallithea.config
111 kallithea.config
112 kallithea.controllers
112 kallithea.controllers
113 kallithea.templates.py
113 kallithea.templates.py
114 scripts
114 scripts
115 '''.split())
115 '''.split())
116
116
117 bottom_external_modules = set('''
117 bottom_external_modules = set('''
118 tg
118 tg
119 mercurial
119 mercurial
120 sqlalchemy
120 sqlalchemy
121 alembic
121 alembic
122 formencode
122 formencode
123 pygments
123 pygments
124 dulwich
124 dulwich
125 beaker
125 beaker
126 psycopg2
126 psycopg2
127 docs
127 docs
128 setup
128 setup
129 conftest
129 conftest
130 '''.split())
130 '''.split())
131
131
132 normal_modules = set('''
132 normal_modules = set('''
133 kallithea
133 kallithea
134 kallithea.controllers.base
134 kallithea.controllers.base
135 kallithea.lib
135 kallithea.lib
136 kallithea.lib.auth
136 kallithea.lib.auth
137 kallithea.lib.auth_modules
137 kallithea.lib.auth_modules
138 kallithea.lib.celerylib
138 kallithea.lib.celerylib
139 kallithea.lib.db_manage
139 kallithea.lib.db_manage
140 kallithea.lib.helpers
140 kallithea.lib.helpers
141 kallithea.lib.hooks
141 kallithea.lib.hooks
142 kallithea.lib.indexers
142 kallithea.lib.indexers
143 kallithea.lib.utils
143 kallithea.lib.utils
144 kallithea.lib.utils2
144 kallithea.lib.utils2
145 kallithea.lib.vcs
145 kallithea.lib.vcs
146 kallithea.lib.webutils
146 kallithea.lib.webutils
147 kallithea.model
147 kallithea.model
148 kallithea.model.async_tasks
148 kallithea.model.async_tasks
149 kallithea.model.scm
149 kallithea.model.scm
150 kallithea.templates.py
150 kallithea.templates.py
151 '''.split())
151 '''.split())
152
152
153 shown_modules = normal_modules | top_modules
153 shown_modules = normal_modules | top_modules
154
154
155 # break the chains somehow - this is a cleanup TODO list
155 # break the chains somehow - this is a cleanup TODO list
156 known_violations = [
156 known_violations = set([
157 ('kallithea.lib.auth_modules', 'kallithea.lib.auth'), # needs base&facade
157 ('kallithea.lib.auth_modules', 'kallithea.lib.auth'), # needs base&facade
158 ('kallithea.lib.utils', 'kallithea.model'), # clean up utils
158 ('kallithea.lib.utils', 'kallithea.model'), # clean up utils
159 ('kallithea.lib.utils', 'kallithea.model.db'),
159 ('kallithea.lib.utils', 'kallithea.model.db'),
160 ('kallithea.lib.utils', 'kallithea.model.scm'),
160 ('kallithea.lib.utils', 'kallithea.model.scm'),
161 ('kallithea.model.async_tasks', 'kallithea.lib.hooks'),
161 ('kallithea.model.async_tasks', 'kallithea.lib.hooks'),
162 ('kallithea.model.async_tasks', 'kallithea.lib.indexers'),
162 ('kallithea.model.async_tasks', 'kallithea.lib.indexers'),
163 ('kallithea.model.async_tasks', 'kallithea.model'),
163 ('kallithea.model.async_tasks', 'kallithea.model'),
164 ('kallithea.model', 'kallithea.lib.auth'), # auth.HasXXX
164 ('kallithea.model', 'kallithea.lib.auth'), # auth.HasXXX
165 ('kallithea.model', 'kallithea.lib.auth_modules'), # validators
165 ('kallithea.model', 'kallithea.lib.auth_modules'), # validators
166 ('kallithea.model', 'kallithea.lib.hooks'), # clean up hooks
166 ('kallithea.model', 'kallithea.lib.hooks'), # clean up hooks
167 ('kallithea.model', 'kallithea.model.scm'),
167 ('kallithea.model', 'kallithea.model.scm'),
168 ('kallithea.model.scm', 'kallithea.lib.hooks'),
168 ('kallithea.model.scm', 'kallithea.lib.hooks'),
169 ]
169 ])
170
170
171 extra_edges = [
171 extra_edges = [
172 ('kallithea.config', 'kallithea.controllers'), # through TG
172 ('kallithea.config', 'kallithea.controllers'), # through TG
173 ('kallithea.lib.auth', 'kallithea.lib.auth_modules'), # custom loader
173 ('kallithea.lib.auth', 'kallithea.lib.auth_modules'), # custom loader
174 ]
174 ]
175
175
176
176
177 def normalize(s):
177 def normalize(s):
178 """Given a string with dot path, return the string it should be shown as."""
178 """Given a string with dot path, return the string it should be shown as."""
179 parts = s.replace('.__init__', '').split('.')
179 parts = s.replace('.__init__', '').split('.')
180 short_2 = '.'.join(parts[:2])
180 short_2 = '.'.join(parts[:2])
181 short_3 = '.'.join(parts[:3])
181 short_3 = '.'.join(parts[:3])
182 short_4 = '.'.join(parts[:4])
182 short_4 = '.'.join(parts[:4])
183 if parts[0] in ['scripts', 'contributor_data', 'i18n_utils']:
183 if parts[0] in ['scripts', 'contributor_data', 'i18n_utils']:
184 return 'scripts'
184 return 'scripts'
185 if short_3 == 'kallithea.model.meta':
185 if short_3 == 'kallithea.model.meta':
186 return 'kallithea.model.db'
186 return 'kallithea.model.db'
187 if parts[:4] == ['kallithea', 'lib', 'vcs', 'ssh']:
187 if parts[:4] == ['kallithea', 'lib', 'vcs', 'ssh']:
188 return 'kallithea.lib.vcs.ssh'
188 return 'kallithea.lib.vcs.ssh'
189 if short_4 in shown_modules:
189 if short_4 in shown_modules:
190 return short_4
190 return short_4
191 if short_3 in shown_modules:
191 if short_3 in shown_modules:
192 return short_3
192 return short_3
193 if short_2 in shown_modules:
193 if short_2 in shown_modules:
194 return short_2
194 return short_2
195 if short_2 == 'kallithea.tests':
195 if short_2 == 'kallithea.tests':
196 return None
196 return None
197 if parts[0] in ignored_modules:
197 if parts[0] in ignored_modules:
198 return None
198 return None
199 assert parts[0] in bottom_external_modules, parts
199 assert parts[0] in bottom_external_modules, parts
200 return parts[0]
200 return parts[0]
201
201
202
202
203 def main(filenames):
203 def main(filenames):
204 if not filenames or filenames[0].startswith('-'):
204 if not filenames or filenames[0].startswith('-'):
205 print('''\
205 print('''\
206 Usage:
206 Usage:
207 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/deps.py
207 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/deps.py
208 dot -Tsvg deps.dot > deps.svg
208 dot -Tsvg deps.dot > deps.svg
209 ''')
209 ''')
210 raise SystemExit(1)
210 raise SystemExit(1)
211
211
212 files_imports = dict() # map filenames to its imports
212 files_imports = dict() # map filenames to its imports
213 import_deps = set() # set of tuples with module name and its imports
213 import_deps = set() # set of tuples with module name and its imports
214 for fn in filenames:
214 for fn in filenames:
215 with open(fn) as f:
215 with open(fn) as f:
216 s = f.read()
216 s = f.read()
217
217
218 dot_name = (fn[:-3] if fn.endswith('.py') else fn).replace('/', '.')
218 dot_name = (fn[:-3] if fn.endswith('.py') else fn).replace('/', '.')
219 file_imports = set()
219 file_imports = set()
220 for m in re.finditer(r'^ *(?:from ([^ ]*) import (?:([a-zA-Z].*)|\(([^)]*)\))|import (.*))$', s, re.MULTILINE):
220 for m in re.finditer(r'^ *(?:from ([^ ]*) import (?:([a-zA-Z].*)|\(([^)]*)\))|import (.*))$', s, re.MULTILINE):
221 m_from, m_from_import, m_from_import2, m_import = m.groups()
221 m_from, m_from_import, m_from_import2, m_import = m.groups()
222 if m_from:
222 if m_from:
223 pre = m_from + '.'
223 pre = m_from + '.'
224 if pre.startswith('.'):
224 if pre.startswith('.'):
225 pre = dot_name.rsplit('.', 1)[0] + pre
225 pre = dot_name.rsplit('.', 1)[0] + pre
226 importlist = m_from_import or m_from_import2
226 importlist = m_from_import or m_from_import2
227 else:
227 else:
228 pre = ''
228 pre = ''
229 importlist = m_import
229 importlist = m_import
230 for imp in importlist.split('#', 1)[0].split(','):
230 for imp in importlist.split('#', 1)[0].split(','):
231 full_imp = pre + imp.strip().split(' as ', 1)[0]
231 full_imp = pre + imp.strip().split(' as ', 1)[0]
232 file_imports.add(full_imp)
232 file_imports.add(full_imp)
233 import_deps.add((dot_name, full_imp))
233 import_deps.add((dot_name, full_imp))
234 files_imports[fn] = file_imports
234 files_imports[fn] = file_imports
235
235
236 # dump out all deps for debugging and analysis
236 # dump out all deps for debugging and analysis
237 with open('deps.txt', 'w') as f:
237 with open('deps.txt', 'w') as f:
238 for fn, file_imports in sorted(files_imports.items()):
238 for fn, file_imports in sorted(files_imports.items()):
239 for file_import in sorted(file_imports):
239 for file_import in sorted(file_imports):
240 if file_import.split('.', 1)[0] in ignored_modules:
240 if file_import.split('.', 1)[0] in ignored_modules:
241 continue
241 continue
242 f.write('%s: %s\n' % (fn, file_import))
242 f.write('%s: %s\n' % (fn, file_import))
243
243
244 # find leafs that haven't been ignored - they are the important external dependencies and shown in the bottom row
244 # find leafs that haven't been ignored - they are the important external dependencies and shown in the bottom row
245 only_imported = set(
245 only_imported = set(
246 set(normalize(b) for a, b in import_deps) -
246 set(normalize(b) for a, b in import_deps) -
247 set(normalize(a) for a, b in import_deps) -
247 set(normalize(a) for a, b in import_deps) -
248 set([None, 'kallithea'])
248 set([None, 'kallithea'])
249 )
249 )
250
250
251 normalized_dep_edges = set()
251 normalized_dep_edges = set()
252 for dot_name, full_imp in import_deps:
252 for dot_name, full_imp in import_deps:
253 a = normalize(dot_name)
253 a = normalize(dot_name)
254 b = normalize(full_imp)
254 b = normalize(full_imp)
255 if a is None or b is None or a == b:
255 if a is None or b is None or a == b:
256 continue
256 continue
257 normalized_dep_edges.add((a, b))
257 normalized_dep_edges.add((a, b))
258 #print((dot_name, full_imp, a, b))
258 #print((dot_name, full_imp, a, b))
259 normalized_dep_edges.update(extra_edges)
259 normalized_dep_edges.update(extra_edges)
260
260
261 unseen_shown_modules = shown_modules.difference(a for a, b in normalized_dep_edges).difference(b for a, b in normalized_dep_edges)
261 unseen_shown_modules = shown_modules.difference(a for a, b in normalized_dep_edges).difference(b for a, b in normalized_dep_edges)
262 assert not unseen_shown_modules, unseen_shown_modules
262 assert not unseen_shown_modules, unseen_shown_modules
263
263
264 with open('deps.dot', 'w') as f:
264 with open('deps.dot', 'w') as f:
265 f.write('digraph {\n')
265 f.write('digraph {\n')
266 f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(top_modules)))
266 f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(top_modules)))
267 f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(only_imported)))
267 f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(only_imported)))
268 for a, b in sorted(normalized_dep_edges):
268 for a, b in sorted(normalized_dep_edges):
269 f.write(' "%s" -> "%s"%s\n' % (a, b, ' [color=red]' if (a, b) in known_violations else ' [color=green]' if (a, b) in extra_edges else ''))
269 f.write(' "%s" -> "%s"%s\n' % (a, b, ' [color=red]' if (a, b) in known_violations else ' [color=green]' if (a, b) in extra_edges else ''))
270 f.write('}\n')
270 f.write('}\n')
271
271
272 # verify dependencies by untangling dependency chain bottom-up:
272 # verify dependencies by untangling dependency chain bottom-up:
273 todo = set(normalized_dep_edges)
273 todo = set(normalized_dep_edges)
274 unseen_violations = known_violations.difference(todo)
275 assert not unseen_violations, unseen_violations
274 for x in known_violations:
276 for x in known_violations:
275 todo.remove(x)
277 todo.remove(x)
276
278
277 while todo:
279 while todo:
278 depending = set(a for a, b in todo)
280 depending = set(a for a, b in todo)
279 depended = set(b for a, b in todo)
281 depended = set(b for a, b in todo)
280 drop = depended - depending
282 drop = depended - depending
281 if not drop:
283 if not drop:
282 print('ERROR: cycles:', len(todo))
284 print('ERROR: cycles:', len(todo))
283 for x in sorted(todo):
285 for x in sorted(todo):
284 print('%s,' % (x,))
286 print('%s,' % (x,))
285 raise SystemExit(1)
287 raise SystemExit(1)
286 #for do_b in sorted(drop):
288 #for do_b in sorted(drop):
287 # print('Picking', do_b, '- unblocks:', ' '.join(a for a, b in sorted((todo)) if b == do_b))
289 # print('Picking', do_b, '- unblocks:', ' '.join(a for a, b in sorted((todo)) if b == do_b))
288 todo = set((a, b) for a, b in todo if b in depending)
290 todo = set((a, b) for a, b in todo if b in depending)
289 #print()
291 #print()
290
292
291
293
292 if __name__ == '__main__':
294 if __name__ == '__main__':
293 main(sys.argv[1:])
295 main(sys.argv[1:])
General Comments 0
You need to be logged in to leave comments. Login now