~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2007 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
37
37
import imp
38
38
import re
39
39
import types
40
 
import zipimport
 
40
import zipfile
41
41
 
42
42
from bzrlib import (
43
43
    config,
44
44
    osutils,
45
 
    plugins,
46
45
    )
 
46
from bzrlib import plugins as _mod_plugins
47
47
""")
48
48
 
 
49
from bzrlib.symbol_versioning import deprecated_function, one_three
49
50
from bzrlib.trace import mutter, warning, log_exception_quietly
50
51
 
51
52
 
60
61
    return DEFAULT_PLUGIN_PATH
61
62
 
62
63
 
63
 
def all_plugins():
64
 
    """Return a dictionary of the plugins."""
65
 
    result = {}
66
 
    for name, plugin in plugins.__dict__.items():
67
 
        if isinstance(plugin, types.ModuleType):
68
 
            result[name] = plugin
69
 
    return result
70
 
 
71
 
 
72
64
def disable_plugins():
73
65
    """Disable loading plugins.
74
66
 
80
72
    _loaded = True
81
73
 
82
74
 
 
75
def _strip_trailing_sep(path):
 
76
    return path.rstrip("\\/")
 
77
 
 
78
 
83
79
def set_plugins_path():
84
80
    """Set the path for plugins to be loaded from."""
85
81
    path = os.environ.get('BZR_PLUGIN_PATH',
86
82
                          get_default_plugin_path()).split(os.pathsep)
87
 
    # search the plugin path before the bzrlib installed dir
88
 
    path.append(os.path.dirname(plugins.__file__))
89
 
    plugins.__path__ = path
 
83
    bzr_exe = bool(getattr(sys, 'frozen', None))
 
84
    if bzr_exe:    # expand path for bzr.exe
 
85
        # We need to use relative path to system-wide plugin
 
86
        # directory because bzrlib from standalone bzr.exe
 
87
        # could be imported by another standalone program
 
88
        # (e.g. bzr-config; or TortoiseBzr/Olive if/when they
 
89
        # will become standalone exe). [bialix 20071123]
 
90
        # __file__ typically is
 
91
        # C:\Program Files\Bazaar\lib\library.zip\bzrlib\plugin.pyc
 
92
        # then plugins directory is
 
93
        # C:\Program Files\Bazaar\plugins
 
94
        # so relative path is ../../../plugins
 
95
        path.append(osutils.abspath(osutils.pathjoin(
 
96
            osutils.dirname(__file__), '../../../plugins')))
 
97
    # Get rid of trailing slashes, since Python can't handle them when
 
98
    # it tries to import modules.
 
99
    path = map(_strip_trailing_sep, path)
 
100
    if not bzr_exe:     # don't look inside library.zip
 
101
        # search the plugin path before the bzrlib installed dir
 
102
        path.append(os.path.dirname(_mod_plugins.__file__))
 
103
    _mod_plugins.__path__ = path
90
104
    return path
91
105
 
92
106
 
123
137
 
124
138
    The python module path for bzrlib.plugins will be modified to be 'dirs'.
125
139
    """
126
 
    plugins.__path__ = dirs
 
140
    # We need to strip the trailing separators here as well as in the
 
141
    # set_plugins_path function because calling code can pass anything in to
 
142
    # this function, and since it sets plugins.__path__, it should set it to
 
143
    # something that will be valid for Python to use (in case people try to
 
144
    # run "import bzrlib.plugins.PLUGINNAME" after calling this function).
 
145
    _mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
127
146
    for d in dirs:
128
147
        if not d:
129
148
            continue
130
149
        mutter('looking for plugins in %s', d)
131
150
        if os.path.isdir(d):
132
151
            load_from_dir(d)
133
 
        else:
134
 
            # it might be a zip: try loading from the zip.
135
 
            load_from_zip(d)
136
 
            continue
137
152
 
138
153
 
139
154
# backwards compatability: load_from_dirs was the old name
169
184
                    break
170
185
            else:
171
186
                continue
172
 
        if getattr(plugins, f, None):
 
187
        if getattr(_mod_plugins, f, None):
173
188
            mutter('Plugin name %s already loaded', f)
174
189
        else:
175
190
            # mutter('add plugin name %s', f)
183
198
        except Exception, e:
184
199
            ## import pdb; pdb.set_trace()
185
200
            if re.search('\.|-| ', name):
186
 
                warning('Unable to load plugin %r from %r: '
187
 
                    'It is not a valid python module name.' % (name, d))
 
201
                sanitised_name = re.sub('[-. ]', '_', name)
 
202
                warning("Unable to load %r in %r as a plugin because file path"
 
203
                        " isn't a valid module name; try renaming it to %r."
 
204
                        % (name, d, sanitised_name))
188
205
            else:
189
206
                warning('Unable to load plugin %r from %r' % (name, d))
190
207
            log_exception_quietly()
191
208
 
192
209
 
 
210
@deprecated_function(one_three)
193
211
def load_from_zip(zip_name):
194
212
    """Load all the plugins in a zip."""
195
213
    valid_suffixes = ('.py', '.pyc', '.pyo')    # only python modules/packages
196
214
                                                # is allowed
197
 
    if '.zip' not in zip_name:
198
 
        return
199
215
    try:
200
 
        ziobj = zipimport.zipimporter(zip_name)
201
 
    except zipimport.ZipImportError:
202
 
        # not a valid zip
 
216
        index = zip_name.rindex('.zip')
 
217
    except ValueError:
203
218
        return
 
219
    archive = zip_name[:index+4]
 
220
    prefix = zip_name[index+5:]
 
221
 
204
222
    mutter('Looking for plugins in %r', zip_name)
205
 
    
206
 
    import zipfile
207
223
 
208
224
    # use zipfile to get list of files/dirs inside zip
209
 
    z = zipfile.ZipFile(ziobj.archive)
210
 
    namelist = z.namelist()
211
 
    z.close()
212
 
    
213
 
    if ziobj.prefix:
214
 
        prefix = ziobj.prefix.replace('\\','/')
 
225
    try:
 
226
        z = zipfile.ZipFile(archive)
 
227
        namelist = z.namelist()
 
228
        z.close()
 
229
    except zipfile.error:
 
230
        # not a valid zip
 
231
        return
 
232
 
 
233
    if prefix:
 
234
        prefix = prefix.replace('\\','/')
 
235
        if prefix[-1] != '/':
 
236
            prefix += '/'
215
237
        ix = len(prefix)
216
238
        namelist = [name[ix:]
217
239
                    for name in namelist
218
240
                    if name.startswith(prefix)]
219
 
    
 
241
 
220
242
    mutter('Names in archive: %r', namelist)
221
243
    
222
244
    for name in namelist:
248
270
    
249
271
        if not plugin_name:
250
272
            continue
251
 
        if getattr(plugins, plugin_name, None):
 
273
        if getattr(_mod_plugins, plugin_name, None):
252
274
            mutter('Plugin name %s already loaded', plugin_name)
253
275
            continue
254
276
    
255
277
        try:
256
 
            plugin = ziobj.load_module(plugin_name)
257
 
            setattr(plugins, plugin_name, plugin)
 
278
            exec "import bzrlib.plugins.%s" % plugin_name in {}
258
279
            mutter('Load plugin %s from zip %r', plugin_name, zip_name)
259
 
        except zipimport.ZipImportError, e:
260
 
            mutter('Unable to load plugin %r from %r: %s',
261
 
                   plugin_name, zip_name, str(e))
262
 
            continue
263
280
        except KeyboardInterrupt:
264
281
            raise
265
282
        except Exception, e:
269
286
            log_exception_quietly()
270
287
 
271
288
 
 
289
def plugins():
 
290
    """Return a dictionary of the plugins.
 
291
    
 
292
    Each item in the dictionary is a PlugIn object.
 
293
    """
 
294
    result = {}
 
295
    for name, plugin in _mod_plugins.__dict__.items():
 
296
        if isinstance(plugin, types.ModuleType):
 
297
            result[name] = PlugIn(name, plugin)
 
298
    return result
 
299
 
 
300
 
272
301
class PluginsHelpIndex(object):
273
302
    """A help index that returns help topics for plugins."""
274
303
 
335
364
    def get_help_topic(self):
336
365
        """Return the modules help topic - its __name__ after bzrlib.plugins.."""
337
366
        return self.module.__name__[len('bzrlib.plugins.'):]
 
367
 
 
368
 
 
369
class PlugIn(object):
 
370
    """The bzrlib representation of a plugin.
 
371
 
 
372
    The PlugIn object provides a way to manipulate a given plugin module.
 
373
    """
 
374
 
 
375
    def __init__(self, name, module):
 
376
        """Construct a plugin for module."""
 
377
        self.name = name
 
378
        self.module = module
 
379
 
 
380
    def path(self):
 
381
        """Get the path that this plugin was loaded from."""
 
382
        if getattr(self.module, '__path__', None) is not None:
 
383
            return os.path.abspath(self.module.__path__[0])
 
384
        elif getattr(self.module, '__file__', None) is not None:
 
385
            path = os.path.abspath(self.module.__file__)
 
386
            if path[-4:] in ('.pyc', '.pyo'):
 
387
                pypath = path[:-4] + '.py'
 
388
                if os.path.isfile(pypath):
 
389
                    path = pypath
 
390
            return path
 
391
        else:
 
392
            return repr(self.module)
 
393
 
 
394
    def __str__(self):
 
395
        return "<%s.%s object at %s, name=%s, module=%s>" % (
 
396
            self.__class__.__module__, self.__class__.__name__, id(self),
 
397
            self.name, self.module)
 
398
 
 
399
    __repr__ = __str__
 
400
 
 
401
    def test_suite(self):
 
402
        """Return the plugin's test suite."""
 
403
        if getattr(self.module, 'test_suite', None) is not None:
 
404
            return self.module.test_suite()
 
405
        else:
 
406
            return None
 
407
 
 
408
    def version_info(self):
 
409
        """Return the plugin's version_tuple or None if unknown."""
 
410
        version_info = getattr(self.module, 'version_info', None)
 
411
        if version_info is not None and len(version_info) == 3:
 
412
            version_info = tuple(version_info) + ('final', 0)
 
413
        return version_info
 
414
    
 
415
    def _get__version__(self):
 
416
        version_info = self.version_info()
 
417
        if version_info is None:
 
418
            return "unknown"
 
419
        if version_info[3] == 'final':
 
420
            version_string = '%d.%d.%d' % version_info[:3]
 
421
        else:
 
422
            version_string = '%d.%d.%d%s%d' % version_info
 
423
        return version_string
 
424
 
 
425
    __version__ = property(_get__version__)