/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugin.py

  • Committer: Jelmer Vernooij
  • Author(s): Richard Wilbur
  • Date: 2017-05-30 23:37:11 UTC
  • mto: This revision was merged to the branch mainline in revision 6645.
  • Revision ID: jelmer@jelmer.uk-20170530233711-r0m0qp8hpkqzpopw
Fix order in which files are processed.

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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
 
18
17
"""bzr python plugin support.
19
18
 
20
19
When load_plugins() is invoked, any python module in any directory in
21
 
$BZR_PLUGIN_PATH will be imported.  The module will be imported as
22
 
'bzrlib.plugins.$BASENAME(PLUGIN)'.  In the plugin's main body, it should
23
 
update any bzrlib registries it wants to extend.
 
20
$BRZ_PLUGIN_PATH will be imported.  The module will be imported as
 
21
'breezy.plugins.$BASENAME(PLUGIN)'.  In the plugin's main body, it should
 
22
update any breezy registries it wants to extend.
24
23
 
25
24
See the plugin-api developer documentation for information about writing
26
25
plugins.
27
26
 
28
 
BZR_PLUGIN_PATH is also honoured for any plugins imported via
29
 
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been
 
27
BRZ_PLUGIN_PATH is also honoured for any plugins imported via
 
28
'import breezy.plugins.PLUGINNAME', as long as set_plugins_path has been
30
29
called.
31
30
"""
32
31
 
 
32
from __future__ import absolute_import
 
33
 
33
34
import os
34
35
import sys
35
36
 
36
 
from bzrlib import osutils
37
 
 
38
 
from bzrlib.lazy_import import lazy_import
39
 
 
 
37
from . import osutils
 
38
 
 
39
from .lazy_import import lazy_import
40
40
lazy_import(globals(), """
41
41
import imp
42
42
import re
43
43
import types
44
44
 
45
 
from bzrlib import (
 
45
from breezy import (
46
46
    _format_version_tuple,
47
47
    config,
48
48
    debug,
49
49
    errors,
50
50
    trace,
51
51
    )
52
 
from bzrlib import plugins as _mod_plugins
 
52
from breezy.i18n import gettext
 
53
from breezy import plugins as _mod_plugins
53
54
""")
54
55
 
55
 
from bzrlib.symbol_versioning import (
56
 
    deprecated_function,
57
 
    deprecated_in,
58
 
    )
59
 
 
60
56
 
61
57
DEFAULT_PLUGIN_PATH = None
62
58
_loaded = False
63
59
_plugins_disabled = False
64
60
 
65
61
 
 
62
plugin_warnings = {}
 
63
# Map from plugin name, to list of string warnings about eg plugin
 
64
# dependencies.
 
65
 
 
66
 
66
67
def are_plugins_disabled():
67
68
    return _plugins_disabled
68
69
 
77
78
    load_plugins([])
78
79
 
79
80
 
 
81
def describe_plugins(show_paths=False):
 
82
    """Generate text description of plugins.
 
83
 
 
84
    Includes both those that have loaded, and those that failed to 
 
85
    load.
 
86
 
 
87
    :param show_paths: If true,
 
88
    :returns: Iterator of text lines (including newlines.)
 
89
    """
 
90
    from inspect import getdoc
 
91
    loaded_plugins = plugins()
 
92
    all_names = sorted(list(set(
 
93
        loaded_plugins.keys() + plugin_warnings.keys())))
 
94
    for name in all_names:
 
95
        if name in loaded_plugins:
 
96
            plugin = loaded_plugins[name]
 
97
            version = plugin.__version__
 
98
            if version == 'unknown':
 
99
                version = ''
 
100
            yield '%s %s\n' % (name, version)
 
101
            d = getdoc(plugin.module)
 
102
            if d:
 
103
                doc = d.split('\n')[0]
 
104
            else:
 
105
                doc = '(no description)'
 
106
            yield ("  %s\n" % doc)
 
107
            if show_paths:
 
108
                yield ("   %s\n" % plugin.path())
 
109
            del plugin
 
110
        else:
 
111
            yield "%s (failed to load)\n" % name
 
112
        if name in plugin_warnings:
 
113
            for line in plugin_warnings[name]:
 
114
                yield "  ** " + line + '\n'
 
115
        yield '\n'
 
116
 
 
117
 
80
118
def _strip_trailing_sep(path):
81
119
    return path.rstrip("\\/")
82
120
 
83
121
 
 
122
def _get_specific_plugin_paths(paths):
 
123
    """Returns the plugin paths from a string describing the associations.
 
124
 
 
125
    :param paths: A string describing the paths associated with the plugins.
 
126
 
 
127
    :returns: A list of (plugin name, path) tuples.
 
128
 
 
129
    For example, if paths is my_plugin@/test/my-test:her_plugin@/production/her,
 
130
    [('my_plugin', '/test/my-test'), ('her_plugin', '/production/her')] 
 
131
    will be returned.
 
132
 
 
133
    Note that ':' in the example above depends on the os.
 
134
    """
 
135
    if not paths:
 
136
        return []
 
137
    specs = []
 
138
    for spec in paths.split(os.pathsep):
 
139
        try:
 
140
            name, path = spec.split('@')
 
141
        except ValueError:
 
142
            raise errors.BzrCommandError(gettext(
 
143
                '"%s" is not a valid <plugin_name>@<plugin_path> description ')
 
144
                % spec)
 
145
        specs.append((name, path))
 
146
    return specs
 
147
 
 
148
 
84
149
def set_plugins_path(path=None):
85
150
    """Set the path for plugins to be loaded from.
86
151
 
93
158
    _mod_plugins.__path__ = path
94
159
    PluginImporter.reset()
95
160
    # Set up a blacklist for disabled plugins
96
 
    disabled_plugins = os.environ.get('BZR_DISABLE_PLUGINS', None)
 
161
    disabled_plugins = os.environ.get('BRZ_DISABLE_PLUGINS', None)
97
162
    if disabled_plugins is not None:
98
163
        for name in disabled_plugins.split(os.pathsep):
99
 
            PluginImporter.blacklist.add('bzrlib.plugins.' + name)
 
164
            PluginImporter.blacklist.add('breezy.plugins.' + name)
100
165
    # 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('@')
 
166
    for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
 
167
            'BRZ_PLUGINS_AT', None)):
105
168
            PluginImporter.specific_paths[
106
 
                'bzrlib.plugins.%s' % plugin_name] = plugin_path
 
169
                'breezy.plugins.%s' % plugin_name] = plugin_path
107
170
    return path
108
171
 
109
172
 
116
179
 
117
180
def get_core_plugin_path():
118
181
    core_path = None
119
 
    bzr_exe = bool(getattr(sys, 'frozen', None))
120
 
    if bzr_exe:    # expand path for bzr.exe
 
182
    brz_exe = bool(getattr(sys, 'frozen', None))
 
183
    if brz_exe:    # expand path for brz.exe
121
184
        # We need to use relative path to system-wide plugin
122
 
        # directory because bzrlib from standalone bzr.exe
 
185
        # directory because breezy from standalone brz.exe
123
186
        # could be imported by another standalone program
124
187
        # (e.g. bzr-config; or TortoiseBzr/Olive if/when they
125
188
        # will become standalone exe). [bialix 20071123]
126
189
        # __file__ typically is
127
 
        # C:\Program Files\Bazaar\lib\library.zip\bzrlib\plugin.pyc
 
190
        # C:\Program Files\Bazaar\lib\library.zip\breezy\plugin.pyc
128
191
        # then plugins directory is
129
192
        # C:\Program Files\Bazaar\plugins
130
193
        # so relative path is ../../../plugins
131
194
        core_path = osutils.abspath(osutils.pathjoin(
132
195
                osutils.dirname(__file__), '../../../plugins'))
133
196
    else:     # don't look inside library.zip
134
 
        # search the plugin path before the bzrlib installed dir
 
197
        # search the plugin path before the breezy installed dir
135
198
        core_path = os.path.dirname(_mod_plugins.__file__)
136
199
    return core_path
137
200
 
149
212
        # If distutuils is not available, we just don't know where they are
150
213
        pass
151
214
    else:
152
 
        site_path = osutils.pathjoin(get_python_lib(), 'bzrlib', 'plugins')
 
215
        site_path = osutils.pathjoin(get_python_lib(), 'breezy', 'plugins')
153
216
    return site_path
154
217
 
155
218
 
162
225
    # Ad-Hoc default: core is not overriden by site but user can overrides both
163
226
    # The rationale is that:
164
227
    # - 'site' comes last, because these plugins should always be available and
165
 
    #   are supposed to be in sync with the bzr installed on site.
166
 
    # - 'core' comes before 'site' so that running bzr from sources or a user
 
228
    #   are supposed to be in sync with the brz installed on site.
 
229
    # - 'core' comes before 'site' so that running brz from sources or a user
167
230
    #   installed version overrides the site version.
168
231
    # - 'user' comes first, because... user is always right.
169
232
    # - the above rules clearly defines which plugin version will be loaded if
171
234
    #   so that a set of plugins is disabled as once. This can be done via
172
235
    #   -site, -core, -user.
173
236
 
174
 
    env_paths = os.environ.get('BZR_PLUGIN_PATH', '+user').split(os.pathsep)
 
237
    env_paths = os.environ.get('BRZ_PLUGIN_PATH', '+user').split(os.pathsep)
175
238
    defaults = ['+core', '+site']
176
239
 
177
240
    # The predefined references
180
243
                user=get_user_plugin_path())
181
244
 
182
245
    # Unset paths that should be removed
183
 
    for k,v in refs.iteritems():
 
246
    for k,v in refs.items():
184
247
        removed = '-%s' % k
185
248
        # defaults can never mention removing paths as that will make it
186
249
        # impossible for the user to revoke these removals.
203
266
 
204
267
    # Get rid of trailing slashes, since Python can't handle them when
205
268
    # it tries to import modules.
206
 
    paths = map(_strip_trailing_sep, paths)
 
269
    paths = list(map(_strip_trailing_sep, paths))
207
270
    return paths
208
271
 
209
272
 
210
273
def load_plugins(path=None):
211
 
    """Load bzrlib plugins.
 
274
    """Load breezy plugins.
212
275
 
213
 
    The environment variable BZR_PLUGIN_PATH is considered a delimited
214
 
    set of paths to look through. Each entry is searched for *.py
 
276
    The environment variable BRZ_PLUGIN_PATH is considered a delimited
 
277
    set of paths to look through. Each entry is searched for `*.py`
215
278
    files (and whatever other extensions are used in the platform,
216
 
    such as *.pyd).
 
279
    such as `*.pyd`).
217
280
 
218
281
    load_from_path() provides the underlying mechanism and is called with
219
282
    the default directory list to provide the normal behaviour.
233
296
 
234
297
 
235
298
def load_from_path(dirs):
236
 
    """Load bzrlib plugins found in each dir in dirs.
 
299
    """Load breezy plugins found in each dir in dirs.
237
300
 
238
301
    Loading a plugin means importing it into the python interpreter.
239
302
    The plugin is expected to make calls to register commands when
240
303
    it's loaded (or perhaps access other hooks in future.)
241
304
 
242
 
    Plugins are loaded into bzrlib.plugins.NAME, and can be found there
 
305
    Plugins are loaded into breezy.plugins.NAME, and can be found there
243
306
    for future reference.
244
307
 
245
 
    The python module path for bzrlib.plugins will be modified to be 'dirs'.
 
308
    The python module path for breezy.plugins will be modified to be 'dirs'.
246
309
    """
247
310
    # Explicitly load the plugins with a specific path
248
 
    for fullname, path in PluginImporter.specific_paths.iteritems():
249
 
        name = fullname[len('bzrlib.plugins.'):]
 
311
    for fullname, path in PluginImporter.specific_paths.items():
 
312
        name = fullname[len('breezy.plugins.'):]
250
313
        _load_plugin_module(name, path)
251
314
 
252
315
    # We need to strip the trailing separators here as well as in the
253
316
    # set_plugins_path function because calling code can pass anything in to
254
317
    # this function, and since it sets plugins.__path__, it should set it to
255
318
    # something that will be valid for Python to use (in case people try to
256
 
    # run "import bzrlib.plugins.PLUGINNAME" after calling this function).
257
 
    _mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
 
319
    # run "import breezy.plugins.PLUGINNAME" after calling this function).
 
320
    _mod_plugins.__path__ = list(map(_strip_trailing_sep, dirs))
258
321
    for d in dirs:
259
322
        if not d:
260
323
            continue
302
365
    return None, None, (None, None, None)
303
366
 
304
367
 
 
368
def record_plugin_warning(plugin_name, warning_message):
 
369
    trace.mutter(warning_message)
 
370
    plugin_warnings.setdefault(plugin_name, []).append(warning_message)
 
371
 
 
372
 
305
373
def _load_plugin_module(name, dir):
306
374
    """Load plugin name from dir.
307
375
 
308
 
    :param name: The plugin name in the bzrlib.plugins namespace.
 
376
    :param name: The plugin name in the breezy.plugins namespace.
309
377
    :param dir: The directory the plugin is loaded from for error messages.
310
378
    """
311
 
    if ('bzrlib.plugins.%s' % name) in PluginImporter.blacklist:
 
379
    if ('breezy.plugins.%s' % name) in PluginImporter.blacklist:
312
380
        return
313
381
    try:
314
 
        exec "import bzrlib.plugins.%s" % name in {}
 
382
        exec("import breezy.plugins.%s" % name, {})
315
383
    except KeyboardInterrupt:
316
384
        raise
317
 
    except errors.IncompatibleAPI, e:
318
 
        trace.warning("Unable to load plugin %r. It requested API version "
 
385
    except errors.IncompatibleAPI as e:
 
386
        warning_message = (
 
387
            "Unable to load plugin %r. It requested API version "
319
388
            "%s of module %s but the minimum exported version is %s, and "
320
389
            "the maximum is %s" %
321
390
            (name, e.wanted, e.api, e.minimum, e.current))
322
 
    except Exception, e:
 
391
        record_plugin_warning(name, warning_message)
 
392
    except Exception as e:
323
393
        trace.warning("%s" % e)
324
394
        if re.search('\.|-| ', name):
325
395
            sanitised_name = re.sub('[-. ]', '_', name)
326
 
            if sanitised_name.startswith('bzr_'):
327
 
                sanitised_name = sanitised_name[len('bzr_'):]
 
396
            if sanitised_name.startswith('brz_'):
 
397
                sanitised_name = sanitised_name[len('brz_'):]
328
398
            trace.warning("Unable to load %r in %r as a plugin because the "
329
399
                    "file path isn't a valid module name; try renaming "
330
400
                    "it to %r." % (name, dir, sanitised_name))
331
401
        else:
332
 
            trace.warning('Unable to load plugin %r from %r' % (name, dir))
 
402
            record_plugin_warning(
 
403
                name,
 
404
                'Unable to load plugin %r from %r' % (name, dir))
333
405
        trace.log_exception_quietly()
334
406
        if 'error' in debug.debug_flags:
335
407
            trace.print_exception(sys.exc_info(), sys.stderr)
347
419
        if name is not None:
348
420
            if name == '__init__':
349
421
                # We do nothing with the __init__.py file in directories from
350
 
                # the bzrlib.plugins module path, we may want to, one day
 
422
                # the breezy.plugins module path, we may want to, one day
351
423
                # -- vila 20100316.
352
424
                continue # We don't load __init__.py in the plugins dirs
353
425
            elif getattr(_mod_plugins, name, None) is not None:
375
447
    return result
376
448
 
377
449
 
 
450
def format_concise_plugin_list():
 
451
    """Return a string holding a concise list of plugins and their version.
 
452
    """
 
453
    items = []
 
454
    for name, a_plugin in sorted(plugins().items()):
 
455
        items.append("%s[%s]" %
 
456
            (name, a_plugin.__version__))
 
457
    return ', '.join(items)
 
458
 
 
459
 
 
460
 
378
461
class PluginsHelpIndex(object):
379
462
    """A help index that returns help topics for plugins."""
380
463
 
394
477
            return []
395
478
        if topic.startswith(self.prefix):
396
479
            topic = topic[len(self.prefix):]
397
 
        plugin_module_name = 'bzrlib.plugins.%s' % topic
 
480
        plugin_module_name = 'breezy.plugins.%s' % topic
398
481
        try:
399
482
            module = sys.modules[plugin_module_name]
400
483
        except KeyError:
425
508
            result = self.module.__doc__
426
509
        if result[-1] != '\n':
427
510
            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'
 
511
        from . import help_topics
 
512
        result += help_topics._format_see_also(additional_see_also)
439
513
        return result
440
514
 
441
515
    def get_help_topic(self):
442
 
        """Return the modules help topic - its __name__ after bzrlib.plugins.."""
443
 
        return self.module.__name__[len('bzrlib.plugins.'):]
 
516
        """Return the module help topic: its basename."""
 
517
        return self.module.__name__[len('breezy.plugins.'):]
444
518
 
445
519
 
446
520
class PlugIn(object):
447
 
    """The bzrlib representation of a plugin.
 
521
    """The breezy representation of a plugin.
448
522
 
449
523
    The PlugIn object provides a way to manipulate a given plugin module.
450
524
    """
503
577
                    version_info = version_info.split('.')
504
578
                elif len(version_info) == 3:
505
579
                    version_info = tuple(version_info) + ('final', 0)
506
 
            except TypeError, e:
 
580
            except TypeError as e:
507
581
                # The given version_info isn't even iteratible
508
582
                trace.log_exception_quietly()
509
583
                version_info = (version_info,)
515
589
            return "unknown"
516
590
        try:
517
591
            version_string = _format_version_tuple(version_info)
518
 
        except (ValueError, TypeError, IndexError), e:
 
592
        except (ValueError, TypeError, IndexError) as e:
519
593
            trace.log_exception_quietly()
520
594
            # try to return something usefull for bad plugins, in stead of
521
595
            # stack tracing.
526
600
 
527
601
 
528
602
class _PluginImporter(object):
529
 
    """An importer tailored to bzr specific needs.
 
603
    """An importer tailored to brz specific needs.
530
604
 
531
605
    This is a singleton that takes care of:
532
606
    - disabled plugins specified in 'blacklist',
549
623
        :return: None if the plugin doesn't need special handling, self
550
624
            otherwise.
551
625
        """
552
 
        if not fullname.startswith('bzrlib.plugins.'):
 
626
        if not fullname.startswith('breezy.plugins.'):
553
627
            return None
554
628
        if fullname in self.blacklist:
555
629
            raise ImportError('%s is disabled' % fullname)
558
632
        return None
559
633
 
560
634
    def load_module(self, fullname):
561
 
        """Load a plugin from a specific directory."""
 
635
        """Load a plugin from a specific directory (or file)."""
562
636
        # We are called only for specific paths
563
637
        plugin_path = self.specific_paths[fullname]
564
638
        loading_path = None
565
 
        package = False
566
639
        if os.path.isdir(plugin_path):
567
640
            for suffix, mode, kind in imp.get_suffixes():
568
641
                if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
570
643
                    continue
571
644
                init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
572
645
                if os.path.isfile(init_path):
573
 
                    loading_path = init_path
574
 
                    package = True
 
646
                    # We've got a module here and load_module needs specific
 
647
                    # parameters.
 
648
                    loading_path = plugin_path
 
649
                    suffix = ''
 
650
                    mode = ''
 
651
                    kind = imp.PKG_DIRECTORY
575
652
                    break
576
653
        else:
577
654
            for suffix, mode, kind in imp.get_suffixes():
581
658
        if loading_path is None:
582
659
            raise ImportError('%s cannot be loaded from %s'
583
660
                              % (fullname, plugin_path))
584
 
        f = open(loading_path, mode)
 
661
        if kind is imp.PKG_DIRECTORY:
 
662
            f = None
 
663
        else:
 
664
            f = open(loading_path, mode)
585
665
        try:
586
666
            mod = imp.load_module(fullname, f, loading_path,
587
667
                                  (suffix, mode, kind))
588
 
            if package:
589
 
                # The plugin can contain modules, so be ready
590
 
                mod.__path__ = [plugin_path]
591
668
            mod.__package__ = fullname
592
669
            return mod
593
670
        finally:
594
 
            f.close()
 
671
            if f is not None:
 
672
                f.close()
595
673
 
596
674
 
597
675
# Install a dedicated importer for plugins requiring special handling