Rev 4673: BZR_PLUGIN_PATH can be used to fully control the plugin directories in http://bazaar.launchpad.net/~vila/bzr/integration

Vincent Ladeuil v.ladeuil+lp at free.fr
Fri Sep 4 16:37:07 BST 2009


At http://bazaar.launchpad.net/~vila/bzr/integration

------------------------------------------------------------
revno: 4673 [merge]
revision-id: v.ladeuil+lp at free.fr-20090904153648-f4ajhttkhs92mgjz
parent: pqm at pqm.ubuntu.com-20090904024310-7a3uqlf6iruxvz6m
parent: v.ladeuil+lp at free.fr-20090821091911-hatou4w3xz99prlp
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: integration
timestamp: Fri 2009-09-04 17:36:48 +0200
message:
  BZR_PLUGIN_PATH can be used to fully control the plugin directories
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/help_topics/en/configuration.txt configuration.txt-20060314161707-868350809502af01
  bzrlib/plugin.py               plugin.py-20050622060424-829b654519533d69
  bzrlib/tests/test_plugins.py   plugins.py-20050622075746-32002b55e5e943e9
  doc/en/user-guide/plugins.txt  plugins.txt-20060314145616-525099a747f3ffdd
  doc/en/user-guide/writing_a_plugin.txt writing_a_plugin.txt-20071114035000-q36a9h57ps06uvnl-7
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS	2009-09-04 00:49:55 +0000
+++ b/NEWS	2009-09-04 15:36:48 +0000
@@ -280,6 +280,11 @@
 New Features
 ************
 
+* Give more control on BZR_PLUGIN_PATH by providing a way to refer to or
+  disable the user, site and core plugin directories.
+  (Vincent Ladeuil, #412930, #316192, #145612)
+
+
 Bug Fixes
 *********
 

=== modified file 'bzrlib/help_topics/en/configuration.txt'
--- a/bzrlib/help_topics/en/configuration.txt	2009-06-26 18:13:41 +0000
+++ b/bzrlib/help_topics/en/configuration.txt	2009-08-21 09:19:11 +0000
@@ -63,6 +63,60 @@
 ~~~~~~~~~~~~~~~
 
 The path to the plugins directory that Bazaar should use.
+If not set, Bazaar will search for plugins in:
+
+* the user specific plugin directory (containing the ``user`` plugins),
+
+* the bzrlib directory (containing the ``core`` plugins),
+
+* the site specific plugin directory if applicable (containing
+  the ``site`` plugins).
+
+If ``BZR_PLUGIN_PATH`` is set in any fashion, it will change the
+the way the plugin are searched. 
+
+As for the ``PATH`` variables, if multiple directories are
+specified in ``BZR_PLUGIN_PATH`` they should be separated by the
+platform specific appropriate character (':' on Unix/Linux/etc,
+';' on windows)
+
+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.
+
+If you need to change the order or remove one of these
+directories, you should use special values:
+
+* ``-user``, ``-core``, ``-site`` will remove the corresponding
+  path from the default values,
+
+* ``+user``, ``+core``, ``+site`` will add the corresponding path
+  before the remaining default values (and also remove it from
+  the default values).
+
+Note that the special values 'user', 'core' and 'site' should be
+used literally, they will be substituted by the corresponding,
+platform specific, values.
+
+Examples:
+^^^^^^^^^
+
+The examples below uses ':' as the separator, windows users
+should use ';'.
+
+Overriding the default user plugin directory:
+``BZR_PLUGIN_PATH='/path/to/my/other/plugins'``
+
+Disabling the site directory while retaining the user directory:
+``BZR_PLUGIN_PATH='-site:+user'``
+
+Disabling all plugins (better achieved with --no-plugins):
+``BZR_PLUGIN_PATH='-user:-core:-site'``
+
+Overriding the default site plugin directory:
+``BZR_PLUGIN_PATH='/path/to/my/site/plugins:-site':+user``
+
+
 
 BZRPATH
 ~~~~~~~

=== modified file 'bzrlib/plugin.py'
--- a/bzrlib/plugin.py	2009-03-24 01:53:42 +0000
+++ b/bzrlib/plugin.py	2009-09-04 15:36:48 +0000
@@ -52,12 +52,16 @@
 from bzrlib import plugins as _mod_plugins
 """)
 
-from bzrlib.symbol_versioning import deprecated_function
+from bzrlib.symbol_versioning import (
+    deprecated_function,
+    deprecated_in,
+    )
 
 
 DEFAULT_PLUGIN_PATH = None
 _loaded = False
 
+ at deprecated_function(deprecated_in((2, 0, 0)))
 def get_default_plugin_path():
     """Get the DEFAULT_PLUGIN_PATH"""
     global DEFAULT_PLUGIN_PATH
@@ -91,13 +95,15 @@
     return path
 
 
-def get_standard_plugins_path():
-    """Determine a plugin path suitable for general use."""
-    path = os.environ.get('BZR_PLUGIN_PATH',
-                          get_default_plugin_path()).split(os.pathsep)
-    # Get rid of trailing slashes, since Python can't handle them when
-    # it tries to import modules.
-    path = map(_strip_trailing_sep, path)
+def _append_new_path(paths, new_path):
+    """Append a new path if it set and not already known."""
+    if new_path is not None and new_path not in paths:
+        paths.append(new_path)
+    return paths
+
+
+def get_core_plugin_path():
+    core_path = None
     bzr_exe = bool(getattr(sys, 'frozen', None))
     if bzr_exe:    # expand path for bzr.exe
         # We need to use relative path to system-wide plugin
@@ -110,25 +116,83 @@
         # then plugins directory is
         # C:\Program Files\Bazaar\plugins
         # so relative path is ../../../plugins
-        path.append(osutils.abspath(osutils.pathjoin(
-            osutils.dirname(__file__), '../../../plugins')))
-    if not bzr_exe:     # don't look inside library.zip
+        core_path = osutils.abspath(osutils.pathjoin(
+                osutils.dirname(__file__), '../../../plugins'))
+    else:     # don't look inside library.zip
         # search the plugin path before the bzrlib installed dir
-        path.append(os.path.dirname(_mod_plugins.__file__))
-    # search the arch independent path if we can determine that and
-    # the plugin is found nowhere else
-    if sys.platform != 'win32':
-        try:
-            from distutils.sysconfig import get_python_lib
-        except ImportError:
-            # If distutuils is not available, we just won't add that path
-            pass
-        else:
-            archless_path = osutils.pathjoin(get_python_lib(), 'bzrlib',
-                    'plugins')
-            if archless_path not in path:
-                path.append(archless_path)
-    return path
+        core_path = os.path.dirname(_mod_plugins.__file__)
+    return core_path
+
+
+def get_site_plugin_path():
+    """Returns the path for the site installed plugins."""
+    if sys.platform == 'win32':
+        # We don't have (yet) a good answer for windows since that is certainly
+        # related to the way we build the installers. -- vila20090821
+        return None
+    site_path = None
+    try:
+        from distutils.sysconfig import get_python_lib
+    except ImportError:
+        # If distutuils is not available, we just don't know where they are
+        pass
+    else:
+        site_path = osutils.pathjoin(get_python_lib(), 'bzrlib', 'plugins')
+    return site_path
+
+
+def get_user_plugin_path():
+    return osutils.pathjoin(config.config_dir(), 'plugins')
+
+
+def get_standard_plugins_path():
+    """Determine a plugin path suitable for general use."""
+    # Ad-Hoc default: core is not overriden by site but user can overrides both
+    # The rationale is that:
+    # - 'site' comes last, because these plugins should always be available and
+    #   are supposed to be in sync with the bzr installed on site.
+    # - 'core' comes before 'site' so that running bzr from sources or a user
+    #   installed version overrides the site version.
+    # - 'user' comes first, because... user is always right.
+    # - the above rules clearly defines which plugin version will be loaded if
+    #   several exist. Yet, it is sometimes desirable to disable some directory
+    #   so that a set of plugins is disabled as once. This can be done via
+    #   -site, -core, -user.
+
+    env_paths = os.environ.get('BZR_PLUGIN_PATH', '+user').split(os.pathsep)
+    defaults = ['+core', '+site']
+
+    # The predefined references
+    refs = dict(core=get_core_plugin_path(),
+                site=get_site_plugin_path(),
+                user=get_user_plugin_path())
+
+    # Unset paths that should be removed
+    for k,v in refs.iteritems():
+        removed = '-%s' % k
+        # defaults can never mention removing paths as that will make it
+        # impossible for the user to revoke these removals.
+        if removed in env_paths:
+            env_paths.remove(removed)
+            refs[k] = None
+
+    # Expand references
+    paths = []
+    for p in env_paths + defaults:
+        if p.startswith('+'):
+            # Resolve reference if they are known
+            try:
+                p = refs[p[1:]]
+            except KeyError:
+                # Leave them untouched otherwise, user may have paths starting
+                # with '+'...
+                pass
+        _append_new_path(paths, p)
+
+    # Get rid of trailing slashes, since Python can't handle them when
+    # it tries to import modules.
+    paths = map(_strip_trailing_sep, paths)
+    return paths
 
 
 def load_plugins(path=None):

=== modified file 'bzrlib/tests/test_plugins.py'
--- a/bzrlib/tests/test_plugins.py	2009-06-10 03:56:49 +0000
+++ b/bzrlib/tests/test_plugins.py	2009-08-21 09:19:11 +0000
@@ -26,7 +26,11 @@
 import sys
 import zipfile
 
-from bzrlib import plugin, tests
+from bzrlib import (
+    osutils,
+    plugin,
+    tests,
+    )
 import bzrlib.plugin
 import bzrlib.plugins
 import bzrlib.commands
@@ -454,41 +458,6 @@
                 delattr(bzrlib.plugins, 'myplug')
 
 
-class TestSetPluginsPath(TestCase):
-
-    def test_set_plugins_path(self):
-        """set_plugins_path should set the module __path__ correctly."""
-        old_path = bzrlib.plugins.__path__
-        try:
-            bzrlib.plugins.__path__ = []
-            expected_path = bzrlib.plugin.set_plugins_path()
-            self.assertEqual(expected_path, bzrlib.plugins.__path__)
-        finally:
-            bzrlib.plugins.__path__ = old_path
-
-    def test_set_plugins_path_with_trailing_slashes(self):
-        """set_plugins_path should set the module __path__ based on
-        BZR_PLUGIN_PATH after removing all trailing slashes."""
-        old_path = bzrlib.plugins.__path__
-        old_env = os.environ.get('BZR_PLUGIN_PATH')
-        try:
-            bzrlib.plugins.__path__ = []
-            os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
-                "second/\\/\\/"
-            bzrlib.plugin.set_plugins_path()
-            # We expect our nominated paths to have all path-seps removed,
-            # and this is testing only that.
-            expected_path = ['first', 'second']
-            self.assertEqual(expected_path,
-                bzrlib.plugins.__path__[:len(expected_path)])
-        finally:
-            bzrlib.plugins.__path__ = old_path
-            if old_env is not None:
-                os.environ['BZR_PLUGIN_PATH'] = old_env
-            else:
-                del os.environ['BZR_PLUGIN_PATH']
-
-
 class TestHelpIndex(tests.TestCase):
     """Tests for the PluginsHelpIndex class."""
 
@@ -597,41 +566,42 @@
         self.assertEqual('foo_bar', topic.get_help_topic())
 
 
-def clear_plugins(test_case):
-    # Save the attributes that we're about to monkey-patch.
-    old_plugins_path = bzrlib.plugins.__path__
-    old_loaded = plugin._loaded
-    old_load_from_path = plugin.load_from_path
-    # Change bzrlib.plugin to think no plugins have been loaded yet.
-    bzrlib.plugins.__path__ = []
-    plugin._loaded = False
-    # Monkey-patch load_from_path to stop it from actually loading anything.
-    def load_from_path(dirs):
-        pass
-    plugin.load_from_path = load_from_path
-    def restore_plugins():
-        bzrlib.plugins.__path__ = old_plugins_path
-        plugin._loaded = old_loaded
-        plugin.load_from_path = old_load_from_path
-    test_case.addCleanup(restore_plugins)
-
-
-class TestPluginPaths(tests.TestCase):
+class TestLoadFromPath(tests.TestCaseInTempDir):
+
+    def setUp(self):
+        super(TestLoadFromPath, self).setUp()
+        # Save the attributes that we're about to monkey-patch.
+        old_plugins_path = bzrlib.plugins.__path__
+        old_loaded = plugin._loaded
+        old_load_from_path = plugin.load_from_path
+
+        def restore():
+            bzrlib.plugins.__path__ = old_plugins_path
+            plugin._loaded = old_loaded
+            plugin.load_from_path = old_load_from_path
+
+        self.addCleanup(restore)
+
+        # Change bzrlib.plugin to think no plugins have been loaded yet.
+        bzrlib.plugins.__path__ = []
+        plugin._loaded = False
+
+        # Monkey-patch load_from_path to stop it from actually loading anything.
+        def load_from_path(dirs):
+            pass
+        plugin.load_from_path = load_from_path
 
     def test_set_plugins_path_with_args(self):
-        clear_plugins(self)
         plugin.set_plugins_path(['a', 'b'])
         self.assertEqual(['a', 'b'], bzrlib.plugins.__path__)
 
     def test_set_plugins_path_defaults(self):
-        clear_plugins(self)
         plugin.set_plugins_path()
         self.assertEqual(plugin.get_standard_plugins_path(),
                          bzrlib.plugins.__path__)
 
     def test_get_standard_plugins_path(self):
         path = plugin.get_standard_plugins_path()
-        self.assertEqual(plugin.get_default_plugin_path(), path[0])
         for directory in path:
             self.assertNotContainsRe(directory, r'\\/$')
         try:
@@ -649,13 +619,11 @@
 
     def test_get_standard_plugins_path_env(self):
         os.environ['BZR_PLUGIN_PATH'] = 'foo/'
-        self.assertEqual('foo', plugin.get_standard_plugins_path()[0])
-
-
-class TestLoadPlugins(tests.TestCaseInTempDir):
+        path = plugin.get_standard_plugins_path()
+        for directory in path:
+            self.assertNotContainsRe(directory, r'\\/$')
 
     def test_load_plugins(self):
-        clear_plugins(self)
         plugin.load_plugins(['.'])
         self.assertEqual(bzrlib.plugins.__path__, ['.'])
         # subsequent loads are no-ops
@@ -663,7 +631,107 @@
         self.assertEqual(bzrlib.plugins.__path__, ['.'])
 
     def test_load_plugins_default(self):
-        clear_plugins(self)
         plugin.load_plugins()
         path = plugin.get_standard_plugins_path()
         self.assertEqual(path, bzrlib.plugins.__path__)
+
+
+class TestEnvPluginPath(tests.TestCaseInTempDir):
+
+    def setUp(self):
+        super(TestEnvPluginPath, self).setUp()
+        old_default = plugin.DEFAULT_PLUGIN_PATH
+
+        def restore():
+            plugin.DEFAULT_PLUGIN_PATH = old_default
+
+        self.addCleanup(restore)
+
+        plugin.DEFAULT_PLUGIN_PATH = None
+
+        self.user = plugin.get_user_plugin_path()
+        self.site = plugin.get_site_plugin_path()
+        self.core = plugin.get_core_plugin_path()
+
+    def _list2paths(self, *args):
+        paths = []
+        for p in args:
+            plugin._append_new_path(paths, p)
+        return paths
+
+    def _set_path(self, *args):
+        path = os.pathsep.join(self._list2paths(*args))
+        osutils.set_or_unset_env('BZR_PLUGIN_PATH', path)
+
+    def check_path(self, expected_dirs, setting_dirs):
+        if setting_dirs:
+            self._set_path(*setting_dirs)
+        actual = plugin.get_standard_plugins_path()
+        self.assertEquals(self._list2paths(*expected_dirs), actual)
+
+    def test_default(self):
+        self.check_path([self.user, self.core, self.site],
+                        None)
+
+    def test_adhoc_policy(self):
+        self.check_path([self.user, self.core, self.site],
+                        ['+user', '+core', '+site'])
+
+    def test_fallback_policy(self):
+        self.check_path([self.core, self.site, self.user],
+                        ['+core', '+site', '+user'])
+
+    def test_override_policy(self):
+        self.check_path([self.user, self.site, self.core],
+                        ['+user', '+site', '+core'])
+
+    def test_disable_user(self):
+        self.check_path([self.core, self.site], ['-user'])
+
+    def test_disable_user_twice(self):
+        # Ensures multiple removals don't left cruft
+        self.check_path([self.core, self.site], ['-user', '-user'])
+
+    def test_duplicates_are_removed(self):
+        self.check_path([self.user, self.core, self.site],
+                        ['+user', '+user'])
+        # And only the first reference is kept (since the later references will
+        # onnly produce <plugin> already loaded mutters)
+        self.check_path([self.user, self.core, self.site],
+                        ['+user', '+user', '+core',
+                         '+user', '+site', '+site',
+                         '+core'])
+
+    def test_disable_overrides_disable(self):
+        self.check_path([self.core, self.site], ['-user', '+user'])
+
+    def test_disable_core(self):
+        self.check_path([self.site], ['-core'])
+        self.check_path([self.user, self.site], ['+user', '-core'])
+
+    def test_disable_site(self):
+        self.check_path([self.core], ['-site'])
+        self.check_path([self.user, self.core], ['-site', '+user'])
+
+    def test_override_site(self):
+        self.check_path(['mysite', self.user, self.core],
+                        ['mysite', '-site', '+user'])
+        self.check_path(['mysite', self.core],
+                        ['mysite', '-site'])
+
+    def test_override_core(self):
+        self.check_path(['mycore', self.user, self.site],
+                        ['mycore', '-core', '+user', '+site'])
+        self.check_path(['mycore', self.site],
+                        ['mycore', '-core'])
+
+    def test_my_plugin_only(self):
+        self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
+
+    def test_my_plugin_first(self):
+        self.check_path(['myplugin', self.core, self.site, self.user],
+                        ['myplugin', '+core', '+site', '+user'])
+
+    def test_bogus_references(self):
+        self.check_path(['+foo', '-bar', self.core, self.site],
+                        ['+foo', '-bar'])

=== modified file 'doc/en/user-guide/plugins.txt'
--- a/doc/en/user-guide/plugins.txt	2007-12-14 07:35:49 +0000
+++ b/doc/en/user-guide/plugins.txt	2009-08-20 13:26:36 +0000
@@ -56,23 +56,11 @@
 Alternative plugin locations
 ----------------------------
 
-If you have the necessary permissions, plugins can also be installed on
-a system-wide basis. Two locations are currently checked for plugins:
-
- 1. the system location - bzrlib/plugins
- 2. the personal location - $BZR_HOME/plugins.
-
-On a Linux installation, these locations are typically
-``/usr/lib/python2.4/site-packages/bzrlib/plugins/`` and
-``$HOME/.bazaar/plugins/``.
-On a Windows installation, the system location might be
-``C:\\Program Files\\Bazaar\\plugins``
-while the personal location might be
-``C:\Documents and Settings\<username>\Application Data\Bazaar\2.0\plugins``.
-
-One can additionally override the personal plugins location
-by setting the environment variable ``BZR_PLUGIN_PATH``
-to a directory that contains plugins.
+If you have the necessary permissions, plugins can also be installed on a
+system-wide basis.  One can additionally override the personal plugins
+location by setting the environment variable ``BZR_PLUGIN_PATH`` (see `User
+Reference <../user-reference/bzr_man.html#bzr-plugin-path>`_ for a detailed
+explanation).
 
 Listing the installed plugins
 -----------------------------

=== modified file 'doc/en/user-guide/writing_a_plugin.txt'
--- a/doc/en/user-guide/writing_a_plugin.txt	2008-10-15 19:34:48 +0000
+++ b/doc/en/user-guide/writing_a_plugin.txt	2009-08-20 13:26:36 +0000
@@ -32,11 +32,14 @@
 Plugin searching rules
 ----------------------
 
-Bzr will scan ``bzrlib/plugins`` and ``~/.bazaar/plugins`` for plugins
-by default.  You can override this with ``BZR_PLUGIN_PATH``.  Plugins
-may be either modules or packages.  If your plugin is a single file,
-you can structure it as a module.  If it has multiple files, or if you
-want to distribute it as a bzr branch, you should structure it as a
+Bzr will scan ``~/.bazaar/plugins``  and ``bzrlib/plugins`` for plugins
+by default.  You can override this with  ``BZR_PLUGIN_PATH``
+(see `User Reference <../user-reference/bzr_man.html#bzr-plugin-path>`_
+for details).
+
+Plugins may be either modules or packages.  If your plugin is a single
+file, you can structure it as a module.  If it has multiple files, or if
+you want to distribute it as a bzr branch, you should structure it as a
 package, i.e. a directory with an ``__init__.py`` file.
 
 More information



More information about the bazaar-commits mailing list