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, zero_ninetyone
50
from bzrlib.trace import mutter, warning, log_exception_quietly
53
DEFAULT_PLUGIN_PATH = None
56
def get_default_plugin_path():
57
"""Get the DEFAULT_PLUGIN_PATH"""
58
global DEFAULT_PLUGIN_PATH
59
if DEFAULT_PLUGIN_PATH is None:
60
DEFAULT_PLUGIN_PATH = osutils.pathjoin(config.config_dir(), 'plugins')
61
return DEFAULT_PLUGIN_PATH
64
@deprecated_function(zero_ninetyone)
66
"""Return a dictionary of the plugins."""
68
for name, plugin in plugins().items():
69
result[name] = plugin.module
73
def disable_plugins():
74
"""Disable loading plugins.
76
Future calls to load_plugins() will be ignored.
78
# TODO: jam 20060131 This should probably also disable
83
def _strip_trailing_sep(path):
84
return path.rstrip("\\/")
86
def set_plugins_path():
87
"""Set the path for plugins to be loaded from."""
88
path = os.environ.get('BZR_PLUGIN_PATH',
89
get_default_plugin_path()).split(os.pathsep)
90
# Get rid of trailing slashes, since Python can't handle them when
91
# it tries to import modules.
92
path = map(_strip_trailing_sep, path)
93
# search the plugin path before the bzrlib installed dir
94
path.append(os.path.dirname(_mod_plugins.__file__))
95
_mod_plugins.__path__ = path
100
"""Load bzrlib plugins.
102
The environment variable BZR_PLUGIN_PATH is considered a delimited
103
set of paths to look through. Each entry is searched for *.py
104
files (and whatever other extensions are used in the platform,
107
load_from_dirs() provides the underlying mechanism and is called with
108
the default directory list to provide the normal behaviour.
112
# People can make sure plugins are loaded, they just won't be twice
116
# scan for all plugins in the path.
117
load_from_path(set_plugins_path())
120
def load_from_path(dirs):
121
"""Load bzrlib plugins found in each dir in dirs.
123
Loading a plugin means importing it into the python interpreter.
124
The plugin is expected to make calls to register commands when
125
it's loaded (or perhaps access other hooks in future.)
127
Plugins are loaded into bzrlib.plugins.NAME, and can be found there
128
for future reference.
130
The python module path for bzrlib.plugins will be modified to be 'dirs'.
132
# We need to strip the trailing separators here as well as in the
133
# set_plugins_path function because calling code can pass anything in to
134
# this function, and since it sets plugins.__path__, it should set it to
135
# something that will be valid for Python to use (in case people try to
136
# run "import bzrlib.plugins.PLUGINNAME" after calling this function).
137
_mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
141
mutter('looking for plugins in %s', d)
145
# it might be a zip: try loading from the zip.
150
# backwards compatability: load_from_dirs was the old name
151
# This was changed in 0.15
152
load_from_dirs = load_from_path
155
def load_from_dir(d):
156
"""Load the plugins in directory d."""
157
# Get the list of valid python suffixes for __init__.py?
158
# this includes .py, .pyc, and .pyo (depending on if we are running -O)
159
# but it doesn't include compiled modules (.so, .dll, etc)
160
valid_suffixes = [suffix for suffix, mod_type, flags in imp.get_suffixes()
161
if flags in (imp.PY_SOURCE, imp.PY_COMPILED)]
162
package_entries = ['__init__'+suffix for suffix in valid_suffixes]
164
for f in os.listdir(d):
165
path = osutils.pathjoin(d, f)
166
if os.path.isdir(path):
167
for entry in package_entries:
168
# This directory should be a package, and thus added to
170
if os.path.isfile(osutils.pathjoin(path, entry)):
172
else: # This directory is not a package
175
for suffix_info in imp.get_suffixes():
176
if f.endswith(suffix_info[0]):
177
f = f[:-len(suffix_info[0])]
178
if suffix_info[2] == imp.C_EXTENSION and f.endswith('module'):
179
f = f[:-len('module')]
183
if getattr(_mod_plugins, f, None):
184
mutter('Plugin name %s already loaded', f)
186
# mutter('add plugin name %s', f)
189
for name in plugin_names:
191
exec "import bzrlib.plugins.%s" % name in {}
192
except KeyboardInterrupt:
195
## import pdb; pdb.set_trace()
196
if re.search('\.|-| ', name):
197
warning('Unable to load plugin %r from %r: '
198
'It is not a valid python module name.' % (name, d))
200
warning('Unable to load plugin %r from %r' % (name, d))
201
log_exception_quietly()
204
def load_from_zip(zip_name):
205
"""Load all the plugins in a zip."""
206
valid_suffixes = ('.py', '.pyc', '.pyo') # only python modules/packages
210
index = zip_name.rindex('.zip')
213
archive = zip_name[:index+4]
214
prefix = zip_name[index+5:]
216
mutter('Looking for plugins in %r', zip_name)
218
# use zipfile to get list of files/dirs inside zip
220
z = zipfile.ZipFile(archive)
221
namelist = z.namelist()
223
except zipfile.error:
228
prefix = prefix.replace('\\','/')
229
if prefix[-1] != '/':
232
namelist = [name[ix:]
234
if name.startswith(prefix)]
236
mutter('Names in archive: %r', namelist)
238
for name in namelist:
239
if not name or name.endswith('/'):
242
# '/' is used to separate pathname components inside zip archives
245
head, tail = '', name
247
head, tail = name.rsplit('/',1)
249
# we don't need looking in subdirectories
252
base, suffix = osutils.splitext(tail)
253
if suffix not in valid_suffixes:
256
if base == '__init__':
267
if getattr(_mod_plugins, plugin_name, None):
268
mutter('Plugin name %s already loaded', plugin_name)
272
exec "import bzrlib.plugins.%s" % plugin_name in {}
273
mutter('Load plugin %s from zip %r', plugin_name, zip_name)
274
except KeyboardInterrupt:
277
## import pdb; pdb.set_trace()
278
warning('Unable to load plugin %r from %r'
280
log_exception_quietly()
284
"""Return a dictionary of the plugins.
286
Each item in the dictionary is a PlugIn object.
289
for name, plugin in _mod_plugins.__dict__.items():
290
if isinstance(plugin, types.ModuleType):
291
result[name] = PlugIn(name, plugin)
295
class PluginsHelpIndex(object):
296
"""A help index that returns help topics for plugins."""
299
self.prefix = 'plugins/'
301
def get_topics(self, topic):
302
"""Search for topic in the loaded plugins.
304
This will not trigger loading of new plugins.
306
:param topic: A topic to search for.
307
:return: A list which is either empty or contains a single
308
RegisteredTopic entry.
312
if topic.startswith(self.prefix):
313
topic = topic[len(self.prefix):]
314
plugin_module_name = 'bzrlib.plugins.%s' % topic
316
module = sys.modules[plugin_module_name]
320
return [ModuleHelpTopic(module)]
323
class ModuleHelpTopic(object):
324
"""A help topic which returns the docstring for a module."""
326
def __init__(self, module):
329
:param module: The module for which help should be generated.
333
def get_help_text(self, additional_see_also=None):
334
"""Return a string with the help for this topic.
336
:param additional_see_also: Additional help topics to be
339
if not self.module.__doc__:
340
result = "Plugin '%s' has no docstring.\n" % self.module.__name__
342
result = self.module.__doc__
343
if result[-1] != '\n':
345
# there is code duplicated here and in bzrlib/help_topic.py's
346
# matching Topic code. This should probably be factored in
347
# to a helper function and a common base class.
348
if additional_see_also is not None:
349
see_also = sorted(set(additional_see_also))
353
result += 'See also: '
354
result += ', '.join(see_also)
358
def get_help_topic(self):
359
"""Return the modules help topic - its __name__ after bzrlib.plugins.."""
360
return self.module.__name__[len('bzrlib.plugins.'):]
363
class PlugIn(object):
364
"""The bzrlib representation of a plugin.
366
The PlugIn object provides a way to manipulate a given plugin module.
369
def __init__(self, name, module):
370
"""Construct a plugin for module."""
375
"""Get the path that this plugin was loaded from."""
376
if getattr(self.module, '__path__', None) is not None:
377
return os.path.abspath(self.module.__path__[0])
378
elif getattr(self.module, '__file__', None) is not None:
379
return os.path.abspath(self.module.__file__)
381
return repr(self.module)
384
return "<%s.%s object at %s, name=%s, module=%s>" % (
385
self.__class__.__module__, self.__class__.__name__, id(self),
386
self.name, self.module)
390
def test_suite(self):
391
"""Return the plugin's test suite."""
392
if getattr(self.module, 'test_suite', None) is not None:
393
return self.module.test_suite()
397
def version_info(self):
398
"""Return the plugin's version_tuple or None if unknown."""
399
version_info = getattr(self.module, 'version_info', None)
400
if version_info is not None and len(version_info) == 3:
401
version_info = tuple(version_info) + ('final', 0)
404
def _get__version__(self):
405
version_info = self.version_info()
406
if version_info is None:
408
if version_info[3] == 'final':
409
version_string = '%d.%d.%d' % version_info[:3]
411
version_string = '%d.%d.%d%s%d' % version_info
412
return version_string
414
__version__ = property(_get__version__)