~bzr-pqm/bzr/bzr.dev

5616.7.4 by Martin Pool
Also use quiet warnings for other failures to load plugins
1
# Copyright (C) 2005-2011 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
16
17
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
18
"""bzr python plugin support.
19
20
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
3620.4.1 by Robert Collins
plugin doc strings update.
23
update any bzrlib registries it wants to extend.
24
25
See the plugin-api developer documentation for information about writing
26
plugins.
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
27
28
BZR_PLUGIN_PATH is also honoured for any plugins imported via
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
29
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
30
called.
1185.16.83 by mbp at sourcefrog
- notes on testability of plugins
31
"""
32
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
33
import os
1185.16.82 by mbp at sourcefrog
- give a quieter warning if a plugin can't be loaded
34
import sys
1996.3.17 by John Arbash Meinel
lazy_import plugin and transport/local
35
3794.1.1 by Martin Pool
Update osutils imports to fix setup.py on Windows
36
from bzrlib import osutils
37
1996.3.17 by John Arbash Meinel
lazy_import plugin and transport/local
38
from bzrlib.lazy_import import lazy_import
39
lazy_import(globals(), """
40
import imp
2256.2.3 by Robert Collins
Review feedback.
41
import re
1516 by Robert Collins
* bzrlib.plugin.all_plugins has been changed from an attribute to a
42
import types
1185.16.82 by mbp at sourcefrog
- give a quieter warning if a plugin can't be loaded
43
1996.3.17 by John Arbash Meinel
lazy_import plugin and transport/local
44
from bzrlib import (
3777.6.3 by Marius Kruger
Use bzrlib._format_version_tuple and map as per review from John.
45
    _format_version_tuple,
3224.5.10 by Andrew Bennetts
Replace some duplication with a different form of hackery.
46
    config,
3427.2.2 by James Westby
Just print the exception, keeping the API of log_exception_quietly the same.
47
    debug,
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
48
    errors,
3224.5.1 by Andrew Bennetts
Lots of assorted hackery to reduce the number of imports for common operations. Improves 'rocks', 'st' and 'help' times by ~50ms on my laptop.
49
    trace,
1996.3.17 by John Arbash Meinel
lazy_import plugin and transport/local
50
    )
6150.3.7 by Jonathan Riddell
gettext() in plugin.py and hooks.py
51
from bzrlib.i18n import gettext
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
52
from bzrlib import plugins as _mod_plugins
1996.3.17 by John Arbash Meinel
lazy_import plugin and transport/local
53
""")
54
55
56
DEFAULT_PLUGIN_PATH = None
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
57
_loaded = False
5017.2.2 by Martin Pool
Add import tariff tests
58
_plugins_disabled = False
59
60
5616.7.2 by Martin Pool
Include plugin warnings in apport crash
61
plugin_warnings = {}
62
# Map from plugin name, to list of string warnings about eg plugin
63
# dependencies.
5616.7.1 by Martin Pool
Record but don't show warnings about updated plugins
64
65
5017.2.2 by Martin Pool
Add import tariff tests
66
def are_plugins_disabled():
67
    return _plugins_disabled
68
1996.3.17 by John Arbash Meinel
lazy_import plugin and transport/local
69
1551.3.11 by Aaron Bentley
Merge from Robert
70
def disable_plugins():
71
    """Disable loading plugins.
72
73
    Future calls to load_plugins() will be ignored.
74
    """
5017.2.2 by Martin Pool
Add import tariff tests
75
    global _plugins_disabled
76
    _plugins_disabled = True
3835.2.5 by Aaron Bentley
Simplify disable_plugins implementation
77
    load_plugins([])
1551.3.11 by Aaron Bentley
Merge from Robert
78
3010.4.1 by Alexander Belchenko
bzr.exe: enable to search system-wide plugins in "plugins" subdirectory of installation directory
79
5616.7.10 by Martin Pool
Clean up describe_plugins to sort loaded and unloaded plugins together.
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.
5616.7.5 by Martin Pool
Factor out describe_loaded_plugins
85
86
    :param show_paths: If true,
87
    :returns: Iterator of text lines (including newlines.)
88
    """
89
    from inspect import getdoc
5616.7.10 by Martin Pool
Clean up describe_plugins to sort loaded and unloaded plugins together.
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:
5616.7.11 by Martin Pool
Additional tests and fixes for refactored describe_plugins.
95
            plugin = loaded_plugins[name]
5616.7.10 by Martin Pool
Clean up describe_plugins to sort loaded and unloaded plugins together.
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)'
5616.7.11 by Martin Pool
Additional tests and fixes for refactored describe_plugins.
105
            yield ("  %s\n" % doc)
5616.7.10 by Martin Pool
Clean up describe_plugins to sort loaded and unloaded plugins together.
106
            if show_paths:
107
                yield ("   %s\n" % plugin.path())
108
            del plugin
5616.7.5 by Martin Pool
Factor out describe_loaded_plugins
109
        else:
5616.7.10 by Martin Pool
Clean up describe_plugins to sort loaded and unloaded plugins together.
110
            yield "%s (failed to load)\n" % name
111
        if name in plugin_warnings:
112
            for line in plugin_warnings[name]:
5616.7.6 by Martin Pool
Use standard plugin list formatting in crash reports
113
                yield "  ** " + line + '\n'
114
        yield '\n'
5616.7.5 by Martin Pool
Factor out describe_loaded_plugins
115
116
2753.1.1 by Ian Clatworthy
(Blake Winton) BZR_PLUGIN_PATH should ignore trailiing slashes
117
def _strip_trailing_sep(path):
2652.2.6 by Blake Winton
Incorporate suggestions from Alexander Belchenko
118
    return path.rstrip("\\/")
1551.3.11 by Aaron Bentley
Merge from Robert
119
3010.4.1 by Alexander Belchenko
bzr.exe: enable to search system-wide plugins in "plugins" subdirectory of installation directory
120
5268.5.1 by Vincent Ladeuil
Reproduce bug #591215.
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):
5268.5.2 by Vincent Ladeuil
Catch the wrong path descriptions in BZR_PLUGINS_AT.
138
        try:
139
            name, path = spec.split('@')
140
        except ValueError:
6150.3.7 by Jonathan Riddell
gettext() in plugin.py and hooks.py
141
            raise errors.BzrCommandError(gettext(
142
                '"%s" is not a valid <plugin_name>@<plugin_path> description ')
5268.5.2 by Vincent Ladeuil
Catch the wrong path descriptions in BZR_PLUGINS_AT.
143
                % spec)
5268.5.1 by Vincent Ladeuil
Reproduce bug #591215.
144
        specs.append((name, path))
145
    return specs
146
147
3835.2.2 by Aaron Bentley
Allow specifying plugin paths, disable co-installed plugins.
148
def set_plugins_path(path=None):
149
    """Set the path for plugins to be loaded from.
150
151
    :param path: The list of paths to search for plugins.  By default,
152
        path will be determined using get_standard_plugins_path.
153
        if path is [], no plugins can be loaded.
154
    """
155
    if path is None:
156
        path = get_standard_plugins_path()
5086.5.5 by Vincent Ladeuil
Use BZR_PLUGINS_AT and stop mucking with BZR_PLUGIN_PATH.
157
    _mod_plugins.__path__ = path
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
158
    PluginImporter.reset()
5086.5.5 by Vincent Ladeuil
Use BZR_PLUGINS_AT and stop mucking with BZR_PLUGIN_PATH.
159
    # Set up a blacklist for disabled plugins
5086.1.10 by Vincent Ladeuil
Fixed as per review comments.
160
    disabled_plugins = os.environ.get('BZR_DISABLE_PLUGINS', None)
161
    if disabled_plugins is not None:
162
        for name in disabled_plugins.split(os.pathsep):
5086.5.4 by Vincent Ladeuil
Merge for fixes from 411413-plugin-path
163
            PluginImporter.blacklist.add('bzrlib.plugins.' + name)
5086.5.5 by Vincent Ladeuil
Use BZR_PLUGINS_AT and stop mucking with BZR_PLUGIN_PATH.
164
    # Set up a the specific paths for plugins
5268.5.1 by Vincent Ladeuil
Reproduce bug #591215.
165
    for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
166
            'BZR_PLUGINS_AT', None)):
5086.5.5 by Vincent Ladeuil
Use BZR_PLUGINS_AT and stop mucking with BZR_PLUGIN_PATH.
167
            PluginImporter.specific_paths[
168
                'bzrlib.plugins.%s' % plugin_name] = plugin_path
3835.2.2 by Aaron Bentley
Allow specifying plugin paths, disable co-installed plugins.
169
    return path
170
171
4628.2.1 by Vincent Ladeuil
Start introducing accessors for plugin paths.
172
def _append_new_path(paths, new_path):
173
    """Append a new path if it set and not already known."""
174
    if new_path is not None and new_path not in paths:
175
        paths.append(new_path)
176
    return paths
177
178
179
def get_core_plugin_path():
180
    core_path = None
181
    bzr_exe = bool(getattr(sys, 'frozen', None))
182
    if bzr_exe:    # expand path for bzr.exe
183
        # We need to use relative path to system-wide plugin
184
        # directory because bzrlib from standalone bzr.exe
185
        # could be imported by another standalone program
186
        # (e.g. bzr-config; or TortoiseBzr/Olive if/when they
187
        # will become standalone exe). [bialix 20071123]
188
        # __file__ typically is
189
        # C:\Program Files\Bazaar\lib\library.zip\bzrlib\plugin.pyc
190
        # then plugins directory is
191
        # C:\Program Files\Bazaar\plugins
192
        # so relative path is ../../../plugins
193
        core_path = osutils.abspath(osutils.pathjoin(
194
                osutils.dirname(__file__), '../../../plugins'))
195
    else:     # don't look inside library.zip
196
        # search the plugin path before the bzrlib installed dir
197
        core_path = os.path.dirname(_mod_plugins.__file__)
198
    return core_path
199
200
201
def get_site_plugin_path():
202
    """Returns the path for the site installed plugins."""
4628.2.5 by Vincent Ladeuil
Fixes prompted by review.
203
    if sys.platform == 'win32':
204
        # We don't have (yet) a good answer for windows since that is certainly
205
        # related to the way we build the installers. -- vila20090821
206
        return None
4628.2.1 by Vincent Ladeuil
Start introducing accessors for plugin paths.
207
    site_path = None
208
    try:
209
        from distutils.sysconfig import get_python_lib
210
    except ImportError:
211
        # If distutuils is not available, we just don't know where they are
212
        pass
213
    else:
214
        site_path = osutils.pathjoin(get_python_lib(), 'bzrlib', 'plugins')
215
    return site_path
216
217
218
def get_user_plugin_path():
219
    return osutils.pathjoin(config.config_dir(), 'plugins')
220
221
3835.2.2 by Aaron Bentley
Allow specifying plugin paths, disable co-installed plugins.
222
def get_standard_plugins_path():
223
    """Determine a plugin path suitable for general use."""
4628.2.2 by Vincent Ladeuil
Add [+-]{user|core|site} handling in BZR_PLUGIN_PATH.
224
    # Ad-Hoc default: core is not overriden by site but user can overrides both
4628.2.3 by Vincent Ladeuil
Update doc and add NEWS entry.
225
    # The rationale is that:
226
    # - 'site' comes last, because these plugins should always be available and
227
    #   are supposed to be in sync with the bzr installed on site.
228
    # - 'core' comes before 'site' so that running bzr from sources or a user
229
    #   installed version overrides the site version.
230
    # - 'user' comes first, because... user is always right.
231
    # - the above rules clearly defines which plugin version will be loaded if
232
    #   several exist. Yet, it is sometimes desirable to disable some directory
4672.1.1 by Vincent Ladeuil
BZR_PLUGIN_PATH can be used to fully control the plugin directories
233
    #   so that a set of plugins is disabled as once. This can be done via
4628.2.3 by Vincent Ladeuil
Update doc and add NEWS entry.
234
    #   -site, -core, -user.
235
236
    env_paths = os.environ.get('BZR_PLUGIN_PATH', '+user').split(os.pathsep)
237
    defaults = ['+core', '+site']
4628.2.2 by Vincent Ladeuil
Add [+-]{user|core|site} handling in BZR_PLUGIN_PATH.
238
239
    # The predefined references
240
    refs = dict(core=get_core_plugin_path(),
241
                site=get_site_plugin_path(),
242
                user=get_user_plugin_path())
243
4628.2.5 by Vincent Ladeuil
Fixes prompted by review.
244
    # Unset paths that should be removed
245
    for k,v in refs.iteritems():
246
        removed = '-%s' % k
247
        # defaults can never mention removing paths as that will make it
248
        # impossible for the user to revoke these removals.
249
        if removed in env_paths:
250
            env_paths.remove(removed)
251
            refs[k] = None
252
253
    # Expand references
254
    paths = []
255
    for p in env_paths + defaults:
256
        if p.startswith('+'):
5086.1.2 by Vincent Ladeuil
Cosmetic changes.
257
            # Resolve references if they are known
4628.2.5 by Vincent Ladeuil
Fixes prompted by review.
258
            try:
259
                p = refs[p[1:]]
260
            except KeyError:
5086.1.6 by Vincent Ladeuil
Crude fix for bug #411413.
261
                # Leave them untouched so user can still use paths starting
262
                # with '+'
4628.2.5 by Vincent Ladeuil
Fixes prompted by review.
263
                pass
264
        _append_new_path(paths, p)
4628.2.2 by Vincent Ladeuil
Add [+-]{user|core|site} handling in BZR_PLUGIN_PATH.
265
3835.2.7 by Aaron Bentley
Add tests for plugins
266
    # Get rid of trailing slashes, since Python can't handle them when
267
    # it tries to import modules.
4628.2.2 by Vincent Ladeuil
Add [+-]{user|core|site} handling in BZR_PLUGIN_PATH.
268
    paths = map(_strip_trailing_sep, paths)
269
    return paths
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
270
271
3835.2.4 by Aaron Bentley
Allow paths to be specified to load_plugins
272
def load_plugins(path=None):
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
273
    """Load bzrlib plugins.
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
274
275
    The environment variable BZR_PLUGIN_PATH is considered a delimited
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
276
    set of paths to look through. Each entry is searched for `*.py`
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
277
    files (and whatever other extensions are used in the platform,
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
278
    such as `*.pyd`).
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
279
5086.1.6 by Vincent Ladeuil
Crude fix for bug #411413.
280
    load_from_path() provides the underlying mechanism and is called with
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
281
    the default directory list to provide the normal behaviour.
3835.2.4 by Aaron Bentley
Allow paths to be specified to load_plugins
282
283
    :param path: The list of paths to search for plugins.  By default,
284
        path will be determined using get_standard_plugins_path.
285
        if path is [], no plugins can be loaded.
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
286
    """
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
287
    global _loaded
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
288
    if _loaded:
289
        # People can make sure plugins are loaded, they just won't be twice
290
        return
291
    _loaded = True
292
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
293
    # scan for all plugins in the path.
3835.2.4 by Aaron Bentley
Allow paths to be specified to load_plugins
294
    load_from_path(set_plugins_path(path))
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
295
296
297
def load_from_path(dirs):
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
298
    """Load bzrlib plugins found in each dir in dirs.
299
300
    Loading a plugin means importing it into the python interpreter.
301
    The plugin is expected to make calls to register commands when
302
    it's loaded (or perhaps access other hooks in future.)
303
304
    Plugins are loaded into bzrlib.plugins.NAME, and can be found there
305
    for future reference.
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
306
307
    The python module path for bzrlib.plugins will be modified to be 'dirs'.
2652.2.5 by Blake Winton
Get rid of CRs.
308
    """
5086.5.10 by Vincent Ladeuil
Cleanup docs.
309
    # Explicitly load the plugins with a specific path
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
310
    for fullname, path in PluginImporter.specific_paths.iteritems():
311
        name = fullname[len('bzrlib.plugins.'):]
5086.5.10 by Vincent Ladeuil
Cleanup docs.
312
        _load_plugin_module(name, path)
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
313
2652.2.5 by Blake Winton
Get rid of CRs.
314
    # We need to strip the trailing separators here as well as in the
315
    # set_plugins_path function because calling code can pass anything in to
316
    # this function, and since it sets plugins.__path__, it should set it to
317
    # something that will be valid for Python to use (in case people try to
2652.2.4 by Blake Winton
Add a note explaining why I strip the slashes twice.
318
    # run "import bzrlib.plugins.PLUGINNAME" after calling this function).
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
319
    _mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
320
    for d in dirs:
321
        if not d:
322
            continue
3224.5.1 by Andrew Bennetts
Lots of assorted hackery to reduce the number of imports for common operations. Improves 'rocks', 'st' and 'help' times by ~50ms on my laptop.
323
        trace.mutter('looking for plugins in %s', d)
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
324
        if os.path.isdir(d):
325
            load_from_dir(d)
326
327
328
# backwards compatability: load_from_dirs was the old name
329
# This was changed in 0.15
330
load_from_dirs = load_from_path
331
332
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
333
def _find_plugin_module(dir, name):
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
334
    """Check if there is a valid python module that can be loaded as a plugin.
335
5086.5.10 by Vincent Ladeuil
Cleanup docs.
336
    :param dir: The directory where the search is performed.
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
337
    :param path: An existing file path, either a python file or a package
338
        directory.
339
340
    :return: (name, path, description) name is the module name, path is the
341
        file to load and description is the tuple returned by
342
        imp.get_suffixes().
343
    """
344
    path = osutils.pathjoin(dir, name)
345
    if os.path.isdir(path):
346
        # Check for a valid __init__.py file, valid suffixes depends on -O and
347
        # can be .py, .pyc and .pyo
348
        for suffix, mode, kind in imp.get_suffixes():
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
349
            if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
350
                # We don't recognize compiled modules (.so, .dll, etc)
351
                continue
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
352
            init_path = osutils.pathjoin(path, '__init__' + suffix)
353
            if os.path.isfile(init_path):
354
                return name, init_path, (suffix, mode, kind)
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
355
    else:
356
        for suffix, mode, kind in imp.get_suffixes():
357
            if name.endswith(suffix):
358
                # Clean up the module name
359
                name = name[:-len(suffix)]
360
                if kind == imp.C_EXTENSION and name.endswith('module'):
361
                    name = name[:-len('module')]
362
                return name, path, (suffix, mode, kind)
363
    # There is no python module here
364
    return None, None, (None, None, None)
365
366
5616.7.4 by Martin Pool
Also use quiet warnings for other failures to load plugins
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
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
372
def _load_plugin_module(name, dir):
5086.5.14 by Vincent Ladeuil
Fix bug #552922 by controlling which files can be used to load a plugin.
373
    """Load plugin name from dir.
5086.5.10 by Vincent Ladeuil
Cleanup docs.
374
375
    :param name: The plugin name in the bzrlib.plugins namespace.
376
    :param dir: The directory the plugin is loaded from for error messages.
377
    """
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
378
    if ('bzrlib.plugins.%s' % name) in PluginImporter.blacklist:
379
        return
380
    try:
381
        exec "import bzrlib.plugins.%s" % name in {}
382
    except KeyboardInterrupt:
383
        raise
384
    except errors.IncompatibleAPI, e:
5616.7.1 by Martin Pool
Record but don't show warnings about updated plugins
385
        warning_message = (
386
            "Unable to load plugin %r. It requested API version "
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
387
            "%s of module %s but the minimum exported version is %s, and "
388
            "the maximum is %s" %
389
            (name, e.wanted, e.api, e.minimum, e.current))
5616.7.4 by Martin Pool
Also use quiet warnings for other failures to load plugins
390
        record_plugin_warning(name, warning_message)
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
391
    except Exception, e:
392
        trace.warning("%s" % e)
393
        if re.search('\.|-| ', name):
394
            sanitised_name = re.sub('[-. ]', '_', name)
395
            if sanitised_name.startswith('bzr_'):
396
                sanitised_name = sanitised_name[len('bzr_'):]
397
            trace.warning("Unable to load %r in %r as a plugin because the "
398
                    "file path isn't a valid module name; try renaming "
399
                    "it to %r." % (name, dir, sanitised_name))
400
        else:
5616.7.4 by Martin Pool
Also use quiet warnings for other failures to load plugins
401
            record_plugin_warning(
402
                name,
403
                'Unable to load plugin %r from %r' % (name, dir))
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
404
        trace.log_exception_quietly()
405
        if 'error' in debug.debug_flags:
406
            trace.print_exception(sys.exc_info(), sys.stderr)
407
408
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
409
def load_from_dir(d):
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
410
    """Load the plugins in directory d.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
411
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
412
    d must be in the plugins module path already.
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
413
    This function is called once for each directory in the module path.
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
414
    """
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
415
    plugin_names = set()
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
416
    for p in os.listdir(d):
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
417
        name, path, desc = _find_plugin_module(d, p)
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
418
        if name is not None:
419
            if name == '__init__':
420
                # We do nothing with the __init__.py file in directories from
421
                # the bzrlib.plugins module path, we may want to, one day
422
                # -- vila 20100316.
423
                continue # We don't load __init__.py in the plugins dirs
424
            elif getattr(_mod_plugins, name, None) is not None:
425
                # The module has already been loaded from another directory
426
                # during a previous call.
427
                # FIXME: There should be a better way to report masked plugins
428
                # -- vila 20100316
429
                trace.mutter('Plugin name %s already loaded', name)
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
430
            else:
5086.5.1 by Vincent Ladeuil
Slight refactoring preparing fix for bug #82693.
431
                plugin_names.add(name)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
432
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
433
    for name in plugin_names:
5086.5.8 by Vincent Ladeuil
Make sure we can load from a non-standard directory name.
434
        _load_plugin_module(name, d)
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
435
436
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
437
def plugins():
438
    """Return a dictionary of the plugins.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
439
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
440
    Each item in the dictionary is a PlugIn object.
441
    """
442
    result = {}
443
    for name, plugin in _mod_plugins.__dict__.items():
444
        if isinstance(plugin, types.ModuleType):
445
            result[name] = PlugIn(name, plugin)
446
    return result
447
448
5609.23.6 by Martin Pool
Show concise list of plugins in non-apport crash; add test for this
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
2432.1.24 by Robert Collins
Add plugins as a help index.
460
class PluginsHelpIndex(object):
461
    """A help index that returns help topics for plugins."""
462
463
    def __init__(self):
464
        self.prefix = 'plugins/'
465
466
    def get_topics(self, topic):
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
467
        """Search for topic in the loaded plugins.
468
469
        This will not trigger loading of new plugins.
470
471
        :param topic: A topic to search for.
472
        :return: A list which is either empty or contains a single
473
            RegisteredTopic entry.
474
        """
475
        if not topic:
476
            return []
477
        if topic.startswith(self.prefix):
478
            topic = topic[len(self.prefix):]
479
        plugin_module_name = 'bzrlib.plugins.%s' % topic
480
        try:
481
            module = sys.modules[plugin_module_name]
482
        except KeyError:
483
            return []
484
        else:
485
            return [ModuleHelpTopic(module)]
486
487
488
class ModuleHelpTopic(object):
489
    """A help topic which returns the docstring for a module."""
490
491
    def __init__(self, module):
492
        """Constructor.
493
494
        :param module: The module for which help should be generated.
495
        """
496
        self.module = module
497
3984.4.5 by Ian Clatworthy
help xxx is full help; xxx -h is concise help
498
    def get_help_text(self, additional_see_also=None, verbose=True):
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
499
        """Return a string with the help for this topic.
500
501
        :param additional_see_also: Additional help topics to be
502
            cross-referenced.
503
        """
504
        if not self.module.__doc__:
505
            result = "Plugin '%s' has no docstring.\n" % self.module.__name__
506
        else:
507
            result = self.module.__doc__
508
        if result[-1] != '\n':
509
            result += '\n'
6059.3.1 by Vincent Ladeuil
Provide per-config option help
510
        from bzrlib import help_topics
6059.3.4 by Vincent Ladeuil
Fix forgotten renaming.
511
        result += help_topics._format_see_also(additional_see_also)
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
512
        return result
2432.1.29 by Robert Collins
Add get_help_topic to ModuleHelpTopic.
513
514
    def get_help_topic(self):
6059.3.4 by Vincent Ladeuil
Fix forgotten renaming.
515
        """Return the module help topic: its basename."""
2432.1.30 by Robert Collins
Fix the ModuleHelpTopic get_help_topic to be tested with closer to real world data and strip the bzrlib.plugins. prefix from the name.
516
        return self.module.__name__[len('bzrlib.plugins.'):]
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
517
518
519
class PlugIn(object):
520
    """The bzrlib representation of a plugin.
521
522
    The PlugIn object provides a way to manipulate a given plugin module.
523
    """
524
525
    def __init__(self, name, module):
526
        """Construct a plugin for module."""
527
        self.name = name
528
        self.module = module
529
5939.3.2 by Andrew Bennetts
Take a slightly more direct approach by largely preserving BZR_DISABLE_PLUGINS/BZR_PLUGINS_AT.
530
    def path(self):
531
        """Get the path that this plugin was loaded from."""
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
532
        if getattr(self.module, '__path__', None) is not None:
533
            return os.path.abspath(self.module.__path__[0])
534
        elif getattr(self.module, '__file__', None) is not None:
3193.2.1 by Alexander Belchenko
show path to plugin module as *.py instead of *.pyc if python source available
535
            path = os.path.abspath(self.module.__file__)
536
            if path[-4:] in ('.pyc', '.pyo'):
537
                pypath = path[:-4] + '.py'
538
                if os.path.isfile(pypath):
539
                    path = pypath
540
            return path
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
541
        else:
5939.3.2 by Andrew Bennetts
Take a slightly more direct approach by largely preserving BZR_DISABLE_PLUGINS/BZR_PLUGINS_AT.
542
            return repr(self.module)
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
543
544
    def __str__(self):
545
        return "<%s.%s object at %s, name=%s, module=%s>" % (
546
            self.__class__.__module__, self.__class__.__name__, id(self),
547
            self.name, self.module)
548
549
    __repr__ = __str__
550
551
    def test_suite(self):
552
        """Return the plugin's test suite."""
553
        if getattr(self.module, 'test_suite', None) is not None:
554
            return self.module.test_suite()
555
        else:
556
            return None
557
3302.8.21 by Vincent Ladeuil
Fixed as per Robert's review.
558
    def load_plugin_tests(self, loader):
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
559
        """Return the adapted plugin's test suite.
560
561
        :param loader: The custom loader that should be used to load additional
562
            tests.
563
564
        """
565
        if getattr(self.module, 'load_tests', None) is not None:
3302.8.11 by Vincent Ladeuil
Simplify plugin.load_tests.
566
            return loader.loadTestsFromModule(self.module)
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
567
        else:
568
            return None
569
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
570
    def version_info(self):
571
        """Return the plugin's version_tuple or None if unknown."""
572
        version_info = getattr(self.module, 'version_info', None)
3777.6.7 by Marius Kruger
* Can now also handle non-iteratable and string plugin versions.
573
        if version_info is not None:
574
            try:
575
                if isinstance(version_info, types.StringType):
576
                    version_info = version_info.split('.')
577
                elif len(version_info) == 3:
578
                    version_info = tuple(version_info) + ('final', 0)
579
            except TypeError, e:
580
                # The given version_info isn't even iteratible
581
                trace.log_exception_quietly()
582
                version_info = (version_info,)
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
583
        return version_info
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
584
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
585
    def _get__version__(self):
586
        version_info = self.version_info()
3777.6.1 by Marius Kruger
Try to return something usefull for plugins with bad version numbers,
587
        if version_info is None or len(version_info) == 0:
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
588
            return "unknown"
3777.6.1 by Marius Kruger
Try to return something usefull for plugins with bad version numbers,
589
        try:
3777.6.3 by Marius Kruger
Use bzrlib._format_version_tuple and map as per review from John.
590
            version_string = _format_version_tuple(version_info)
3777.6.6 by Marius Kruger
catch only ValueError, TypeError, IndexError as per feedback from John
591
        except (ValueError, TypeError, IndexError), e:
592
            trace.log_exception_quietly()
3777.6.1 by Marius Kruger
Try to return something usefull for plugins with bad version numbers,
593
            # try to return something usefull for bad plugins, in stead of
594
            # stack tracing.
3777.6.3 by Marius Kruger
Use bzrlib._format_version_tuple and map as per review from John.
595
            version_string = '.'.join(map(str, version_info))
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
596
        return version_string
597
598
    __version__ = property(_get__version__)
5086.1.6 by Vincent Ladeuil
Crude fix for bug #411413.
599
600
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
601
class _PluginImporter(object):
5086.5.10 by Vincent Ladeuil
Cleanup docs.
602
    """An importer tailored to bzr specific needs.
603
604
    This is a singleton that takes care of:
605
    - disabled plugins specified in 'blacklist',
606
    - plugins that needs to be loaded from specific directories.
607
    """
5086.1.7 by Vincent Ladeuil
Cleaner fix for bug #411413.
608
609
    def __init__(self):
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
610
        self.reset()
611
612
    def reset(self):
613
        self.blacklist = set()
614
        self.specific_paths = {}
5086.1.6 by Vincent Ladeuil
Crude fix for bug #411413.
615
616
    def find_module(self, fullname, parent_path=None):
5086.5.10 by Vincent Ladeuil
Cleanup docs.
617
        """Search a plugin module.
618
619
        Disabled plugins raise an import error, plugins with specific paths
620
        returns a specific loader.
621
622
        :return: None if the plugin doesn't need special handling, self
623
            otherwise.
624
        """
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
625
        if not fullname.startswith('bzrlib.plugins.'):
626
            return None
5086.1.7 by Vincent Ladeuil
Cleaner fix for bug #411413.
627
        if fullname in self.blacklist:
5086.1.6 by Vincent Ladeuil
Crude fix for bug #411413.
628
            raise ImportError('%s is disabled' % fullname)
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
629
        if fullname in self.specific_paths:
630
            return self
5086.1.6 by Vincent Ladeuil
Crude fix for bug #411413.
631
        return None
5086.1.7 by Vincent Ladeuil
Cleaner fix for bug #411413.
632
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
633
    def load_module(self, fullname):
5086.5.10 by Vincent Ladeuil
Cleanup docs.
634
        """Load a plugin from a specific directory."""
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
635
        # We are called only for specific paths
5086.5.14 by Vincent Ladeuil
Fix bug #552922 by controlling which files can be used to load a plugin.
636
        plugin_path = self.specific_paths[fullname]
637
        loading_path = None
638
        if os.path.isdir(plugin_path):
639
            for suffix, mode, kind in imp.get_suffixes():
640
                if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
641
                    # We don't recognize compiled modules (.so, .dll, etc)
642
                    continue
643
                init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
644
                if os.path.isfile(init_path):
5268.6.3 by Vincent Ladeuil
BZR_PLUGINS_AT should use packages properly to handle relative imports.
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
5086.5.14 by Vincent Ladeuil
Fix bug #552922 by controlling which files can be used to load a plugin.
651
                    break
652
        else:
653
            for suffix, mode, kind in imp.get_suffixes():
654
                if plugin_path.endswith(suffix):
655
                    loading_path = plugin_path
656
                    break
657
        if loading_path is None:
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
658
            raise ImportError('%s cannot be loaded from %s'
5086.5.14 by Vincent Ladeuil
Fix bug #552922 by controlling which files can be used to load a plugin.
659
                              % (fullname, plugin_path))
5268.6.3 by Vincent Ladeuil
BZR_PLUGINS_AT should use packages properly to handle relative imports.
660
        if kind is imp.PKG_DIRECTORY:
661
            f = None
662
        else:
663
            f = open(loading_path, mode)
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
664
        try:
5086.5.14 by Vincent Ladeuil
Fix bug #552922 by controlling which files can be used to load a plugin.
665
            mod = imp.load_module(fullname, f, loading_path,
666
                                  (suffix, mode, kind))
5086.5.12 by Vincent Ladeuil
Force __package__ to fix pqm failure.
667
            mod.__package__ = fullname
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
668
            return mod
669
        finally:
5268.6.3 by Vincent Ladeuil
BZR_PLUGINS_AT should use packages properly to handle relative imports.
670
            if f is not None:
671
                f.close()
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
672
673
5086.5.10 by Vincent Ladeuil
Cleanup docs.
674
# Install a dedicated importer for plugins requiring special handling
5086.5.3 by Vincent Ladeuil
First shot at loading plugins from a specific directory.
675
PluginImporter = _PluginImporter()
676
sys.meta_path.append(PluginImporter)