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