Merge lp:~bzr/bzr/412930-plugin-path into lp:~bzr/bzr/trunk-old

Proposed by Vincent Ladeuil
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merge reported by: Vincent Ladeuil
Merged at revision: not available
Proposed branch: lp:~bzr/bzr/412930-plugin-path
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 535 lines
To merge this branch: bzr merge lp:~bzr/bzr/412930-plugin-path
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+10518@code.launchpad.net

This proposal supersedes a proposal from 2009-08-20.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal

This patch add the capability to fully control BZR_PLUGIN_PATH by defining magic values
for user, core and site directories and a way to remove them from the search path.

It's compatible with the actual implementation.

This will allow the test/build farm to test:
- the core plugins (the test are currently run with --no-plugins),
- a chosen set of plugins

Thanks in advance to the reviewer :-)

Revision history for this message
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal
Download full text (4.2 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~bzr/bzr/412930-plugin-path into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This patch add the capability to fully control BZR_PLUGIN_PATH by defining magic values
> for user, core and site directories and a way to remove them from the search path.
>
> It's compatible with the actual implementation.
>
> This will allow the test/build farm to test:
> - the core plugins (the test are currently run with --no-plugins),
> - a chosen set of plugins
>
> Thanks in advance to the reviewer :-)
>

So does this break the existing BZR_PLUGIN_PATH behavior? At first I
thought it did, but it looks like the existing code says "if
BZR_PLUGIN_PATH is set, override default location". Which matches your
"+user" version.

+By default all directories specified in ``BZR_PLUGIN_PATH``
+replace 'user' in the but keep searching in 'core' then 'site'.

^- bad grammar here.

By default if BZR_PLUGIN_PATH is set, it replaces searching in 'user'.
However it will continue to search in 'core' and 'site' unless they are
explicitly removed.

...

+ # Handle removals first
+ def unset_ref(p):
+ if p.startswith('-'):
+ key = p[1:]
+ if key in refs: # Needed to handle multiple removals
+ refs[key] = None
+ p = None
+ return p
+
+ defaults = [p for p in defaults if unset_ref(p) is not None]
+ env_paths = [p for p in env_paths if unset_ref(p) is not None]

^- This doesn't look right to me.

defaults = [p for p in defaults if unset_ref(p) is not None]

Specifically:
 1) I don't think you would ever have '-site' in defaults, for one
    thing, it is a local variable.
 2) If you did have '-site' in the list, then it would see that it is
    present in refs, set p to None, and then the 'if unset_ref(p) is not
    None' would return True, and '-site' is then included in the final
    defaults list.

I'm not 100% sure that doing:

BZR_PLUGIN_PATH='-site:+site'

Should ignore the site path. I would actually say 'last value wins'. So
that you can just do:

BZR_PLUGIN_PATH="$BZR_PLUGIN_PATH:+site" bzr test

I'm not stuck on this, but I certainly find it hard to follow exactly
what is going on in your get_standard_plugins_path() function. (The diff
makes things worse, because it mixes statements a little bit.)

I believe you are also trying to allow for altering the order of the
switch. So doing:

BZR_PLUGIN_PATH='+site:+user'

Would then search site locations before user locations.

I would probably do something more like:

env_path = os.environ.get('BZR_PLUGIN_PATH', '+user').split(os.pathsep)

default_core = True
default_site = True
plugin_paths = []
for path in env_path:
  if path.startswith('-'):
    if path == '-site':
      default_site = False
      if '+site' in plugin_paths:
        plugin_paths.remove('+site')
    elif path == '-core':
    ...
 elif path.startswith('+'):
    if path == '+site':
      default_site = False
      if '+site' in plugin_paths:
        plugin_paths.remove('+site')
    elif path == '+core':
      ...

    plugin_paths.append(path)
 else:
    ...

Read more...

review: Needs Fixing
Revision history for this message
Alexander Belchenko (bialix) wrote : Posted in a previous version of this proposal

30 +* the size specific plugin directory if applicable (containing
31 + the site plugins).

the size specific plugin directory? What is it?

Revision history for this message
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Alexander Belchenko wrote:
> 30 +* the size specific plugin directory if applicable (containing
> 31 + the site plugins).
>
> the size specific plugin directory? What is it?

site-specific. It only really exists on Linux where they split out the
python path because of wanting a separate "/usr/lib64" versus "/usr/lib"
and other such tweaks for the different between 64-bit compiled code,
and pure python code.

At least, that is how I understand it.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkqNlz0ACgkQJdeBCYSNAAMxxQCeOJopPvaV0JgNHuf+5pRpyl6m
5y4An0jzQ0IjHY4ZVHIm/+370zinGdpX
=WfNs
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal
Download full text (7.0 KiB)

>>>>> "jam" == John A Meinel <email address hidden> writes:

    jam> Review: Needs Fixing
    jam> Vincent Ladeuil wrote:
    >> Vincent Ladeuil has proposed merging lp:~bzr/bzr/412930-plugin-path into lp:bzr.
    >>
    >> Requested reviews:
    >> bzr-core (bzr-core)
    >>
    >> This patch add the capability to fully control BZR_PLUGIN_PATH by defining magic values
    >> for user, core and site directories and a way to remove them from the search path.
    >>
    >> It's compatible with the actual implementation.
    >>
    >> This will allow the test/build farm to test:
    >> - the core plugins (the test are currently run with --no-plugins),
    >> - a chosen set of plugins
    >>
    >> Thanks in advance to the reviewer :-)
    >>

    jam> So does this break the existing BZR_PLUGIN_PATH behavior?

No. But it makes the code more complex.

    jam> At first I thought it did, but it looks like the
    jam> existing code says "if BZR_PLUGIN_PATH is set, override
    jam> default location". Which matches your "+user" version.

Yes. That's the complicated part, 'core' and 'site' cannot be
removed without trickery.

    jam> +By default all directories specified in ``BZR_PLUGIN_PATH``
    jam> +replace 'user' in the but keep searching in 'core' then 'site'.

    jam> ^- bad grammar here.

    jam> By default if BZR_PLUGIN_PATH is set, it replaces searching in 'user'.
    jam> However it will continue to search in 'core' and 'site' unless they are
    jam> explicitly removed.

Thanks, fixed.

    jam> ...

    jam> + # Handle removals first
    jam> + def unset_ref(p):
    jam> + if p.startswith('-'):
    jam> + key = p[1:]
    jam> + if key in refs: # Needed to handle multiple removals
    jam> + refs[key] = None
    jam> + p = None
    jam> + return p
    jam> +
    jam> + defaults = [p for p in defaults if unset_ref(p) is not None]
    jam> + env_paths = [p for p in env_paths if unset_ref(p) is not None]

    jam> ^- This doesn't look right to me.

Then forget about it I removed that part :)

    jam> defaults = [p for p in defaults if unset_ref(p) is not None]

    jam> Specifically:

    jam> 1) I don't think you would ever have '-site' in defaults, for one
    jam> thing, it is a local variable.

You won't have '-site', but you have '+site', and you want it to
get removed when '-site' is specified. That's even the only way
to ensure that s 'site' plugin is not involved.

Both https://bugs.edge.launchpad.net/bzr/+bug/412930 and
https://bugs.edge.launchpad.net/bzr/+bug/316192 needs that
feature.

    jam> 2) If you did have '-site' in the list, then it would
    jam> see that it is present in refs, set p to None, and then
    jam> the 'if unset_ref(p) is not None' would return True,

???

if p is None, 'unset_ref(p) is not None' is False

    jam> and '-site' is then included in the final defaults
    jam> list.

No.

    jam> I'm not 100% sure that doing:

    jam> BZR_PLUGIN_PATH='-site:+site'

    jam> Should ignore the site path. I would actually say 'last
    jam> value wins'. So that you can just do:

    jam> BZR_PLUGIN_PATH="$BZR_PLU...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal

>>>>> "jam" == John A Meinel <email address hidden> writes:

    jam> Alexander Belchenko wrote:
    >> 30 +* the size specific plugin directory if applicable (containing
    >> 31 + the site plugins).
    >>
    >> the size specific plugin directory? What is it?

    jam> site-specific.

Right. The intent is to give access to plugins installed by
whoever administer the site, this directory contains plugins that
can be used by all users.

    jam> It only really exists on Linux

Wrong.

The get_python_lib() doc string in distutils says:

    """Return the directory containing the Python library (standard or
    site additions).

    If 'plat_specific' is true, return the directory containing
    platform-specific modules, i.e. any module from a non-pure-Python
    module distribution; otherwise, return the platform-shared library
    directory. If 'standard_lib' is true, return the directory
    containing standard Python library modules; otherwise, return the
    directory for site-specific modules.

    If 'prefix' is supplied, use it instead of sys.prefix or
    sys.exec_prefix -- i.e., ignore 'plat_specific'.
    """

On linux it can be 'dist-packages' or 'site-packages' in the
python hierarchy.

On mac it's 'Lib/site-packages' but again the installers may be
wrong here (I don't know them enough to be sure, they may well be
right).

On os.name == "nt" (windows right ?) it seems to be
'Lib/site-packages'.

So, as I understand it, it makes no sense for bzr.exe (but we may
want to say it's 'All Users/Application Data/Bazaar/Plugins') and
when running from sources (or from easy_install when it will
work), we may want to activate it (in addition to 'All Users/...')

In the mean time, I kept the compatibility with previous versions
and add the win32 check that I accidentally removed in my patch.

Feedback welcome even if I think that should be the subject of
another submission are there are certainly tweaks to be done in
the windows installers.

Revision history for this message
Alexander Belchenko (bialix) wrote : Posted in a previous version of this proposal

Vincent Ladeuil пишет:
>>>>>> "jam" == John A Meinel <email address hidden> writes:
>
> jam> Alexander Belchenko wrote:
> >> 30 +* the size specific plugin directory if applicable (containing
> >> 31 + the site plugins).
> >>
> >> the size specific plugin directory? What is it?
>
> jam> site-specific.

I understand, it was light joke. But it was not very good joke. Move on.

> Right. The intent is to give access to plugins installed by
> whoever administer the site, this directory contains plugins that
> can be used by all users.
>
> jam> It only really exists on Linux
>
> Wrong.

Really?

> The get_python_lib() doc string in distutils says:
[...]
>
> On linux it can be 'dist-packages' or 'site-packages' in the
> python hierarchy.
>
> On mac it's 'Lib/site-packages' but again the installers may be
> wrong here (I don't know them enough to be sure, they may well be
> right).
>
> On os.name == "nt" (windows right ?) it seems to be
> 'Lib/site-packages'.

Yes, so there is no distinction between "site" and "core" for Windows?

Because if I run bzr from sources current bzrlib/plugin.py (as I know)
even don't try to load plugins from
C:\PythonXX\Lib\site-packages\bzrlib\plugins

And perhaps it's right? No? Why?

If yes, what should be the right thing if I run bzr 3.0 from sources and
have installed bzr 2.x in site-packages? Do you think they will be
compatible? Quick responses on IRC reveal that it's not.

> So, as I understand it, it makes no sense for bzr.exe

For bzr.exe there is only "core" plugins I think. They are installed as
$(INSTALL_DIR)\plugins\, e.g. C:\Program Files\Bazaar\plugins

> (but we may
> want to say it's 'All Users/Application Data/Bazaar/Plugins')

I'm recall vaguely it was even discussed. But honestly: I don't want
this. Actually the less places bzr will use to look for plugins the
faster its startup time will be. Because on Windows it makes huge
difference if you need to read 1 file of 10MB and 100 files of 100K. The
less disk IO the faster application.

> and
> when running from sources (or from easy_install when it will
> work), we may want to activate it (in addition to 'All Users/...')

I have no opinion on using bzr in easy_install way. I love bzr.exe and
think it's the best we have today.

> In the mean time, I kept the compatibility with previous versions
> and add the win32 check that I accidentally removed in my patch.

If you return this code back then I should not worried?

> Feedback welcome even if I think that should be the subject of
> another submission are there are certainly tweaks to be done in
> the windows installers.

I'm still trying to understand what question I should answer.
I hope said above will help you. Ask me more and preferably directly,
I'm not subscribed on merge proposal comments.

Revision history for this message
Alexander Belchenko (bialix) wrote : Posted in a previous version of this proposal

My 5 kopecks: as hardcore windows user I don't really like idea of Vincent example:

BZR_PLUGIN_PATH=-site bzr selftest ...

Because on pure Windows I can't set env variable only for one command. But I can't imagine why I need the proposed feature so I'm actually indifferent on this. But as windows users I'd prefer global command-line options similar to --no-plugins. Maybe:

--plugins=-site

would work as alternate syntax. Or maybe it will be even more obscure.
I dunno.

Revision history for this message
Vincent Ladeuil (vila) wrote :

That version is simpler, see the superseded proposal for relevant comments.

Revision history for this message
John A Meinel (jameinel) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-08-30 23:51:10 +0000
+++ NEWS 2009-08-31 04:35:30 +0000
@@ -237,6 +237,11 @@
237New Features237New Features
238************238************
239239
240* Give more control on BZR_PLUGIN_PATH by providing a way to refer to or
241 disable the user, site and core plugin directories.
242 (Vincent Ladeuil, #412930, #316192, #145612)
243
244
240Bug Fixes245Bug Fixes
241*********246*********
242247
243248
=== modified file 'bzrlib/help_topics/en/configuration.txt'
--- bzrlib/help_topics/en/configuration.txt 2009-06-26 18:13:41 +0000
+++ bzrlib/help_topics/en/configuration.txt 2009-08-31 04:35:30 +0000
@@ -63,6 +63,60 @@
63~~~~~~~~~~~~~~~63~~~~~~~~~~~~~~~
6464
65The path to the plugins directory that Bazaar should use.65The path to the plugins directory that Bazaar should use.
66If not set, Bazaar will search for plugins in:
67
68* the user specific plugin directory (containing the ``user`` plugins),
69
70* the bzrlib directory (containing the ``core`` plugins),
71
72* the site specific plugin directory if applicable (containing
73 the ``site`` plugins).
74
75If ``BZR_PLUGIN_PATH`` is set in any fashion, it will change the
76the way the plugin are searched.
77
78As for the ``PATH`` variables, if multiple directories are
79specified in ``BZR_PLUGIN_PATH`` they should be separated by the
80platform specific appropriate character (':' on Unix/Linux/etc,
81';' on windows)
82
83By default if ``BZR_PLUGIN_PATH`` is set, it replaces searching
84in ``user``. However it will continue to search in ``core`` and
85``site`` unless they are explicitly removed.
86
87If you need to change the order or remove one of these
88directories, you should use special values:
89
90* ``-user``, ``-core``, ``-site`` will remove the corresponding
91 path from the default values,
92
93* ``+user``, ``+core``, ``+site`` will add the corresponding path
94 before the remaining default values (and also remove it from
95 the default values).
96
97Note that the special values 'user', 'core' and 'site' should be
98used literally, they will be substituted by the corresponding,
99platform specific, values.
100
101Examples:
102^^^^^^^^^
103
104The examples below uses ':' as the separator, windows users
105should use ';'.
106
107Overriding the default user plugin directory:
108``BZR_PLUGIN_PATH='/path/to/my/other/plugins'``
109
110Disabling the site directory while retaining the user directory:
111``BZR_PLUGIN_PATH='-site:+user'``
112
113Disabling all plugins (better achieved with --no-plugins):
114``BZR_PLUGIN_PATH='-user:-core:-site'``
115
116Overriding the default site plugin directory:
117``BZR_PLUGIN_PATH='/path/to/my/site/plugins:-site':+user``
118
119
66120
67BZRPATH121BZRPATH
68~~~~~~~122~~~~~~~
69123
=== modified file 'bzrlib/plugin.py'
--- bzrlib/plugin.py 2009-03-24 01:53:42 +0000
+++ bzrlib/plugin.py 2009-08-31 04:35:30 +0000
@@ -52,12 +52,16 @@
52from bzrlib import plugins as _mod_plugins52from bzrlib import plugins as _mod_plugins
53""")53""")
5454
55from bzrlib.symbol_versioning import deprecated_function55from bzrlib.symbol_versioning import (
56 deprecated_function,
57 deprecated_in,
58 )
5659
5760
58DEFAULT_PLUGIN_PATH = None61DEFAULT_PLUGIN_PATH = None
59_loaded = False62_loaded = False
6063
64@deprecated_function(deprecated_in((2, 0, 0)))
61def get_default_plugin_path():65def get_default_plugin_path():
62 """Get the DEFAULT_PLUGIN_PATH"""66 """Get the DEFAULT_PLUGIN_PATH"""
63 global DEFAULT_PLUGIN_PATH67 global DEFAULT_PLUGIN_PATH
@@ -91,13 +95,15 @@
91 return path95 return path
9296
9397
94def get_standard_plugins_path():98def _append_new_path(paths, new_path):
95 """Determine a plugin path suitable for general use."""99 """Append a new path if it set and not already known."""
96 path = os.environ.get('BZR_PLUGIN_PATH',100 if new_path is not None and new_path not in paths:
97 get_default_plugin_path()).split(os.pathsep)101 paths.append(new_path)
98 # Get rid of trailing slashes, since Python can't handle them when102 return paths
99 # it tries to import modules.103
100 path = map(_strip_trailing_sep, path)104
105def get_core_plugin_path():
106 core_path = None
101 bzr_exe = bool(getattr(sys, 'frozen', None))107 bzr_exe = bool(getattr(sys, 'frozen', None))
102 if bzr_exe: # expand path for bzr.exe108 if bzr_exe: # expand path for bzr.exe
103 # We need to use relative path to system-wide plugin109 # We need to use relative path to system-wide plugin
@@ -110,25 +116,83 @@
110 # then plugins directory is116 # then plugins directory is
111 # C:\Program Files\Bazaar\plugins117 # C:\Program Files\Bazaar\plugins
112 # so relative path is ../../../plugins118 # so relative path is ../../../plugins
113 path.append(osutils.abspath(osutils.pathjoin(119 core_path = osutils.abspath(osutils.pathjoin(
114 osutils.dirname(__file__), '../../../plugins')))120 osutils.dirname(__file__), '../../../plugins'))
115 if not bzr_exe: # don't look inside library.zip121 else: # don't look inside library.zip
116 # search the plugin path before the bzrlib installed dir122 # search the plugin path before the bzrlib installed dir
117 path.append(os.path.dirname(_mod_plugins.__file__))123 core_path = os.path.dirname(_mod_plugins.__file__)
118 # search the arch independent path if we can determine that and124 return core_path
119 # the plugin is found nowhere else125
120 if sys.platform != 'win32':126
121 try:127def get_site_plugin_path():
122 from distutils.sysconfig import get_python_lib128 """Returns the path for the site installed plugins."""
123 except ImportError:129 if sys.platform == 'win32':
124 # If distutuils is not available, we just won't add that path130 # We don't have (yet) a good answer for windows since that is certainly
125 pass131 # related to the way we build the installers. -- vila20090821
126 else:132 return None
127 archless_path = osutils.pathjoin(get_python_lib(), 'bzrlib',133 site_path = None
128 'plugins')134 try:
129 if archless_path not in path:135 from distutils.sysconfig import get_python_lib
130 path.append(archless_path)136 except ImportError:
131 return path137 # If distutuils is not available, we just don't know where they are
138 pass
139 else:
140 site_path = osutils.pathjoin(get_python_lib(), 'bzrlib', 'plugins')
141 return site_path
142
143
144def get_user_plugin_path():
145 return osutils.pathjoin(config.config_dir(), 'plugins')
146
147
148def get_standard_plugins_path():
149 """Determine a plugin path suitable for general use."""
150 # Ad-Hoc default: core is not overriden by site but user can overrides both
151 # The rationale is that:
152 # - 'site' comes last, because these plugins should always be available and
153 # are supposed to be in sync with the bzr installed on site.
154 # - 'core' comes before 'site' so that running bzr from sources or a user
155 # installed version overrides the site version.
156 # - 'user' comes first, because... user is always right.
157 # - the above rules clearly defines which plugin version will be loaded if
158 # several exist. Yet, it is sometimes desirable to disable some directory
159 # so that a set of plugin is disabled as once. This can be done via
160 # -site, -core, -user.
161
162 env_paths = os.environ.get('BZR_PLUGIN_PATH', '+user').split(os.pathsep)
163 defaults = ['+core', '+site']
164
165 # The predefined references
166 refs = dict(core=get_core_plugin_path(),
167 site=get_site_plugin_path(),
168 user=get_user_plugin_path())
169
170 # Unset paths that should be removed
171 for k,v in refs.iteritems():
172 removed = '-%s' % k
173 # defaults can never mention removing paths as that will make it
174 # impossible for the user to revoke these removals.
175 if removed in env_paths:
176 env_paths.remove(removed)
177 refs[k] = None
178
179 # Expand references
180 paths = []
181 for p in env_paths + defaults:
182 if p.startswith('+'):
183 # Resolve reference if they are known
184 try:
185 p = refs[p[1:]]
186 except KeyError:
187 # Leave them untouched otherwise, user may have paths starting
188 # with '+'...
189 pass
190 _append_new_path(paths, p)
191
192 # Get rid of trailing slashes, since Python can't handle them when
193 # it tries to import modules.
194 paths = map(_strip_trailing_sep, paths)
195 return paths
132196
133197
134def load_plugins(path=None):198def load_plugins(path=None):
135199
=== modified file 'bzrlib/tests/test_plugins.py'
--- bzrlib/tests/test_plugins.py 2009-06-10 03:56:49 +0000
+++ bzrlib/tests/test_plugins.py 2009-08-31 04:35:31 +0000
@@ -26,7 +26,11 @@
26import sys26import sys
27import zipfile27import zipfile
2828
29from bzrlib import plugin, tests29from bzrlib import (
30 osutils,
31 plugin,
32 tests,
33 )
30import bzrlib.plugin34import bzrlib.plugin
31import bzrlib.plugins35import bzrlib.plugins
32import bzrlib.commands36import bzrlib.commands
@@ -454,41 +458,6 @@
454 delattr(bzrlib.plugins, 'myplug')458 delattr(bzrlib.plugins, 'myplug')
455459
456460
457class TestSetPluginsPath(TestCase):
458
459 def test_set_plugins_path(self):
460 """set_plugins_path should set the module __path__ correctly."""
461 old_path = bzrlib.plugins.__path__
462 try:
463 bzrlib.plugins.__path__ = []
464 expected_path = bzrlib.plugin.set_plugins_path()
465 self.assertEqual(expected_path, bzrlib.plugins.__path__)
466 finally:
467 bzrlib.plugins.__path__ = old_path
468
469 def test_set_plugins_path_with_trailing_slashes(self):
470 """set_plugins_path should set the module __path__ based on
471 BZR_PLUGIN_PATH after removing all trailing slashes."""
472 old_path = bzrlib.plugins.__path__
473 old_env = os.environ.get('BZR_PLUGIN_PATH')
474 try:
475 bzrlib.plugins.__path__ = []
476 os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
477 "second/\\/\\/"
478 bzrlib.plugin.set_plugins_path()
479 # We expect our nominated paths to have all path-seps removed,
480 # and this is testing only that.
481 expected_path = ['first', 'second']
482 self.assertEqual(expected_path,
483 bzrlib.plugins.__path__[:len(expected_path)])
484 finally:
485 bzrlib.plugins.__path__ = old_path
486 if old_env is not None:
487 os.environ['BZR_PLUGIN_PATH'] = old_env
488 else:
489 del os.environ['BZR_PLUGIN_PATH']
490
491
492class TestHelpIndex(tests.TestCase):461class TestHelpIndex(tests.TestCase):
493 """Tests for the PluginsHelpIndex class."""462 """Tests for the PluginsHelpIndex class."""
494463
@@ -597,41 +566,42 @@
597 self.assertEqual('foo_bar', topic.get_help_topic())566 self.assertEqual('foo_bar', topic.get_help_topic())
598567
599568
600def clear_plugins(test_case):569class TestLoadFromPath(tests.TestCaseInTempDir):
601 # Save the attributes that we're about to monkey-patch.570
602 old_plugins_path = bzrlib.plugins.__path__571 def setUp(self):
603 old_loaded = plugin._loaded572 super(TestLoadFromPath, self).setUp()
604 old_load_from_path = plugin.load_from_path573 # Save the attributes that we're about to monkey-patch.
605 # Change bzrlib.plugin to think no plugins have been loaded yet.574 old_plugins_path = bzrlib.plugins.__path__
606 bzrlib.plugins.__path__ = []575 old_loaded = plugin._loaded
607 plugin._loaded = False576 old_load_from_path = plugin.load_from_path
608 # Monkey-patch load_from_path to stop it from actually loading anything.577
609 def load_from_path(dirs):578 def restore():
610 pass579 bzrlib.plugins.__path__ = old_plugins_path
611 plugin.load_from_path = load_from_path580 plugin._loaded = old_loaded
612 def restore_plugins():581 plugin.load_from_path = old_load_from_path
613 bzrlib.plugins.__path__ = old_plugins_path582
614 plugin._loaded = old_loaded583 self.addCleanup(restore)
615 plugin.load_from_path = old_load_from_path584
616 test_case.addCleanup(restore_plugins)585 # Change bzrlib.plugin to think no plugins have been loaded yet.
617586 bzrlib.plugins.__path__ = []
618587 plugin._loaded = False
619class TestPluginPaths(tests.TestCase):588
589 # Monkey-patch load_from_path to stop it from actually loading anything.
590 def load_from_path(dirs):
591 pass
592 plugin.load_from_path = load_from_path
620593
621 def test_set_plugins_path_with_args(self):594 def test_set_plugins_path_with_args(self):
622 clear_plugins(self)
623 plugin.set_plugins_path(['a', 'b'])595 plugin.set_plugins_path(['a', 'b'])
624 self.assertEqual(['a', 'b'], bzrlib.plugins.__path__)596 self.assertEqual(['a', 'b'], bzrlib.plugins.__path__)
625597
626 def test_set_plugins_path_defaults(self):598 def test_set_plugins_path_defaults(self):
627 clear_plugins(self)
628 plugin.set_plugins_path()599 plugin.set_plugins_path()
629 self.assertEqual(plugin.get_standard_plugins_path(),600 self.assertEqual(plugin.get_standard_plugins_path(),
630 bzrlib.plugins.__path__)601 bzrlib.plugins.__path__)
631602
632 def test_get_standard_plugins_path(self):603 def test_get_standard_plugins_path(self):
633 path = plugin.get_standard_plugins_path()604 path = plugin.get_standard_plugins_path()
634 self.assertEqual(plugin.get_default_plugin_path(), path[0])
635 for directory in path:605 for directory in path:
636 self.assertNotContainsRe(directory, r'\\/$')606 self.assertNotContainsRe(directory, r'\\/$')
637 try:607 try:
@@ -649,13 +619,11 @@
649619
650 def test_get_standard_plugins_path_env(self):620 def test_get_standard_plugins_path_env(self):
651 os.environ['BZR_PLUGIN_PATH'] = 'foo/'621 os.environ['BZR_PLUGIN_PATH'] = 'foo/'
652 self.assertEqual('foo', plugin.get_standard_plugins_path()[0])622 path = plugin.get_standard_plugins_path()
653623 for directory in path:
654624 self.assertNotContainsRe(directory, r'\\/$')
655class TestLoadPlugins(tests.TestCaseInTempDir):
656625
657 def test_load_plugins(self):626 def test_load_plugins(self):
658 clear_plugins(self)
659 plugin.load_plugins(['.'])627 plugin.load_plugins(['.'])
660 self.assertEqual(bzrlib.plugins.__path__, ['.'])628 self.assertEqual(bzrlib.plugins.__path__, ['.'])
661 # subsequent loads are no-ops629 # subsequent loads are no-ops
@@ -663,7 +631,107 @@
663 self.assertEqual(bzrlib.plugins.__path__, ['.'])631 self.assertEqual(bzrlib.plugins.__path__, ['.'])
664632
665 def test_load_plugins_default(self):633 def test_load_plugins_default(self):
666 clear_plugins(self)
667 plugin.load_plugins()634 plugin.load_plugins()
668 path = plugin.get_standard_plugins_path()635 path = plugin.get_standard_plugins_path()
669 self.assertEqual(path, bzrlib.plugins.__path__)636 self.assertEqual(path, bzrlib.plugins.__path__)
637
638
639class TestEnvPluginPath(tests.TestCaseInTempDir):
640
641 def setUp(self):
642 super(TestEnvPluginPath, self).setUp()
643 old_default = plugin.DEFAULT_PLUGIN_PATH
644
645 def restore():
646 plugin.DEFAULT_PLUGIN_PATH = old_default
647
648 self.addCleanup(restore)
649
650 plugin.DEFAULT_PLUGIN_PATH = None
651
652 self.user = plugin.get_user_plugin_path()
653 self.site = plugin.get_site_plugin_path()
654 self.core = plugin.get_core_plugin_path()
655
656 def _list2paths(self, *args):
657 paths = []
658 for p in args:
659 plugin._append_new_path(paths, p)
660 return paths
661
662 def _set_path(self, *args):
663 path = os.pathsep.join(self._list2paths(*args))
664 osutils.set_or_unset_env('BZR_PLUGIN_PATH', path)
665
666 def check_path(self, expected_dirs, setting_dirs):
667 if setting_dirs:
668 self._set_path(*setting_dirs)
669 actual = plugin.get_standard_plugins_path()
670 self.assertEquals(self._list2paths(*expected_dirs), actual)
671
672 def test_default(self):
673 self.check_path([self.user, self.core, self.site],
674 None)
675
676 def test_adhoc_policy(self):
677 self.check_path([self.user, self.core, self.site],
678 ['+user', '+core', '+site'])
679
680 def test_fallback_policy(self):
681 self.check_path([self.core, self.site, self.user],
682 ['+core', '+site', '+user'])
683
684 def test_override_policy(self):
685 self.check_path([self.user, self.site, self.core],
686 ['+user', '+site', '+core'])
687
688 def test_disable_user(self):
689 self.check_path([self.core, self.site], ['-user'])
690
691 def test_disable_user_twice(self):
692 # Ensures multiple removals don't left cruft
693 self.check_path([self.core, self.site], ['-user', '-user'])
694
695 def test_duplicates_are_removed(self):
696 self.check_path([self.user, self.core, self.site],
697 ['+user', '+user'])
698 # And only the first reference is kept (since the later references will
699 # onnly produce <plugin> already loaded mutters)
700 self.check_path([self.user, self.core, self.site],
701 ['+user', '+user', '+core',
702 '+user', '+site', '+site',
703 '+core'])
704
705 def test_disable_overrides_disable(self):
706 self.check_path([self.core, self.site], ['-user', '+user'])
707
708 def test_disable_core(self):
709 self.check_path([self.site], ['-core'])
710 self.check_path([self.user, self.site], ['+user', '-core'])
711
712 def test_disable_site(self):
713 self.check_path([self.core], ['-site'])
714 self.check_path([self.user, self.core], ['-site', '+user'])
715
716 def test_override_site(self):
717 self.check_path(['mysite', self.user, self.core],
718 ['mysite', '-site', '+user'])
719 self.check_path(['mysite', self.core],
720 ['mysite', '-site'])
721
722 def test_override_core(self):
723 self.check_path(['mycore', self.user, self.site],
724 ['mycore', '-core', '+user', '+site'])
725 self.check_path(['mycore', self.site],
726 ['mycore', '-core'])
727
728 def test_my_plugin_only(self):
729 self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
730
731 def test_my_plugin_first(self):
732 self.check_path(['myplugin', self.core, self.site, self.user],
733 ['myplugin', '+core', '+site', '+user'])
734
735 def test_bogus_references(self):
736 self.check_path(['+foo', '-bar', self.core, self.site],
737 ['+foo', '-bar'])
670738
=== modified file 'doc/en/user-guide/plugins.txt'
--- doc/en/user-guide/plugins.txt 2007-12-14 07:35:49 +0000
+++ doc/en/user-guide/plugins.txt 2009-08-31 04:35:31 +0000
@@ -56,23 +56,11 @@
56Alternative plugin locations56Alternative plugin locations
57----------------------------57----------------------------
5858
59If you have the necessary permissions, plugins can also be installed on59If you have the necessary permissions, plugins can also be installed on a
60a system-wide basis. Two locations are currently checked for plugins:60system-wide basis. One can additionally override the personal plugins
6161location by setting the environment variable ``BZR_PLUGIN_PATH`` (see `User
62 1. the system location - bzrlib/plugins62Reference <../user-reference/bzr_man.html#bzr-plugin-path>`_ for a detailed
63 2. the personal location - $BZR_HOME/plugins.63explanation).
64
65On a Linux installation, these locations are typically
66``/usr/lib/python2.4/site-packages/bzrlib/plugins/`` and
67``$HOME/.bazaar/plugins/``.
68On a Windows installation, the system location might be
69``C:\\Program Files\\Bazaar\\plugins``
70while the personal location might be
71``C:\Documents and Settings\<username>\Application Data\Bazaar\2.0\plugins``.
72
73One can additionally override the personal plugins location
74by setting the environment variable ``BZR_PLUGIN_PATH``
75to a directory that contains plugins.
7664
77Listing the installed plugins65Listing the installed plugins
78-----------------------------66-----------------------------
7967
=== modified file 'doc/en/user-guide/writing_a_plugin.txt'
--- doc/en/user-guide/writing_a_plugin.txt 2008-10-15 19:34:48 +0000
+++ doc/en/user-guide/writing_a_plugin.txt 2009-08-31 04:35:31 +0000
@@ -32,11 +32,14 @@
32Plugin searching rules32Plugin searching rules
33----------------------33----------------------
3434
35Bzr will scan ``bzrlib/plugins`` and ``~/.bazaar/plugins`` for plugins35Bzr will scan ``~/.bazaar/plugins`` and ``bzrlib/plugins`` for plugins
36by default. You can override this with ``BZR_PLUGIN_PATH``. Plugins36by default. You can override this with ``BZR_PLUGIN_PATH``
37may be either modules or packages. If your plugin is a single file,37(see `User Reference <../user-reference/bzr_man.html#bzr-plugin-path>`_
38you can structure it as a module. If it has multiple files, or if you38for details).
39want to distribute it as a bzr branch, you should structure it as a39
40Plugins may be either modules or packages. If your plugin is a single
41file, you can structure it as a module. If it has multiple files, or if
42you want to distribute it as a bzr branch, you should structure it as a
40package, i.e. a directory with an ``__init__.py`` file.43package, i.e. a directory with an ``__init__.py`` file.
4144
42More information45More information