~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

  • Committer: Neil Martinsen-Burrell
  • Date: 2011-11-28 19:00:43 UTC
  • mto: (6331.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 6332.
  • Revision ID: nmb@wartburg.edu-20111128190043-3txzmh7eb9erlvlh
document in Release Notes

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