comparison mercurial/extensions.py @ 38798:d58958676b3c

extensions: add detailed loading information This lets you track down what exactly is happening during extension loading, and how long various steps took.
author Martijn Pieters <mj@zopatista.com>
date Wed, 01 Aug 2018 16:06:53 +0200
parents fcb517ff9562
children 331ab85e910b
comparison
equal deleted inserted replaced
38797:8751d1e2a7ff 38798:d58958676b3c
122 122
123 def _reportimporterror(ui, err, failed, next): 123 def _reportimporterror(ui, err, failed, next):
124 # note: this ui.debug happens before --debug is processed, 124 # note: this ui.debug happens before --debug is processed,
125 # Use --config ui.debug=1 to see them. 125 # Use --config ui.debug=1 to see them.
126 if ui.configbool('devel', 'debug.extensions'): 126 if ui.configbool('devel', 'debug.extensions'):
127 ui.debug('could not import %s (%s): trying %s\n' 127 ui.debug('debug.extensions: - could not import %s (%s): trying %s\n'
128 % (failed, stringutil.forcebytestr(err), next)) 128 % (failed, stringutil.forcebytestr(err), next))
129 if ui.debugflag: 129 if ui.debugflag:
130 ui.traceback() 130 ui.traceback()
131 131
132 def _rejectunicode(name, xs): 132 def _rejectunicode(name, xs):
164 o = getattr(mod, t, None) 164 o = getattr(mod, t, None)
165 if o: 165 if o:
166 _rejectunicode(t, o._table) 166 _rejectunicode(t, o._table)
167 _validatecmdtable(ui, getattr(mod, 'cmdtable', {})) 167 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
168 168
169 def load(ui, name, path): 169 def load(ui, name, path, log=lambda *a: None):
170 if name.startswith('hgext.') or name.startswith('hgext/'): 170 if name.startswith('hgext.') or name.startswith('hgext/'):
171 shortname = name[6:] 171 shortname = name[6:]
172 else: 172 else:
173 shortname = name 173 shortname = name
174 if shortname in _builtin: 174 if shortname in _builtin:
175 return None 175 return None
176 if shortname in _extensions: 176 if shortname in _extensions:
177 return _extensions[shortname] 177 return _extensions[shortname]
178 log(' - loading extension: %r\n', shortname)
178 _extensions[shortname] = None 179 _extensions[shortname] = None
179 mod = _importext(name, path, bind(_reportimporterror, ui)) 180 with util.timedcm() as stats:
181 mod = _importext(name, path, bind(_reportimporterror, ui))
182 log(' > %r extension loaded in %s\n', shortname, stats)
180 183
181 # Before we do anything with the extension, check against minimum stated 184 # Before we do anything with the extension, check against minimum stated
182 # compatibility. This gives extension authors a mechanism to have their 185 # compatibility. This gives extension authors a mechanism to have their
183 # extensions short circuit when loaded with a known incompatible version 186 # extensions short circuit when loaded with a known incompatible version
184 # of Mercurial. 187 # of Mercurial.
185 minver = getattr(mod, 'minimumhgversion', None) 188 minver = getattr(mod, 'minimumhgversion', None)
186 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2): 189 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
187 ui.warn(_('(third party extension %s requires version %s or newer ' 190 ui.warn(_('(third party extension %s requires version %s or newer '
188 'of Mercurial; disabling)\n') % (shortname, minver)) 191 'of Mercurial; disabling)\n') % (shortname, minver))
189 return 192 return
193 log(' - validating extension tables: %r\n', shortname)
190 _validatetables(ui, mod) 194 _validatetables(ui, mod)
191 195
192 _extensions[shortname] = mod 196 _extensions[shortname] = mod
193 _order.append(shortname) 197 _order.append(shortname)
194 for fn in _aftercallbacks.get(shortname, []): 198 log(' - invoking registered callbacks: %r\n', shortname)
195 fn(loaded=True) 199 with util.timedcm() as stats:
200 for fn in _aftercallbacks.get(shortname, []):
201 fn(loaded=True)
202 log(' > callbacks completed in %s\n', stats)
196 return mod 203 return mod
197 204
198 def _runuisetup(name, ui): 205 def _runuisetup(name, ui):
199 uisetup = getattr(_extensions[name], 'uisetup', None) 206 uisetup = getattr(_extensions[name], 'uisetup', None)
200 if uisetup: 207 if uisetup:
223 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg)) 230 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
224 return False 231 return False
225 return True 232 return True
226 233
227 def loadall(ui, whitelist=None): 234 def loadall(ui, whitelist=None):
235 if ui.configbool('devel', 'debug.extensions'):
236 log = lambda msg, *values: ui.debug('debug.extensions: ',
237 msg % values, label='debug.extensions')
238 else:
239 log = lambda *a, **kw: None
228 result = ui.configitems("extensions") 240 result = ui.configitems("extensions")
229 if whitelist is not None: 241 if whitelist is not None:
230 result = [(k, v) for (k, v) in result if k in whitelist] 242 result = [(k, v) for (k, v) in result if k in whitelist]
231 newindex = len(_order) 243 newindex = len(_order)
232 for (name, path) in result: 244 log('loading %sextensions\n', 'additional ' if newindex else '')
233 if path: 245 log('- processing %d entries\n', len(result))
234 if path[0:1] == '!': 246 with util.timedcm() as stats:
235 _disabledextensions[name] = path[1:] 247 for (name, path) in result:
236 continue
237 try:
238 load(ui, name, path)
239 except Exception as inst:
240 msg = stringutil.forcebytestr(inst)
241 if path: 248 if path:
242 ui.warn(_("*** failed to import extension %s from %s: %s\n") 249 if path[0:1] == '!':
243 % (name, path, msg)) 250 if name not in _disabledextensions:
244 else: 251 log(' - skipping disabled extension: %r\n', name)
245 ui.warn(_("*** failed to import extension %s: %s\n") 252 _disabledextensions[name] = path[1:]
246 % (name, msg)) 253 continue
247 if isinstance(inst, error.Hint) and inst.hint: 254 try:
248 ui.warn(_("*** (%s)\n") % inst.hint) 255 load(ui, name, path, log)
249 ui.traceback() 256 except Exception as inst:
257 msg = stringutil.forcebytestr(inst)
258 if path:
259 ui.warn(_("*** failed to import extension %s from %s: %s\n")
260 % (name, path, msg))
261 else:
262 ui.warn(_("*** failed to import extension %s: %s\n")
263 % (name, msg))
264 if isinstance(inst, error.Hint) and inst.hint:
265 ui.warn(_("*** (%s)\n") % inst.hint)
266 ui.traceback()
267
268 log('> loaded %d extensions, total time %s\n',
269 len(_order) - newindex, stats)
250 # list of (objname, loadermod, loadername) tuple: 270 # list of (objname, loadermod, loadername) tuple:
251 # - objname is the name of an object in extension module, 271 # - objname is the name of an object in extension module,
252 # from which extra information is loaded 272 # from which extra information is loaded
253 # - loadermod is the module where loader is placed 273 # - loadermod is the module where loader is placed
254 # - loadername is the name of the function, 274 # - loadername is the name of the function,
256 # 276 #
257 # This one is for the list of item that must be run before running any setup 277 # This one is for the list of item that must be run before running any setup
258 earlyextraloaders = [ 278 earlyextraloaders = [
259 ('configtable', configitems, 'loadconfigtable'), 279 ('configtable', configitems, 'loadconfigtable'),
260 ] 280 ]
281
282 log('- loading configtable attributes\n')
261 _loadextra(ui, newindex, earlyextraloaders) 283 _loadextra(ui, newindex, earlyextraloaders)
262 284
263 broken = set() 285 broken = set()
286 log('- executing uisetup hooks\n')
264 for name in _order[newindex:]: 287 for name in _order[newindex:]:
265 if not _runuisetup(name, ui): 288 log(' - running uisetup for %r\n', name)
266 broken.add(name) 289 with util.timedcm() as stats:
267 290 if not _runuisetup(name, ui):
291 log(' - the %r extension uisetup failed\n', name)
292 broken.add(name)
293 log(' > uisetup for %r took %s\n', name, stats)
294
295 log('- executing extsetup hooks\n')
268 for name in _order[newindex:]: 296 for name in _order[newindex:]:
269 if name in broken: 297 if name in broken:
270 continue 298 continue
271 if not _runextsetup(name, ui): 299 log(' - running extsetup for %r\n', name)
272 broken.add(name) 300 with util.timedcm() as stats:
301 if not _runextsetup(name, ui):
302 log(' - the %r extension extsetup failed\n', name)
303 broken.add(name)
304 log(' > extsetup for %r took %s\n', name, stats)
273 305
274 for name in broken: 306 for name in broken:
307 log(' - disabling broken %r extension\n', name)
275 _extensions[name] = None 308 _extensions[name] = None
276 309
277 # Call aftercallbacks that were never met. 310 # Call aftercallbacks that were never met.
278 for shortname in _aftercallbacks: 311 log('- executing remaining aftercallbacks\n')
279 if shortname in _extensions: 312 with util.timedcm() as stats:
280 continue 313 for shortname in _aftercallbacks:
281 314 if shortname in _extensions:
282 for fn in _aftercallbacks[shortname]: 315 continue
283 fn(loaded=False) 316
317 for fn in _aftercallbacks[shortname]:
318 log(' - extension %r not loaded, notify callbacks\n',
319 shortname)
320 fn(loaded=False)
321 log('> remaining aftercallbacks completed in %s\n', stats)
284 322
285 # loadall() is called multiple times and lingering _aftercallbacks 323 # loadall() is called multiple times and lingering _aftercallbacks
286 # entries could result in double execution. See issue4646. 324 # entries could result in double execution. See issue4646.
287 _aftercallbacks.clear() 325 _aftercallbacks.clear()
288 326
302 # - objname is the name of an object in extension module, 340 # - objname is the name of an object in extension module,
303 # from which extra information is loaded 341 # from which extra information is loaded
304 # - loadermod is the module where loader is placed 342 # - loadermod is the module where loader is placed
305 # - loadername is the name of the function, 343 # - loadername is the name of the function,
306 # which takes (ui, extensionname, extraobj) arguments 344 # which takes (ui, extensionname, extraobj) arguments
345 log('- loading extension registration objects\n')
307 extraloaders = [ 346 extraloaders = [
308 ('cmdtable', commands, 'loadcmdtable'), 347 ('cmdtable', commands, 'loadcmdtable'),
309 ('colortable', color, 'loadcolortable'), 348 ('colortable', color, 'loadcolortable'),
310 ('filesetpredicate', fileset, 'loadpredicate'), 349 ('filesetpredicate', fileset, 'loadpredicate'),
311 ('internalmerge', filemerge, 'loadinternalmerge'), 350 ('internalmerge', filemerge, 'loadinternalmerge'),
312 ('revsetpredicate', revset, 'loadpredicate'), 351 ('revsetpredicate', revset, 'loadpredicate'),
313 ('templatefilter', templatefilters, 'loadfilter'), 352 ('templatefilter', templatefilters, 'loadfilter'),
314 ('templatefunc', templatefuncs, 'loadfunction'), 353 ('templatefunc', templatefuncs, 'loadfunction'),
315 ('templatekeyword', templatekw, 'loadkeyword'), 354 ('templatekeyword', templatekw, 'loadkeyword'),
316 ] 355 ]
317 _loadextra(ui, newindex, extraloaders) 356 with util.timedcm() as stats:
357 _loadextra(ui, newindex, extraloaders)
358 log('> extension registration object loading took %s\n', stats)
359 log('extension loading complete\n')
318 360
319 def _loadextra(ui, newindex, extraloaders): 361 def _loadextra(ui, newindex, extraloaders):
320 for name in _order[newindex:]: 362 for name in _order[newindex:]:
321 module = _extensions[name] 363 module = _extensions[name]
322 if not module: 364 if not module: