1
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""bzr python plugin support.
20
When load_plugins() is invoked, any python module in any directory in
21
$BZR_PLUGIN_PATH will be imported. The module will be imported as
22
'bzrlib.plugins.$BASENAME(PLUGIN)'. In the plugin's main body, it should
23
update any bzrlib registries it wants to extend; for example, to add new
24
commands, import bzrlib.commands and add your new command to the plugin_cmds
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
35
from bzrlib.lazy_import import lazy_import
36
lazy_import(globals(), """
46
from bzrlib import plugins as _mod_plugins
49
from bzrlib.symbol_versioning import deprecated_function, one_three
52
DEFAULT_PLUGIN_PATH = None
55
# XXX: copied and pasted from config.py to avoid importing configobj before
56
# we've installed lazy_regex.
58
"""Return per-user configuration directory.
60
By default this is ~/.bazaar/
62
TODO: Global option --config-dir to override this.
64
base = os.environ.get('BZR_HOME', None)
65
if sys.platform == 'win32':
67
base = win32utils.get_appdata_location_unicode()
69
base = os.environ.get('HOME', None)
71
raise errors.BzrError('You must have one of BZR_HOME, APPDATA,'
73
return osutils.pathjoin(base, 'bazaar', '2.0')
75
# cygwin, linux, and darwin all have a $HOME directory
77
base = os.path.expanduser("~")
78
return osutils.pathjoin(base, ".bazaar")
81
def get_default_plugin_path():
82
"""Get the DEFAULT_PLUGIN_PATH"""
83
global DEFAULT_PLUGIN_PATH
84
if DEFAULT_PLUGIN_PATH is None:
85
DEFAULT_PLUGIN_PATH = osutils.pathjoin(_config_dir(), 'plugins')
86
return DEFAULT_PLUGIN_PATH
89
def disable_plugins():
90
"""Disable loading plugins.
92
Future calls to load_plugins() will be ignored.
94
# TODO: jam 20060131 This should probably also disable
100
def _strip_trailing_sep(path):
101
return path.rstrip("\\/")
104
def set_plugins_path():
105
"""Set the path for plugins to be loaded from."""
106
path = os.environ.get('BZR_PLUGIN_PATH',
107
get_default_plugin_path()).split(os.pathsep)
108
bzr_exe = bool(getattr(sys, 'frozen', None))
109
if bzr_exe: # expand path for bzr.exe
110
# We need to use relative path to system-wide plugin
111
# directory because bzrlib from standalone bzr.exe
112
# could be imported by another standalone program
113
# (e.g. bzr-config; or TortoiseBzr/Olive if/when they
114
# will become standalone exe). [bialix 20071123]
115
# __file__ typically is
116
# C:\Program Files\Bazaar\lib\library.zip\bzrlib\plugin.pyc
117
# then plugins directory is
118
# C:\Program Files\Bazaar\plugins
119
# so relative path is ../../../plugins
120
path.append(osutils.abspath(osutils.pathjoin(
121
osutils.dirname(__file__), '../../../plugins')))
122
# Get rid of trailing slashes, since Python can't handle them when
123
# it tries to import modules.
124
path = map(_strip_trailing_sep, path)
125
if not bzr_exe: # don't look inside library.zip
126
# search the plugin path before the bzrlib installed dir
127
path.append(os.path.dirname(_mod_plugins.__file__))
128
_mod_plugins.__path__ = path
133
"""Load bzrlib plugins.
135
The environment variable BZR_PLUGIN_PATH is considered a delimited
136
set of paths to look through. Each entry is searched for *.py
137
files (and whatever other extensions are used in the platform,
140
load_from_dirs() provides the underlying mechanism and is called with
141
the default directory list to provide the normal behaviour.
145
# People can make sure plugins are loaded, they just won't be twice
149
# scan for all plugins in the path.
150
load_from_path(set_plugins_path())
153
def load_from_path(dirs):
154
"""Load bzrlib plugins found in each dir in dirs.
156
Loading a plugin means importing it into the python interpreter.
157
The plugin is expected to make calls to register commands when
158
it's loaded (or perhaps access other hooks in future.)
160
Plugins are loaded into bzrlib.plugins.NAME, and can be found there
161
for future reference.
163
The python module path for bzrlib.plugins will be modified to be 'dirs'.
165
# We need to strip the trailing separators here as well as in the
166
# set_plugins_path function because calling code can pass anything in to
167
# this function, and since it sets plugins.__path__, it should set it to
168
# something that will be valid for Python to use (in case people try to
169
# run "import bzrlib.plugins.PLUGINNAME" after calling this function).
170
_mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
174
trace.mutter('looking for plugins in %s', d)
179
# backwards compatability: load_from_dirs was the old name
180
# This was changed in 0.15
181
load_from_dirs = load_from_path
184
def load_from_dir(d):
185
"""Load the plugins in directory d."""
186
# Get the list of valid python suffixes for __init__.py?
187
# this includes .py, .pyc, and .pyo (depending on if we are running -O)
188
# but it doesn't include compiled modules (.so, .dll, etc)
189
valid_suffixes = [suffix for suffix, mod_type, flags in imp.get_suffixes()
190
if flags in (imp.PY_SOURCE, imp.PY_COMPILED)]
191
package_entries = ['__init__'+suffix for suffix in valid_suffixes]
193
for f in os.listdir(d):
194
path = osutils.pathjoin(d, f)
195
if os.path.isdir(path):
196
for entry in package_entries:
197
# This directory should be a package, and thus added to
199
if os.path.isfile(osutils.pathjoin(path, entry)):
201
else: # This directory is not a package
204
for suffix_info in imp.get_suffixes():
205
if f.endswith(suffix_info[0]):
206
f = f[:-len(suffix_info[0])]
207
if suffix_info[2] == imp.C_EXTENSION and f.endswith('module'):
208
f = f[:-len('module')]
212
if getattr(_mod_plugins, f, None):
213
trace.mutter('Plugin name %s already loaded', f)
215
# trace.mutter('add plugin name %s', f)
218
for name in plugin_names:
220
exec "import bzrlib.plugins.%s" % name in {}
221
except KeyboardInterrupt:
224
## import pdb; pdb.set_trace()
225
if re.search('\.|-| ', name):
226
sanitised_name = re.sub('[-. ]', '_', name)
227
trace.warning("Unable to load %r in %r as a plugin because file path"
228
" isn't a valid module name; try renaming it to %r."
229
% (name, d, sanitised_name))
231
trace.warning('Unable to load plugin %r from %r' % (name, d))
232
trace.log_exception_quietly()
235
@deprecated_function(one_three)
236
def load_from_zip(zip_name):
237
"""Load all the plugins in a zip."""
238
valid_suffixes = ('.py', '.pyc', '.pyo') # only python modules/packages
241
index = zip_name.rindex('.zip')
244
archive = zip_name[:index+4]
245
prefix = zip_name[index+5:]
247
trace.mutter('Looking for plugins in %r', zip_name)
249
# use zipfile to get list of files/dirs inside zip
251
z = zipfile.ZipFile(archive)
252
namelist = z.namelist()
254
except zipfile.error:
259
prefix = prefix.replace('\\','/')
260
if prefix[-1] != '/':
263
namelist = [name[ix:]
265
if name.startswith(prefix)]
267
trace.mutter('Names in archive: %r', namelist)
269
for name in namelist:
270
if not name or name.endswith('/'):
273
# '/' is used to separate pathname components inside zip archives
276
head, tail = '', name
278
head, tail = name.rsplit('/',1)
280
# we don't need looking in subdirectories
283
base, suffix = osutils.splitext(tail)
284
if suffix not in valid_suffixes:
287
if base == '__init__':
298
if getattr(_mod_plugins, plugin_name, None):
299
trace.mutter('Plugin name %s already loaded', plugin_name)
303
exec "import bzrlib.plugins.%s" % plugin_name in {}
304
trace.mutter('Load plugin %s from zip %r', plugin_name, zip_name)
305
except KeyboardInterrupt:
308
## import pdb; pdb.set_trace()
309
trace.warning('Unable to load plugin %r from %r'
311
trace.log_exception_quietly()
315
"""Return a dictionary of the plugins.
317
Each item in the dictionary is a PlugIn object.
320
for name, plugin in _mod_plugins.__dict__.items():
321
if isinstance(plugin, types.ModuleType):
322
result[name] = PlugIn(name, plugin)
326
class PluginsHelpIndex(object):
327
"""A help index that returns help topics for plugins."""
330
self.prefix = 'plugins/'
332
def get_topics(self, topic):
333
"""Search for topic in the loaded plugins.
335
This will not trigger loading of new plugins.
337
:param topic: A topic to search for.
338
:return: A list which is either empty or contains a single
339
RegisteredTopic entry.
343
if topic.startswith(self.prefix):
344
topic = topic[len(self.prefix):]
345
plugin_module_name = 'bzrlib.plugins.%s' % topic
347
module = sys.modules[plugin_module_name]
351
return [ModuleHelpTopic(module)]
354
class ModuleHelpTopic(object):
355
"""A help topic which returns the docstring for a module."""
357
def __init__(self, module):
360
:param module: The module for which help should be generated.
364
def get_help_text(self, additional_see_also=None):
365
"""Return a string with the help for this topic.
367
:param additional_see_also: Additional help topics to be
370
if not self.module.__doc__:
371
result = "Plugin '%s' has no docstring.\n" % self.module.__name__
373
result = self.module.__doc__
374
if result[-1] != '\n':
376
# there is code duplicated here and in bzrlib/help_topic.py's
377
# matching Topic code. This should probably be factored in
378
# to a helper function and a common base class.
379
if additional_see_also is not None:
380
see_also = sorted(set(additional_see_also))
384
result += 'See also: '
385
result += ', '.join(see_also)
389
def get_help_topic(self):
390
"""Return the modules help topic - its __name__ after bzrlib.plugins.."""
391
return self.module.__name__[len('bzrlib.plugins.'):]
394
class PlugIn(object):
395
"""The bzrlib representation of a plugin.
397
The PlugIn object provides a way to manipulate a given plugin module.
400
def __init__(self, name, module):
401
"""Construct a plugin for module."""
406
"""Get the path that this plugin was loaded from."""
407
if getattr(self.module, '__path__', None) is not None:
408
return os.path.abspath(self.module.__path__[0])
409
elif getattr(self.module, '__file__', None) is not None:
410
path = os.path.abspath(self.module.__file__)
411
if path[-4:] in ('.pyc', '.pyo'):
412
pypath = path[:-4] + '.py'
413
if os.path.isfile(pypath):
417
return repr(self.module)
420
return "<%s.%s object at %s, name=%s, module=%s>" % (
421
self.__class__.__module__, self.__class__.__name__, id(self),
422
self.name, self.module)
426
def test_suite(self):
427
"""Return the plugin's test suite."""
428
if getattr(self.module, 'test_suite', None) is not None:
429
return self.module.test_suite()
433
def version_info(self):
434
"""Return the plugin's version_tuple or None if unknown."""
435
version_info = getattr(self.module, 'version_info', None)
436
if version_info is not None and len(version_info) == 3:
437
version_info = tuple(version_info) + ('final', 0)
440
def _get__version__(self):
441
version_info = self.version_info()
442
if version_info is None:
444
if version_info[3] == 'final':
445
version_string = '%d.%d.%d' % version_info[:3]
447
version_string = '%d.%d.%d%s%d' % version_info
448
return version_string
450
__version__ = property(_get__version__)