~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

  • Committer: Martin Pool
  • Date: 2009-03-24 05:21:02 UTC
  • mfrom: (4192 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4202.
  • Revision ID: mbp@sourcefrog.net-20090324052102-8kk087b32tep3d9h
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
20
20
When load_plugins() is invoked, any python module in any directory in
21
21
$BZR_PLUGIN_PATH will be imported.  The module will be imported as
22
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
25
 
variable.
 
23
update any bzrlib registries it wants to extend.
 
24
 
 
25
See the plugin-api developer documentation for information about writing
 
26
plugins.
26
27
 
27
28
BZR_PLUGIN_PATH is also honoured for any plugins imported via
28
 
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been 
 
29
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been
29
30
called.
30
31
"""
31
32
 
32
33
import os
33
34
import sys
34
35
 
 
36
from bzrlib import osutils
 
37
 
35
38
from bzrlib.lazy_import import lazy_import
 
39
 
36
40
lazy_import(globals(), """
37
41
import imp
38
42
import re
39
43
import types
40
 
import zipfile
41
44
 
42
45
from bzrlib import (
 
46
    _format_version_tuple,
43
47
    config,
44
48
    debug,
45
 
    osutils,
 
49
    errors,
46
50
    trace,
47
51
    )
48
52
from bzrlib import plugins as _mod_plugins
49
53
""")
50
54
 
51
 
from bzrlib.symbol_versioning import deprecated_function, one_three
52
 
from bzrlib.trace import mutter, warning, log_exception_quietly
 
55
from bzrlib.symbol_versioning import deprecated_function
53
56
 
54
57
 
55
58
DEFAULT_PLUGIN_PATH = None
68
71
 
69
72
    Future calls to load_plugins() will be ignored.
70
73
    """
71
 
    # TODO: jam 20060131 This should probably also disable
72
 
    #       load_from_dirs()
73
 
    global _loaded
74
 
    _loaded = True
 
74
    load_plugins([])
75
75
 
76
76
 
77
77
def _strip_trailing_sep(path):
78
78
    return path.rstrip("\\/")
79
79
 
80
80
 
81
 
def set_plugins_path():
82
 
    """Set the path for plugins to be loaded from."""
 
81
def set_plugins_path(path=None):
 
82
    """Set the path for plugins to be loaded from.
 
83
 
 
84
    :param path: The list of paths to search for plugins.  By default,
 
85
        path will be determined using get_standard_plugins_path.
 
86
        if path is [], no plugins can be loaded.
 
87
    """
 
88
    if path is None:
 
89
        path = get_standard_plugins_path()
 
90
    _mod_plugins.__path__ = path
 
91
    return path
 
92
 
 
93
 
 
94
def get_standard_plugins_path():
 
95
    """Determine a plugin path suitable for general use."""
83
96
    path = os.environ.get('BZR_PLUGIN_PATH',
84
97
                          get_default_plugin_path()).split(os.pathsep)
 
98
    # Get rid of trailing slashes, since Python can't handle them when
 
99
    # it tries to import modules.
 
100
    path = map(_strip_trailing_sep, path)
85
101
    bzr_exe = bool(getattr(sys, 'frozen', None))
86
102
    if bzr_exe:    # expand path for bzr.exe
87
103
        # We need to use relative path to system-wide plugin
96
112
        # so relative path is ../../../plugins
97
113
        path.append(osutils.abspath(osutils.pathjoin(
98
114
            osutils.dirname(__file__), '../../../plugins')))
99
 
    # Get rid of trailing slashes, since Python can't handle them when
100
 
    # it tries to import modules.
101
 
    path = map(_strip_trailing_sep, path)
102
115
    if not bzr_exe:     # don't look inside library.zip
103
116
        # search the plugin path before the bzrlib installed dir
104
117
        path.append(os.path.dirname(_mod_plugins.__file__))
115
128
                    'plugins')
116
129
            if archless_path not in path:
117
130
                path.append(archless_path)
118
 
    _mod_plugins.__path__ = path
119
131
    return path
120
132
 
121
133
 
122
 
def load_plugins():
 
134
def load_plugins(path=None):
123
135
    """Load bzrlib plugins.
124
136
 
125
137
    The environment variable BZR_PLUGIN_PATH is considered a delimited
129
141
 
130
142
    load_from_dirs() provides the underlying mechanism and is called with
131
143
    the default directory list to provide the normal behaviour.
 
144
 
 
145
    :param path: The list of paths to search for plugins.  By default,
 
146
        path will be determined using get_standard_plugins_path.
 
147
        if path is [], no plugins can be loaded.
132
148
    """
133
149
    global _loaded
134
150
    if _loaded:
137
153
    _loaded = True
138
154
 
139
155
    # scan for all plugins in the path.
140
 
    load_from_path(set_plugins_path())
 
156
    load_from_path(set_plugins_path(path))
141
157
 
142
158
 
143
159
def load_from_path(dirs):
161
177
    for d in dirs:
162
178
        if not d:
163
179
            continue
164
 
        mutter('looking for plugins in %s', d)
 
180
        trace.mutter('looking for plugins in %s', d)
165
181
        if os.path.isdir(d):
166
182
            load_from_dir(d)
167
183
 
172
188
 
173
189
 
174
190
def load_from_dir(d):
175
 
    """Load the plugins in directory d."""
 
191
    """Load the plugins in directory d.
 
192
 
 
193
    d must be in the plugins module path already.
 
194
    """
176
195
    # Get the list of valid python suffixes for __init__.py?
177
196
    # this includes .py, .pyc, and .pyo (depending on if we are running -O)
178
197
    # but it doesn't include compiled modules (.so, .dll, etc)
199
218
                    break
200
219
            else:
201
220
                continue
202
 
        if getattr(_mod_plugins, f, None):
203
 
            mutter('Plugin name %s already loaded', f)
 
221
        if f == '__init__':
 
222
            continue # We don't load __init__.py again in the plugin dir
 
223
        elif getattr(_mod_plugins, f, None):
 
224
            trace.mutter('Plugin name %s already loaded', f)
204
225
        else:
205
 
            # mutter('add plugin name %s', f)
 
226
            # trace.mutter('add plugin name %s', f)
206
227
            plugin_names.add(f)
207
 
    
 
228
 
208
229
    for name in plugin_names:
209
230
        try:
210
231
            exec "import bzrlib.plugins.%s" % name in {}
211
232
        except KeyboardInterrupt:
212
233
            raise
 
234
        except errors.IncompatibleAPI, e:
 
235
            trace.warning("Unable to load plugin %r. It requested API version "
 
236
                "%s of module %s but the minimum exported version is %s, and "
 
237
                "the maximum is %s" %
 
238
                (name, e.wanted, e.api, e.minimum, e.current))
213
239
        except Exception, e:
 
240
            trace.warning("%s" % e)
214
241
            ## import pdb; pdb.set_trace()
215
242
            if re.search('\.|-| ', name):
216
243
                sanitised_name = re.sub('[-. ]', '_', name)
217
244
                if sanitised_name.startswith('bzr_'):
218
245
                    sanitised_name = sanitised_name[len('bzr_'):]
219
 
                warning("Unable to load %r in %r as a plugin because the "
 
246
                trace.warning("Unable to load %r in %r as a plugin because the "
220
247
                        "file path isn't a valid module name; try renaming "
221
248
                        "it to %r." % (name, d, sanitised_name))
222
249
            else:
223
 
                warning('Unable to load plugin %r from %r' % (name, d))
224
 
            log_exception_quietly()
225
 
            if 'error' in debug.debug_flags:
226
 
                trace.print_exception(sys.exc_info(), sys.stderr)
227
 
 
228
 
 
229
 
@deprecated_function(one_three)
230
 
def load_from_zip(zip_name):
231
 
    """Load all the plugins in a zip."""
232
 
    valid_suffixes = ('.py', '.pyc', '.pyo')    # only python modules/packages
233
 
                                                # is allowed
234
 
    try:
235
 
        index = zip_name.rindex('.zip')
236
 
    except ValueError:
237
 
        return
238
 
    archive = zip_name[:index+4]
239
 
    prefix = zip_name[index+5:]
240
 
 
241
 
    mutter('Looking for plugins in %r', zip_name)
242
 
 
243
 
    # use zipfile to get list of files/dirs inside zip
244
 
    try:
245
 
        z = zipfile.ZipFile(archive)
246
 
        namelist = z.namelist()
247
 
        z.close()
248
 
    except zipfile.error:
249
 
        # not a valid zip
250
 
        return
251
 
 
252
 
    if prefix:
253
 
        prefix = prefix.replace('\\','/')
254
 
        if prefix[-1] != '/':
255
 
            prefix += '/'
256
 
        ix = len(prefix)
257
 
        namelist = [name[ix:]
258
 
                    for name in namelist
259
 
                    if name.startswith(prefix)]
260
 
 
261
 
    mutter('Names in archive: %r', namelist)
262
 
    
263
 
    for name in namelist:
264
 
        if not name or name.endswith('/'):
265
 
            continue
266
 
    
267
 
        # '/' is used to separate pathname components inside zip archives
268
 
        ix = name.rfind('/')
269
 
        if ix == -1:
270
 
            head, tail = '', name
271
 
        else:
272
 
            head, tail = name.rsplit('/',1)
273
 
        if '/' in head:
274
 
            # we don't need looking in subdirectories
275
 
            continue
276
 
    
277
 
        base, suffix = osutils.splitext(tail)
278
 
        if suffix not in valid_suffixes:
279
 
            continue
280
 
    
281
 
        if base == '__init__':
282
 
            # package
283
 
            plugin_name = head
284
 
        elif head == '':
285
 
            # module
286
 
            plugin_name = base
287
 
        else:
288
 
            continue
289
 
    
290
 
        if not plugin_name:
291
 
            continue
292
 
        if getattr(_mod_plugins, plugin_name, None):
293
 
            mutter('Plugin name %s already loaded', plugin_name)
294
 
            continue
295
 
    
296
 
        try:
297
 
            exec "import bzrlib.plugins.%s" % plugin_name in {}
298
 
            mutter('Load plugin %s from zip %r', plugin_name, zip_name)
299
 
        except KeyboardInterrupt:
300
 
            raise
301
 
        except Exception, e:
302
 
            ## import pdb; pdb.set_trace()
303
 
            warning('Unable to load plugin %r from %r'
304
 
                    % (name, zip_name))
305
 
            log_exception_quietly()
 
250
                trace.warning('Unable to load plugin %r from %r' % (name, d))
 
251
            trace.log_exception_quietly()
306
252
            if 'error' in debug.debug_flags:
307
253
                trace.print_exception(sys.exc_info(), sys.stderr)
308
254
 
309
255
 
310
256
def plugins():
311
257
    """Return a dictionary of the plugins.
312
 
    
 
258
 
313
259
    Each item in the dictionary is a PlugIn object.
314
260
    """
315
261
    result = {}
357
303
        """
358
304
        self.module = module
359
305
 
360
 
    def get_help_text(self, additional_see_also=None):
 
306
    def get_help_text(self, additional_see_also=None, verbose=True):
361
307
        """Return a string with the help for this topic.
362
308
 
363
309
        :param additional_see_also: Additional help topics to be
369
315
            result = self.module.__doc__
370
316
        if result[-1] != '\n':
371
317
            result += '\n'
372
 
        # there is code duplicated here and in bzrlib/help_topic.py's 
 
318
        # there is code duplicated here and in bzrlib/help_topic.py's
373
319
        # matching Topic code. This should probably be factored in
374
320
        # to a helper function and a common base class.
375
321
        if additional_see_also is not None:
441
387
    def version_info(self):
442
388
        """Return the plugin's version_tuple or None if unknown."""
443
389
        version_info = getattr(self.module, 'version_info', None)
444
 
        if version_info is not None and len(version_info) == 3:
445
 
            version_info = tuple(version_info) + ('final', 0)
 
390
        if version_info is not None:
 
391
            try:
 
392
                if isinstance(version_info, types.StringType):
 
393
                    version_info = version_info.split('.')
 
394
                elif len(version_info) == 3:
 
395
                    version_info = tuple(version_info) + ('final', 0)
 
396
            except TypeError, e:
 
397
                # The given version_info isn't even iteratible
 
398
                trace.log_exception_quietly()
 
399
                version_info = (version_info,)
446
400
        return version_info
447
401
 
448
402
    def _get__version__(self):
449
403
        version_info = self.version_info()
450
 
        if version_info is None:
 
404
        if version_info is None or len(version_info) == 0:
451
405
            return "unknown"
452
 
        if version_info[3] == 'final':
453
 
            version_string = '%d.%d.%d' % version_info[:3]
454
 
        else:
455
 
            version_string = '%d.%d.%d%s%d' % version_info
 
406
        try:
 
407
            version_string = _format_version_tuple(version_info)
 
408
        except (ValueError, TypeError, IndexError), e:
 
409
            trace.log_exception_quietly()
 
410
            # try to return something usefull for bad plugins, in stead of
 
411
            # stack tracing.
 
412
            version_string = '.'.join(map(str, version_info))
456
413
        return version_string
457
414
 
458
415
    __version__ = property(_get__version__)