~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

  • Committer: Martin
  • Date: 2011-08-04 00:17:53 UTC
  • mto: This revision was merged to the branch mainline in revision 6055.
  • Revision ID: gzlist@googlemail.com-20110804001753-plgpwcpsxcum16yb
Make tests raising KnownFailure use the knownFailure method instead

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
36
36
from bzrlib import osutils
37
37
 
38
38
from bzrlib.lazy_import import lazy_import
39
 
 
40
39
lazy_import(globals(), """
41
40
import imp
42
41
import re
52
51
from bzrlib import plugins as _mod_plugins
53
52
""")
54
53
 
55
 
from bzrlib.symbol_versioning import (
56
 
    deprecated_function,
57
 
    deprecated_in,
58
 
    )
59
 
 
60
54
 
61
55
DEFAULT_PLUGIN_PATH = None
62
56
_loaded = False
63
57
_plugins_disabled = False
64
58
 
65
59
 
 
60
plugin_warnings = {}
 
61
# Map from plugin name, to list of string warnings about eg plugin
 
62
# dependencies.
 
63
 
 
64
 
66
65
def are_plugins_disabled():
67
66
    return _plugins_disabled
68
67
 
77
76
    load_plugins([])
78
77
 
79
78
 
 
79
def describe_plugins(show_paths=False):
 
80
    """Generate text description of plugins.
 
81
 
 
82
    Includes both those that have loaded, and those that failed to 
 
83
    load.
 
84
 
 
85
    :param show_paths: If true,
 
86
    :returns: Iterator of text lines (including newlines.)
 
87
    """
 
88
    from inspect import getdoc
 
89
    loaded_plugins = plugins()
 
90
    all_names = sorted(list(set(
 
91
        loaded_plugins.keys() + plugin_warnings.keys())))
 
92
    for name in all_names:
 
93
        if name in loaded_plugins:
 
94
            plugin = loaded_plugins[name]
 
95
            version = plugin.__version__
 
96
            if version == 'unknown':
 
97
                version = ''
 
98
            yield '%s %s\n' % (name, version)
 
99
            d = getdoc(plugin.module)
 
100
            if d:
 
101
                doc = d.split('\n')[0]
 
102
            else:
 
103
                doc = '(no description)'
 
104
            yield ("  %s\n" % doc)
 
105
            if show_paths:
 
106
                yield ("   %s\n" % plugin.path())
 
107
            del plugin
 
108
        else:
 
109
            yield "%s (failed to load)\n" % name
 
110
        if name in plugin_warnings:
 
111
            for line in plugin_warnings[name]:
 
112
                yield "  ** " + line + '\n'
 
113
        yield '\n'
 
114
 
 
115
 
80
116
def _strip_trailing_sep(path):
81
117
    return path.rstrip("\\/")
82
118
 
83
119
 
 
120
def _get_specific_plugin_paths(paths):
 
121
    """Returns the plugin paths from a string describing the associations.
 
122
 
 
123
    :param paths: A string describing the paths associated with the plugins.
 
124
 
 
125
    :returns: A list of (plugin name, path) tuples.
 
126
 
 
127
    For example, if paths is my_plugin@/test/my-test:her_plugin@/production/her,
 
128
    [('my_plugin', '/test/my-test'), ('her_plugin', '/production/her')] 
 
129
    will be returned.
 
130
 
 
131
    Note that ':' in the example above depends on the os.
 
132
    """
 
133
    if not paths:
 
134
        return []
 
135
    specs = []
 
136
    for spec in paths.split(os.pathsep):
 
137
        try:
 
138
            name, path = spec.split('@')
 
139
        except ValueError:
 
140
            raise errors.BzrCommandError(
 
141
                '"%s" is not a valid <plugin_name>@<plugin_path> description '
 
142
                % spec)
 
143
        specs.append((name, path))
 
144
    return specs
 
145
 
 
146
 
84
147
def set_plugins_path(path=None):
85
148
    """Set the path for plugins to be loaded from.
86
149
 
98
161
        for name in disabled_plugins.split(os.pathsep):
99
162
            PluginImporter.blacklist.add('bzrlib.plugins.' + name)
100
163
    # Set up a the specific paths for plugins
101
 
    specific_plugins = os.environ.get('BZR_PLUGINS_AT', None)
102
 
    if specific_plugins is not None:
103
 
        for spec in specific_plugins.split(os.pathsep):
104
 
            plugin_name, plugin_path = spec.split('@')
 
164
    for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
 
165
            'BZR_PLUGINS_AT', None)):
105
166
            PluginImporter.specific_paths[
106
167
                'bzrlib.plugins.%s' % plugin_name] = plugin_path
107
168
    return path
211
272
    """Load bzrlib plugins.
212
273
 
213
274
    The environment variable BZR_PLUGIN_PATH is considered a delimited
214
 
    set of paths to look through. Each entry is searched for *.py
 
275
    set of paths to look through. Each entry is searched for `*.py`
215
276
    files (and whatever other extensions are used in the platform,
216
 
    such as *.pyd).
 
277
    such as `*.pyd`).
217
278
 
218
279
    load_from_path() provides the underlying mechanism and is called with
219
280
    the default directory list to provide the normal behaviour.
302
363
    return None, None, (None, None, None)
303
364
 
304
365
 
 
366
def record_plugin_warning(plugin_name, warning_message):
 
367
    trace.mutter(warning_message)
 
368
    plugin_warnings.setdefault(plugin_name, []).append(warning_message)
 
369
 
 
370
 
305
371
def _load_plugin_module(name, dir):
306
372
    """Load plugin name from dir.
307
373
 
315
381
    except KeyboardInterrupt:
316
382
        raise
317
383
    except errors.IncompatibleAPI, e:
318
 
        trace.warning("Unable to load plugin %r. It requested API version "
 
384
        warning_message = (
 
385
            "Unable to load plugin %r. It requested API version "
319
386
            "%s of module %s but the minimum exported version is %s, and "
320
387
            "the maximum is %s" %
321
388
            (name, e.wanted, e.api, e.minimum, e.current))
 
389
        record_plugin_warning(name, warning_message)
322
390
    except Exception, e:
323
391
        trace.warning("%s" % e)
324
392
        if re.search('\.|-| ', name):
329
397
                    "file path isn't a valid module name; try renaming "
330
398
                    "it to %r." % (name, dir, sanitised_name))
331
399
        else:
332
 
            trace.warning('Unable to load plugin %r from %r' % (name, dir))
 
400
            record_plugin_warning(
 
401
                name,
 
402
                'Unable to load plugin %r from %r' % (name, dir))
333
403
        trace.log_exception_quietly()
334
404
        if 'error' in debug.debug_flags:
335
405
            trace.print_exception(sys.exc_info(), sys.stderr)
375
445
    return result
376
446
 
377
447
 
 
448
def format_concise_plugin_list():
 
449
    """Return a string holding a concise list of plugins and their version.
 
450
    """
 
451
    items = []
 
452
    for name, a_plugin in sorted(plugins().items()):
 
453
        items.append("%s[%s]" %
 
454
            (name, a_plugin.__version__))
 
455
    return ', '.join(items)
 
456
 
 
457
 
 
458
 
378
459
class PluginsHelpIndex(object):
379
460
    """A help index that returns help topics for plugins."""
380
461
 
562
643
        # We are called only for specific paths
563
644
        plugin_path = self.specific_paths[fullname]
564
645
        loading_path = None
565
 
        package = False
566
646
        if os.path.isdir(plugin_path):
567
647
            for suffix, mode, kind in imp.get_suffixes():
568
648
                if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
570
650
                    continue
571
651
                init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
572
652
                if os.path.isfile(init_path):
573
 
                    loading_path = init_path
574
 
                    package = True
 
653
                    # We've got a module here and load_module needs specific
 
654
                    # parameters.
 
655
                    loading_path = plugin_path
 
656
                    suffix = ''
 
657
                    mode = ''
 
658
                    kind = imp.PKG_DIRECTORY
575
659
                    break
576
660
        else:
577
661
            for suffix, mode, kind in imp.get_suffixes():
581
665
        if loading_path is None:
582
666
            raise ImportError('%s cannot be loaded from %s'
583
667
                              % (fullname, plugin_path))
584
 
        f = open(loading_path, mode)
 
668
        if kind is imp.PKG_DIRECTORY:
 
669
            f = None
 
670
        else:
 
671
            f = open(loading_path, mode)
585
672
        try:
586
673
            mod = imp.load_module(fullname, f, loading_path,
587
674
                                  (suffix, mode, kind))
588
 
            if package:
589
 
                # The plugin can contain modules, so be ready
590
 
                mod.__path__ = [plugin_path]
591
675
            mod.__package__ = fullname
592
676
            return mod
593
677
        finally:
594
 
            f.close()
 
678
            if f is not None:
 
679
                f.close()
595
680
 
596
681
 
597
682
# Install a dedicated importer for plugins requiring special handling