14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Breezy plugin support.
19
Which plugins to load can be configured by setting these environment variables:
21
- BRZ_PLUGIN_PATH: Paths to look for plugins in.
22
- BRZ_DISABLE_PLUGINS: Plugin names to block from being loaded.
23
- BRZ_PLUGINS_AT: Name and paths for plugins to load from specific locations.
25
The interfaces this module exports include:
27
- disable_plugins: Load no plugins and stop future automatic loading.
28
- load_plugins: Load all plugins that can be found in configuration.
29
- describe_plugins: Generate text for each loaded (or failed) plugin.
30
- extend_path: Mechanism by which the plugins package path is set.
31
- plugin_name: Gives unprefixed name of a plugin module.
17
"""bzr python plugin support.
19
When load_plugins() is invoked, any python module in any directory in
20
$BZR_PLUGIN_PATH will be imported. The module will be imported as
21
'bzrlib.plugins.$BASENAME(PLUGIN)'. In the plugin's main body, it should
22
update any bzrlib registries it wants to extend.
33
24
See the plugin-api developer documentation for information about writing
27
BZR_PLUGIN_PATH is also honoured for any plugins imported via
28
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been
32
from __future__ import absolute_import
37
from bzrlib import osutils
44
from .lazy_import import lazy_import
39
from bzrlib.lazy_import import lazy_import
45
40
lazy_import(globals(), """
47
from importlib import util as importlib_util
46
_format_version_tuple,
52
from bzrlib.i18n import gettext
53
from bzrlib import plugins as _mod_plugins
62
_MODULE_PREFIX = "breezy.plugins."
67
def disable_plugins(state=None):
57
DEFAULT_PLUGIN_PATH = None
59
_plugins_disabled = False
63
# Map from plugin name, to list of string warnings about eg plugin
67
def are_plugins_disabled():
68
return _plugins_disabled
71
def disable_plugins():
68
72
"""Disable loading plugins.
70
74
Future calls to load_plugins() will be ignored.
72
:param state: The library state object that records loaded plugins.
75
state = breezy.get_global_state()
79
def load_plugins(path=None, state=None, warn_load_problems=True):
80
"""Load breezy plugins.
82
The environment variable BRZ_PLUGIN_PATH is considered a delimited
83
set of paths to look through. Each entry is searched for `*.py`
84
files (and whatever other extensions are used in the platform,
87
:param path: The list of paths to search for plugins. By default,
88
it is populated from the __path__ of the breezy.plugins package.
89
:param state: The library state object that records loaded plugins.
92
state = breezy.get_global_state()
93
if getattr(state, 'plugins', None) is not None:
94
# People can make sure plugins are loaded, they just won't be twice
98
# Calls back into extend_path() here
99
from breezy.plugins import __path__ as path
101
state.plugin_warnings = {}
102
_load_plugins_from_path(state, path)
103
if (None, 'entrypoints') in _env_plugin_path():
104
_load_plugins_from_entrypoints(state)
105
state.plugins = plugins()
106
if warn_load_problems:
107
for plugin, errors in state.plugin_warnings.items():
109
trace.warning('%s', error)
112
def _load_plugins_from_entrypoints(state):
116
# No pkg_resources, no entrypoints.
119
for ep in pkg_resources.iter_entry_points('breezy.plugin'):
120
fullname = _MODULE_PREFIX + ep.name
121
if fullname in sys.modules:
123
sys.modules[fullname] = ep.load()
126
def plugin_name(module_name):
127
"""Gives unprefixed name from module_name or None."""
128
if module_name.startswith(_MODULE_PREFIX):
129
parts = module_name.split(".")
135
def extend_path(path, name):
136
"""Helper so breezy.plugins can be a sort of namespace package.
138
To be used in similar fashion to pkgutil.extend_path:
140
from breezy.plugins import extend_path
141
__path__ = extend_path(__path__, __name__)
143
Inspects the BRZ_PLUGIN* envvars, sys.path, and the filesystem to find
144
plugins. May mutate sys.modules in order to block plugin loading, and may
145
append a new meta path finder to sys.meta_path for plugins@ loading.
147
Returns a list of paths to import from, as an enhanced object that also
148
contains details of the other configuration used.
150
blocks = _env_disable_plugins()
151
_block_plugins(blocks)
153
extra_details = _env_plugins_at()
154
_install_importer_if_needed(extra_details)
156
paths = _iter_plugin_paths(_env_plugin_path(), path)
158
return _Path(name, blocks, extra_details, paths)
162
"""List type to use as __path__ but containing additional details.
164
Python 3 allows any iterable for __path__ but Python 2 is more fussy.
167
def __init__(self, package_name, blocked, extra, paths):
168
super(_Path, self).__init__(paths)
169
self.package_name = package_name
170
self.blocked_names = blocked
171
self.extra_details = extra
174
return "%s(%r, %r, %r, %s)" % (
175
self.__class__.__name__, self.package_name, self.blocked_names,
176
self.extra_details, list.__repr__(self))
179
def _expect_identifier(name, env_key, env_value):
180
"""Validate given name from envvar is usable as a Python identifier.
182
Returns the name as a native str, or None if it was invalid.
184
Per PEP 3131 this is no longer strictly correct for Python 3, but as MvL
185
didn't include a neat way to check except eval, this enforces ascii.
187
if re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name) is None:
188
trace.warning("Invalid name '%s' in %s='%s'", name, env_key, env_value)
193
def _env_disable_plugins(key='BRZ_DISABLE_PLUGINS'):
194
"""Gives list of names for plugins to disable from environ key."""
196
env = os.environ.get(key)
198
for name in env.split(os.pathsep):
199
name = _expect_identifier(name, key, env)
201
disabled_names.append(name)
202
return disabled_names
205
def _env_plugins_at(key='BRZ_PLUGINS_AT'):
206
"""Gives list of names and paths of specific plugins from environ key."""
208
env = os.environ.get(key)
210
for pair in env.split(os.pathsep):
212
name, path = pair.split('@', 1)
215
name = osutils.basename(path).split('.', 1)[0]
216
name = _expect_identifier(name, key, env)
218
plugin_details.append((name, path))
219
return plugin_details
222
def _env_plugin_path(key='BRZ_PLUGIN_PATH'):
223
"""Gives list of paths and contexts for plugins from environ key.
225
Each entry is either a specific path to load plugins from and the value
226
'path', or None and one of the values 'user', 'core', 'entrypoints', 'site'.
229
env = os.environ.get(key)
234
'entrypoints': False,
237
# Add paths specified by user in order
238
for p in env.split(os.pathsep):
239
flag, name = p[:1], p[1:]
240
if flag in ("+", "-") and name in defaults:
241
if flag == "+" and defaults[name] is not None:
242
path_details.append((None, name))
243
defaults[name] = None
245
path_details.append((p, 'path'))
247
# Add any remaining default paths
248
for name in ('user', 'core', 'entrypoints', 'site'):
250
path_details.append((None, name))
255
def _iter_plugin_paths(paths_from_env, core_paths):
256
"""Generate paths using paths_from_env and core_paths."""
257
# GZ 2017-06-02: This is kinda horrid, should make better.
258
for path, context in paths_from_env:
259
if context == 'path':
261
elif context == 'user':
262
path = get_user_plugin_path()
263
if os.path.isdir(path):
265
elif context == 'core':
266
for path in _get_core_plugin_paths(core_paths):
268
elif context == 'site':
269
for path in _get_site_plugin_paths(sys.path):
270
if os.path.isdir(path):
274
def _install_importer_if_needed(plugin_details):
275
"""Install a meta path finder to handle plugin_details if any."""
277
finder = _PluginsAtFinder(_MODULE_PREFIX, plugin_details)
278
# For Python 3, must insert before default PathFinder to override.
279
sys.meta_path.insert(2, finder)
282
def _load_plugins_from_path(state, paths):
283
"""Do the importing all plugins from paths."""
284
imported_names = set()
285
for name, path in _iter_possible_plugins(paths):
286
if name not in imported_names:
287
msg = _load_plugin_module(name, path)
289
state.plugin_warnings.setdefault(name, []).append(msg)
290
imported_names.add(name)
293
def _block_plugins(names):
294
"""Add names to sys.modules to block future imports."""
296
package_name = _MODULE_PREFIX + name
297
if sys.modules.get(package_name) is not None:
298
trace.mutter("Blocked plugin %s already loaded.", name)
299
sys.modules[package_name] = None
302
def _get_package_init(package_path):
303
"""Get path of __init__ file from package_path or None if not a package."""
304
init_path = osutils.pathjoin(package_path, "__init__.py")
305
if os.path.exists(init_path):
307
init_path = init_path[:-3] + COMPILED_EXT
308
if os.path.exists(init_path):
313
def _iter_possible_plugins(plugin_paths):
314
"""Generate names and paths of possible plugins from plugin_paths."""
315
# Inspect any from BRZ_PLUGINS_AT first.
316
for name, path in getattr(plugin_paths, "extra_details", ()):
318
# Then walk over files and directories in the paths from the package.
319
for path in plugin_paths:
320
if os.path.isfile(path):
321
if path.endswith(".zip"):
322
trace.mutter("Don't yet support loading plugins from zip.")
324
for name, path in _walk_modules(path):
328
def _walk_modules(path):
329
"""Generate name and path of modules and packages on path."""
330
for root, dirs, files in os.walk(path):
334
if f.endswith((".py", COMPILED_EXT)):
335
yield f.rsplit(".", 1)[0], root
339
package_dir = osutils.pathjoin(root, d)
340
fullpath = _get_package_init(package_dir)
341
if fullpath is not None:
343
# Don't descend into subdirectories
347
def describe_plugins(show_paths=False, state=None):
76
global _plugins_disabled
77
_plugins_disabled = True
81
def describe_plugins(show_paths=False):
348
82
"""Generate text description of plugins.
350
Includes both those that have loaded, and those that failed to load.
84
Includes both those that have loaded, and those that failed to
352
:param show_paths: If true, include the plugin path.
353
:param state: The library state object to inspect.
87
:param show_paths: If true,
354
88
:returns: Iterator of text lines (including newlines.)
357
state = breezy.get_global_state()
358
loaded_plugins = getattr(state, 'plugins', {})
359
plugin_warnings = set(getattr(state, 'plugin_warnings', []))
360
all_names = sorted(set(loaded_plugins.keys()).union(plugin_warnings))
90
from inspect import getdoc
91
loaded_plugins = plugins()
92
all_names = sorted(list(set(
93
loaded_plugins.keys() + plugin_warnings.keys())))
361
94
for name in all_names:
362
95
if name in loaded_plugins:
363
96
plugin = loaded_plugins[name]
373
106
yield (" %s\n" % doc)
375
108
yield (" %s\n" % plugin.path())
377
111
yield "%s (failed to load)\n" % name
378
if name in state.plugin_warnings:
379
for line in state.plugin_warnings[name]:
112
if name in plugin_warnings:
113
for line in plugin_warnings[name]:
380
114
yield " ** " + line + '\n'
384
def _get_core_plugin_paths(existing_paths):
385
"""Generate possible locations for plugins based on existing_paths."""
386
if getattr(sys, 'frozen', False):
118
def _strip_trailing_sep(path):
119
return path.rstrip("\\/")
122
def _get_specific_plugin_paths(paths):
123
"""Returns the plugin paths from a string describing the associations.
125
:param paths: A string describing the paths associated with the plugins.
127
:returns: A list of (plugin name, path) tuples.
129
For example, if paths is my_plugin@/test/my-test:her_plugin@/production/her,
130
[('my_plugin', '/test/my-test'), ('her_plugin', '/production/her')]
133
Note that ':' in the example above depends on the os.
138
for spec in paths.split(os.pathsep):
140
name, path = spec.split('@')
142
raise errors.BzrCommandError(gettext(
143
'"%s" is not a valid <plugin_name>@<plugin_path> description ')
145
specs.append((name, path))
149
def set_plugins_path(path=None):
150
"""Set the path for plugins to be loaded from.
152
:param path: The list of paths to search for plugins. By default,
153
path will be determined using get_standard_plugins_path.
154
if path is [], no plugins can be loaded.
157
path = get_standard_plugins_path()
158
_mod_plugins.__path__ = path
159
PluginImporter.reset()
160
# Set up a blacklist for disabled plugins
161
disabled_plugins = os.environ.get('BZR_DISABLE_PLUGINS', None)
162
if disabled_plugins is not None:
163
for name in disabled_plugins.split(os.pathsep):
164
PluginImporter.blacklist.add('bzrlib.plugins.' + name)
165
# Set up a the specific paths for plugins
166
for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
167
'BZR_PLUGINS_AT', None)):
168
PluginImporter.specific_paths[
169
'bzrlib.plugins.%s' % plugin_name] = plugin_path
173
def _append_new_path(paths, new_path):
174
"""Append a new path if it set and not already known."""
175
if new_path is not None and new_path not in paths:
176
paths.append(new_path)
180
def get_core_plugin_path():
182
bzr_exe = bool(getattr(sys, 'frozen', None))
183
if bzr_exe: # expand path for bzr.exe
387
184
# We need to use relative path to system-wide plugin
388
# directory because breezy from standalone brz.exe
185
# directory because bzrlib from standalone bzr.exe
389
186
# could be imported by another standalone program
390
# (e.g. brz-config; or TortoiseBzr/Olive if/when they
187
# (e.g. bzr-config; or TortoiseBzr/Olive if/when they
391
188
# will become standalone exe). [bialix 20071123]
392
189
# __file__ typically is
393
# C:\Program Files\Bazaar\lib\library.zip\breezy\plugin.pyc
190
# C:\Program Files\Bazaar\lib\library.zip\bzrlib\plugin.pyc
394
191
# then plugins directory is
395
192
# C:\Program Files\Bazaar\plugins
396
193
# so relative path is ../../../plugins
397
yield osutils.abspath(osutils.pathjoin(
398
osutils.dirname(__file__), '../../../plugins'))
194
core_path = osutils.abspath(osutils.pathjoin(
195
osutils.dirname(__file__), '../../../plugins'))
399
196
else: # don't look inside library.zip
400
for path in existing_paths:
404
def _get_site_plugin_paths(sys_paths):
405
"""Generate possible locations for plugins from given sys_paths."""
406
for path in sys_paths:
407
if os.path.basename(path) in ('dist-packages', 'site-packages'):
408
yield osutils.pathjoin(path, 'breezy', 'plugins')
197
# search the plugin path before the bzrlib installed dir
198
core_path = os.path.dirname(_mod_plugins.__file__)
202
def get_site_plugin_path():
203
"""Returns the path for the site installed plugins."""
204
if sys.platform == 'win32':
205
# We don't have (yet) a good answer for windows since that is certainly
206
# related to the way we build the installers. -- vila20090821
210
from distutils.sysconfig import get_python_lib
212
# If distutuils is not available, we just don't know where they are
215
site_path = osutils.pathjoin(get_python_lib(), 'bzrlib', 'plugins')
411
219
def get_user_plugin_path():
412
return osutils.pathjoin(bedding.config_dir(), 'plugins')
415
def record_plugin_warning(warning_message):
220
return osutils.pathjoin(config.config_dir(), 'plugins')
223
def get_standard_plugins_path():
224
"""Determine a plugin path suitable for general use."""
225
# Ad-Hoc default: core is not overriden by site but user can overrides both
226
# The rationale is that:
227
# - 'site' comes last, because these plugins should always be available and
228
# are supposed to be in sync with the bzr installed on site.
229
# - 'core' comes before 'site' so that running bzr from sources or a user
230
# installed version overrides the site version.
231
# - 'user' comes first, because... user is always right.
232
# - the above rules clearly defines which plugin version will be loaded if
233
# several exist. Yet, it is sometimes desirable to disable some directory
234
# so that a set of plugins is disabled as once. This can be done via
235
# -site, -core, -user.
237
env_paths = os.environ.get('BZR_PLUGIN_PATH', '+user').split(os.pathsep)
238
defaults = ['+core', '+site']
240
# The predefined references
241
refs = dict(core=get_core_plugin_path(),
242
site=get_site_plugin_path(),
243
user=get_user_plugin_path())
245
# Unset paths that should be removed
246
for k,v in refs.iteritems():
248
# defaults can never mention removing paths as that will make it
249
# impossible for the user to revoke these removals.
250
if removed in env_paths:
251
env_paths.remove(removed)
256
for p in env_paths + defaults:
257
if p.startswith('+'):
258
# Resolve references if they are known
262
# Leave them untouched so user can still use paths starting
265
_append_new_path(paths, p)
267
# Get rid of trailing slashes, since Python can't handle them when
268
# it tries to import modules.
269
paths = map(_strip_trailing_sep, paths)
273
def load_plugins(path=None):
274
"""Load bzrlib plugins.
276
The environment variable BZR_PLUGIN_PATH is considered a delimited
277
set of paths to look through. Each entry is searched for `*.py`
278
files (and whatever other extensions are used in the platform,
281
load_from_path() provides the underlying mechanism and is called with
282
the default directory list to provide the normal behaviour.
284
:param path: The list of paths to search for plugins. By default,
285
path will be determined using get_standard_plugins_path.
286
if path is [], no plugins can be loaded.
290
# People can make sure plugins are loaded, they just won't be twice
294
# scan for all plugins in the path.
295
load_from_path(set_plugins_path(path))
298
def load_from_path(dirs):
299
"""Load bzrlib plugins found in each dir in dirs.
301
Loading a plugin means importing it into the python interpreter.
302
The plugin is expected to make calls to register commands when
303
it's loaded (or perhaps access other hooks in future.)
305
Plugins are loaded into bzrlib.plugins.NAME, and can be found there
306
for future reference.
308
The python module path for bzrlib.plugins will be modified to be 'dirs'.
310
# Explicitly load the plugins with a specific path
311
for fullname, path in PluginImporter.specific_paths.iteritems():
312
name = fullname[len('bzrlib.plugins.'):]
313
_load_plugin_module(name, path)
315
# We need to strip the trailing separators here as well as in the
316
# set_plugins_path function because calling code can pass anything in to
317
# this function, and since it sets plugins.__path__, it should set it to
318
# something that will be valid for Python to use (in case people try to
319
# run "import bzrlib.plugins.PLUGINNAME" after calling this function).
320
_mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
324
trace.mutter('looking for plugins in %s', d)
329
# backwards compatability: load_from_dirs was the old name
330
# This was changed in 0.15
331
load_from_dirs = load_from_path
334
def _find_plugin_module(dir, name):
335
"""Check if there is a valid python module that can be loaded as a plugin.
337
:param dir: The directory where the search is performed.
338
:param path: An existing file path, either a python file or a package
341
:return: (name, path, description) name is the module name, path is the
342
file to load and description is the tuple returned by
345
path = osutils.pathjoin(dir, name)
346
if os.path.isdir(path):
347
# Check for a valid __init__.py file, valid suffixes depends on -O and
348
# can be .py, .pyc and .pyo
349
for suffix, mode, kind in imp.get_suffixes():
350
if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
351
# We don't recognize compiled modules (.so, .dll, etc)
353
init_path = osutils.pathjoin(path, '__init__' + suffix)
354
if os.path.isfile(init_path):
355
return name, init_path, (suffix, mode, kind)
357
for suffix, mode, kind in imp.get_suffixes():
358
if name.endswith(suffix):
359
# Clean up the module name
360
name = name[:-len(suffix)]
361
if kind == imp.C_EXTENSION and name.endswith('module'):
362
name = name[:-len('module')]
363
return name, path, (suffix, mode, kind)
364
# There is no python module here
365
return None, None, (None, None, None)
368
def record_plugin_warning(plugin_name, warning_message):
416
369
trace.mutter(warning_message)
417
return warning_message
370
plugin_warnings.setdefault(plugin_name, []).append(warning_message)
420
373
def _load_plugin_module(name, dir):
421
"""Load plugin by name.
374
"""Load plugin name from dir.
423
:param name: The plugin name in the breezy.plugins namespace.
376
:param name: The plugin name in the bzrlib.plugins namespace.
424
377
:param dir: The directory the plugin is loaded from for error messages.
426
if _MODULE_PREFIX + name in sys.modules:
379
if ('bzrlib.plugins.%s' % name) in PluginImporter.blacklist:
429
__import__(_MODULE_PREFIX + name)
430
except errors.IncompatibleVersion as e:
382
exec "import bzrlib.plugins.%s" % name in {}
383
except KeyboardInterrupt:
385
except errors.IncompatibleAPI, e:
431
386
warning_message = (
432
"Unable to load plugin %r. It supports %s "
433
"versions %r but the current version is %s" %
434
(name, e.api.__name__, e.wanted, e.current))
435
return record_plugin_warning(warning_message)
436
except Exception as e:
387
"Unable to load plugin %r. It requested API version "
388
"%s of module %s but the minimum exported version is %s, and "
389
"the maximum is %s" %
390
(name, e.wanted, e.api, e.minimum, e.current))
391
record_plugin_warning(name, warning_message)
393
trace.warning("%s" % e)
394
if re.search('\.|-| ', name):
395
sanitised_name = re.sub('[-. ]', '_', name)
396
if sanitised_name.startswith('bzr_'):
397
sanitised_name = sanitised_name[len('bzr_'):]
398
trace.warning("Unable to load %r in %r as a plugin because the "
399
"file path isn't a valid module name; try renaming "
400
"it to %r." % (name, dir, sanitised_name))
402
record_plugin_warning(
404
'Unable to load plugin %r from %r' % (name, dir))
437
405
trace.log_exception_quietly()
438
406
if 'error' in debug.debug_flags:
439
407
trace.print_exception(sys.exc_info(), sys.stderr)
440
# GZ 2017-06-02: Move this name checking up a level, no point trying
441
# to import things with bad names.
442
if re.search('\\.|-| ', name):
443
sanitised_name = re.sub('[-. ]', '_', name)
444
if sanitised_name.startswith('brz_'):
445
sanitised_name = sanitised_name[len('brz_'):]
446
trace.warning("Unable to load %r in %r as a plugin because the "
447
"file path isn't a valid module name; try renaming "
448
"it to %r." % (name, dir, sanitised_name))
450
return record_plugin_warning(
451
'Unable to load plugin %r from %r: %s' % (name, dir, e))
410
def load_from_dir(d):
411
"""Load the plugins in directory d.
413
d must be in the plugins module path already.
414
This function is called once for each directory in the module path.
417
for p in os.listdir(d):
418
name, path, desc = _find_plugin_module(d, p)
420
if name == '__init__':
421
# We do nothing with the __init__.py file in directories from
422
# the bzrlib.plugins module path, we may want to, one day
424
continue # We don't load __init__.py in the plugins dirs
425
elif getattr(_mod_plugins, name, None) is not None:
426
# The module has already been loaded from another directory
427
# during a previous call.
428
# FIXME: There should be a better way to report masked plugins
430
trace.mutter('Plugin name %s already loaded', name)
432
plugin_names.add(name)
434
for name in plugin_names:
435
_load_plugin_module(name, d)
602
573
version_info = getattr(self.module, 'version_info', None)
603
574
if version_info is not None:
605
if isinstance(version_info, str):
576
if isinstance(version_info, types.StringType):
606
577
version_info = version_info.split('.')
607
578
elif len(version_info) == 3:
608
579
version_info = tuple(version_info) + ('final', 0)
610
581
# The given version_info isn't even iteratible
611
582
trace.log_exception_quietly()
612
583
version_info = (version_info,)
613
584
return version_info
616
def __version__(self):
586
def _get__version__(self):
617
587
version_info = self.version_info()
618
588
if version_info is None or len(version_info) == 0:
621
version_string = breezy._format_version_tuple(version_info)
622
except (ValueError, TypeError, IndexError):
591
version_string = _format_version_tuple(version_info)
592
except (ValueError, TypeError, IndexError), e:
623
593
trace.log_exception_quietly()
624
# Try to show something for the version anyway
594
# try to return something usefull for bad plugins, in stead of
625
596
version_string = '.'.join(map(str, version_info))
626
597
return version_string
629
class _PluginsAtFinder(object):
630
"""Meta path finder to support BRZ_PLUGINS_AT configuration."""
632
def __init__(self, prefix, names_and_paths):
634
self.names_to_path = dict((prefix + n, p) for n, p in names_and_paths)
637
return "<%s %r>" % (self.__class__.__name__, self.prefix)
639
def find_spec(self, fullname, paths, target=None):
640
"""New module spec returning find method."""
641
if fullname not in self.names_to_path:
643
path = self.names_to_path[fullname]
644
if os.path.isdir(path):
645
path = _get_package_init(path)
647
# GZ 2017-06-02: Any reason to block loading of the name from
648
# further down the path like this?
649
raise ImportError("Not loading namespace package %s as %s" % (
651
return importlib_util.spec_from_file_location(fullname, path)
653
def find_module(self, fullname, path):
654
"""Old PEP 302 import hook find_module method."""
655
if fullname not in self.names_to_path:
657
return _LegacyLoader(self.names_to_path[fullname])
660
class _LegacyLoader(object):
661
"""Source loader implementation for Python versions without importlib."""
663
def __init__(self, filepath):
664
self.filepath = filepath
667
return "<%s %r>" % (self.__class__.__name__, self.filepath)
599
__version__ = property(_get__version__)
602
class _PluginImporter(object):
603
"""An importer tailored to bzr specific needs.
605
This is a singleton that takes care of:
606
- disabled plugins specified in 'blacklist',
607
- plugins that needs to be loaded from specific directories.
614
self.blacklist = set()
615
self.specific_paths = {}
617
def find_module(self, fullname, parent_path=None):
618
"""Search a plugin module.
620
Disabled plugins raise an import error, plugins with specific paths
621
returns a specific loader.
623
:return: None if the plugin doesn't need special handling, self
626
if not fullname.startswith('bzrlib.plugins.'):
628
if fullname in self.blacklist:
629
raise ImportError('%s is disabled' % fullname)
630
if fullname in self.specific_paths:
669
634
def load_module(self, fullname):
670
635
"""Load a plugin from a specific directory (or file)."""
671
plugin_path = self.filepath
636
# We are called only for specific paths
637
plugin_path = self.specific_paths[fullname]
672
638
loading_path = None
673
639
if os.path.isdir(plugin_path):
674
init_path = _get_package_init(plugin_path)
675
if init_path is not None:
676
loading_path = plugin_path
679
kind = imp.PKG_DIRECTORY
640
for suffix, mode, kind in imp.get_suffixes():
641
if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
642
# We don't recognize compiled modules (.so, .dll, etc)
644
init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
645
if os.path.isfile(init_path):
646
# We've got a module here and load_module needs specific
648
loading_path = plugin_path
651
kind = imp.PKG_DIRECTORY
681
654
for suffix, mode, kind in imp.get_suffixes():
682
655
if plugin_path.endswith(suffix):