~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

  • Committer: Robert Collins
  • Date: 2010-05-11 08:44:59 UTC
  • mfrom: (5221 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511084459-pb0uinna9zs3wu59
Merge trunk - resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
 
39
40
lazy_import(globals(), """
40
41
import imp
41
42
import re
48
49
    errors,
49
50
    trace,
50
51
    )
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
 
55
60
 
56
61
DEFAULT_PLUGIN_PATH = None
57
62
_loaded = False
58
63
_plugins_disabled = False
59
64
 
60
65
 
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
 
 
117
80
def _strip_trailing_sep(path):
118
81
    return path.rstrip("\\/")
119
82
 
120
83
 
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
 
 
148
84
def set_plugins_path(path=None):
149
85
    """Set the path for plugins to be loaded from.
150
86
 
162
98
        for name in disabled_plugins.split(os.pathsep):
163
99
            PluginImporter.blacklist.add('bzrlib.plugins.' + name)
164
100
    # Set up a the specific paths for plugins
165
 
    for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
166
 
            'BZR_PLUGINS_AT', None)):
 
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('@')
167
105
            PluginImporter.specific_paths[
168
106
                'bzrlib.plugins.%s' % plugin_name] = plugin_path
169
107
    return path
273
211
    """Load bzrlib plugins.
274
212
 
275
213
    The environment variable BZR_PLUGIN_PATH is considered a delimited
276
 
    set of paths to look through. Each entry is searched for `*.py`
 
214
    set of paths to look through. Each entry is searched for *.py
277
215
    files (and whatever other extensions are used in the platform,
278
 
    such as `*.pyd`).
 
216
    such as *.pyd).
279
217
 
280
218
    load_from_path() provides the underlying mechanism and is called with
281
219
    the default directory list to provide the normal behaviour.
364
302
    return None, None, (None, None, None)
365
303
 
366
304
 
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
 
 
372
305
def _load_plugin_module(name, dir):
373
306
    """Load plugin name from dir.
374
307
 
382
315
    except KeyboardInterrupt:
383
316
        raise
384
317
    except errors.IncompatibleAPI, e:
385
 
        warning_message = (
386
 
            "Unable to load plugin %r. It requested API version "
 
318
        trace.warning("Unable to load plugin %r. It requested API version "
387
319
            "%s of module %s but the minimum exported version is %s, and "
388
320
            "the maximum is %s" %
389
321
            (name, e.wanted, e.api, e.minimum, e.current))
390
 
        record_plugin_warning(name, warning_message)
391
322
    except Exception, e:
392
323
        trace.warning("%s" % e)
393
324
        if re.search('\.|-| ', name):
398
329
                    "file path isn't a valid module name; try renaming "
399
330
                    "it to %r." % (name, dir, sanitised_name))
400
331
        else:
401
 
            record_plugin_warning(
402
 
                name,
403
 
                'Unable to load plugin %r from %r' % (name, dir))
 
332
            trace.warning('Unable to load plugin %r from %r' % (name, dir))
404
333
        trace.log_exception_quietly()
405
334
        if 'error' in debug.debug_flags:
406
335
            trace.print_exception(sys.exc_info(), sys.stderr)
446
375
    return result
447
376
 
448
377
 
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
 
 
460
378
class PluginsHelpIndex(object):
461
379
    """A help index that returns help topics for plugins."""
462
380
 
507
425
            result = self.module.__doc__
508
426
        if result[-1] != '\n':
509
427
            result += '\n'
510
 
        from bzrlib import help_topics
511
 
        result += help_topics._format_see_also(additional_see_also)
 
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'
512
439
        return result
513
440
 
514
441
    def get_help_topic(self):
515
 
        """Return the module help topic: its basename."""
 
442
        """Return the modules help topic - its __name__ after bzrlib.plugins.."""
516
443
        return self.module.__name__[len('bzrlib.plugins.'):]
517
444
 
518
445
 
635
562
        # We are called only for specific paths
636
563
        plugin_path = self.specific_paths[fullname]
637
564
        loading_path = None
 
565
        package = False
638
566
        if os.path.isdir(plugin_path):
639
567
            for suffix, mode, kind in imp.get_suffixes():
640
568
                if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
642
570
                    continue
643
571
                init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
644
572
                if os.path.isfile(init_path):
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
 
573
                    loading_path = init_path
 
574
                    package = True
651
575
                    break
652
576
        else:
653
577
            for suffix, mode, kind in imp.get_suffixes():
657
581
        if loading_path is None:
658
582
            raise ImportError('%s cannot be loaded from %s'
659
583
                              % (fullname, plugin_path))
660
 
        if kind is imp.PKG_DIRECTORY:
661
 
            f = None
662
 
        else:
663
 
            f = open(loading_path, mode)
 
584
        f = open(loading_path, mode)
664
585
        try:
665
586
            mod = imp.load_module(fullname, f, loading_path,
666
587
                                  (suffix, mode, kind))
 
588
            if package:
 
589
                # The plugin can contain modules, so be ready
 
590
                mod.__path__ = [plugin_path]
667
591
            mod.__package__ = fullname
668
592
            return mod
669
593
        finally:
670
 
            if f is not None:
671
 
                f.close()
 
594
            f.close()
672
595
 
673
596
 
674
597
# Install a dedicated importer for plugins requiring special handling