[Checkins] SVN: zc.buildout/branches/gary-betafix/ fix virtualenv interaction by identfying broken virtualenv characteristic and reverting to previous behavior in that case.

Gary Poster gary.poster at canonical.com
Fri Jun 18 19:56:15 EDT 2010


Log message for revision 113627:
  fix virtualenv interaction by identfying broken virtualenv characteristic and reverting to previous behavior in that case.

Changed:
  U   zc.buildout/branches/gary-betafix/bootstrap/bootstrap.py
  U   zc.buildout/branches/gary-betafix/dev.py
  U   zc.buildout/branches/gary-betafix/src/zc/buildout/buildout.py
  U   zc.buildout/branches/gary-betafix/src/zc/buildout/easy_install.py
  U   zc.buildout/branches/gary-betafix/src/zc/buildout/testing.py
  U   zc.buildout/branches/gary-betafix/src/zc/buildout/tests.py
  A   zc.buildout/branches/gary-betafix/src/zc/buildout/virtualenv.txt

-=-
Modified: zc.buildout/branches/gary-betafix/bootstrap/bootstrap.py
===================================================================
--- zc.buildout/branches/gary-betafix/bootstrap/bootstrap.py	2010-06-18 23:56:12 UTC (rev 113626)
+++ zc.buildout/branches/gary-betafix/bootstrap/bootstrap.py	2010-06-18 23:56:15 UTC (rev 113627)
@@ -20,7 +20,7 @@
 $Id$
 """
 
-import os, shutil, sys, tempfile, textwrap, urllib, urllib2
+import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
 from optparse import OptionParser
 
 if sys.platform == 'win32':
@@ -32,11 +32,18 @@
 else:
     quote = str
 
+# Detect https://bugs.launchpad.net/virtualenv/+bug/572545 .
+proc = subprocess.Popen(
+    [sys.executable, '-Sc', 'import ConfigParser'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+proc.communicate()
+has_broken_dash_S = bool(proc.returncode)
+
 # In order to be more robust in the face of system Pythons, we want to
 # run without site-packages loaded.  This is somewhat tricky, in
 # particular because Python 2.6's distutils imports site, so starting
 # with the -S flag is not sufficient.  However, we'll start with that:
-if 'site' in sys.modules:
+if not has_broken_dash_S and 'site' in sys.modules:
     # We will restart with python -S.
     args = sys.argv[:]
     args[0:0] = [sys.executable, '-S']
@@ -167,6 +174,9 @@
        '-mqNxd',
        quote(eggs_dir)]
 
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
 if options.download_base:
     cmd.extend(['-f', quote(options.download_base)])
 

Modified: zc.buildout/branches/gary-betafix/dev.py
===================================================================
--- zc.buildout/branches/gary-betafix/dev.py	2010-06-18 23:56:12 UTC (rev 113626)
+++ zc.buildout/branches/gary-betafix/dev.py	2010-06-18 23:56:15 UTC (rev 113627)
@@ -19,7 +19,7 @@
 $Id$
 """
 
-import os, shutil, sys, subprocess, urllib2
+import os, shutil, sys, subprocess, urllib2, subprocess
 from optparse import OptionParser
 
 if sys.platform == 'win32':
@@ -31,11 +31,15 @@
 else:
     quote = str
 
+# Detect https://bugs.launchpad.net/virtualenv/+bug/572545 .
+has_broken_dash_S = subprocess.call(
+    [sys.executable, '-Sc', 'import ConfigParser'])
+
 # In order to be more robust in the face of system Pythons, we want to
 # run without site-packages loaded.  This is somewhat tricky, in
 # particular because Python 2.6's distutils imports site, so starting
 # with the -S flag is not sufficient.  However, we'll start with that:
-if 'site' in sys.modules:
+if not has_broken_dash_S and 'site' in sys.modules:
     # We will restart with python -S.
     args = sys.argv[:]
     args[0:0] = [sys.executable, '-S']
@@ -117,11 +121,15 @@
 
 env = os.environ.copy() # Windows needs yet-to-be-determined values from this.
 env['PYTHONPATH'] = os.path.dirname(pkg_resources.__file__)
-subprocess.Popen(
-    [sys.executable] +
-    ['setup.py', '-q', 'develop', '-m', '-x', '-d', 'develop-eggs'],
-    env=env).wait()
 
+cmd = [quote(sys.executable),
+       'setup.py', '-q', 'develop', '-m', '-x', '-d', 'develop-eggs']
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+subprocess.Popen(cmd, env=env).wait()
+
 pkg_resources.working_set.add_entry('src')
 
 import zc.buildout.easy_install

Modified: zc.buildout/branches/gary-betafix/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/branches/gary-betafix/src/zc/buildout/buildout.py	2010-06-18 23:56:12 UTC (rev 113626)
+++ zc.buildout/branches/gary-betafix/src/zc/buildout/buildout.py	2010-06-18 23:56:15 UTC (rev 113627)
@@ -34,6 +34,7 @@
 import sys
 import tempfile
 import UserDict
+import warnings
 import zc.buildout
 import zc.buildout.download
 import zc.buildout.easy_install
@@ -51,6 +52,9 @@
 if is_jython:
     import subprocess
 
+_sys_executable_has_broken_dash_S = (
+    zc.buildout.easy_install._has_broken_dash_S(sys.executable))
+
 class MissingOption(zc.buildout.UserError, KeyError):
     """A required option was missing.
     """
@@ -359,7 +363,7 @@
                 distributions, options['executable'],
                 [options['develop-eggs-directory'],
                  options['eggs-directory']],
-                include_site_packages=False,
+                include_site_packages=_sys_executable_has_broken_dash_S,
                 )
         else:
             ws = zc.buildout.easy_install.install(
@@ -370,7 +374,7 @@
                 path=[options['develop-eggs-directory']],
                 newest=self.newest,
                 allow_hosts=self._allow_hosts,
-                include_site_packages=False,
+                include_site_packages=_sys_executable_has_broken_dash_S,
                 )
 
         # Now copy buildout and setuptools eggs, and record destination eggs:
@@ -408,7 +412,8 @@
             relative_paths = ''
         zc.buildout.easy_install.sitepackage_safe_scripts(
             options['bin-directory'], ws, options['executable'], partsdir,
-            reqs=['zc.buildout'], relative_paths=relative_paths)
+            reqs=['zc.buildout'], relative_paths=relative_paths,
+            include_site_packages=_sys_executable_has_broken_dash_S)
 
     init = bootstrap
 
@@ -854,7 +859,7 @@
             index = options.get('index'),
             path = [options['develop-eggs-directory']],
             allow_hosts = self._allow_hosts,
-            include_site_packages=False
+            include_site_packages=_sys_executable_has_broken_dash_S
             )
 
         upgraded = []
@@ -910,7 +915,8 @@
         os.mkdir(partsdir)
         zc.buildout.easy_install.sitepackage_safe_scripts(
             options['bin-directory'], ws, sys.executable, partsdir,
-            reqs=['zc.buildout'])
+            reqs=['zc.buildout'],
+            include_site_packages=_sys_executable_has_broken_dash_S)
 
         # Restart
         args = map(zc.buildout.easy_install._safe_arg, sys.argv)
@@ -951,7 +957,7 @@
                 links = self['buildout'].get('find-links', '').split(),
                 index = self['buildout'].get('index'),
                 newest=self.newest, allow_hosts=self._allow_hosts,
-                include_site_packages=False)
+                include_site_packages=_sys_executable_has_broken_dash_S)
 
             # Clear cache because extensions might now let us read pages we
             # couldn't read before.
@@ -1069,8 +1075,7 @@
                 working_set=pkg_resources.working_set,
                 newest=buildout.newest,
                 allow_hosts=buildout._allow_hosts,
-                include_site_packages=False,
-                )
+                include_site_packages=_sys_executable_has_broken_dash_S)
 
         __doing__ = 'Loading %s recipe entry %s:%s.', group, spec, entry
         return pkg_resources.load_entry_point(
@@ -1577,6 +1582,11 @@
     will be started. This is especially useful for debuging recipe
     problems.
 
+  -s
+
+    Squelch warnings about using an executable with a broken -S
+    implementation.
+
 Assignments are of the form: section:option=value and are used to
 provide configuration options that override those given in the
 configuration file.  For example, to run the buildout in offline mode,
@@ -1642,11 +1652,12 @@
     windows_restart = False
     user_defaults = True
     debug = False
+    ignore_broken_dash_s = False
     while args:
         if args[0][0] == '-':
             op = orig_op = args.pop(0)
             op = op[1:]
-            while op and op[0] in 'vqhWUoOnNDA':
+            while op and op[0] in 'vqhWUoOnNDAs':
                 if op[0] == 'v':
                     verbosity += 10
                 elif op[0] == 'q':
@@ -1665,6 +1676,8 @@
                     options.append(('buildout', 'newest', 'false'))
                 elif op[0] == 'D':
                     debug = True
+                elif op[0] == 's':
+                    ignore_broken_dash_s = True
                 else:
                     _help()
                 op = op[1:]
@@ -1708,6 +1721,17 @@
             # The rest should be commands, so we'll stop here
             break
 
+    if verbosity < 0 or ignore_broken_dash_s:
+        broken_dash_S_filter_action = 'ignore'
+    elif verbosity == 0: # This is the default.
+        broken_dash_S_filter_action = 'once'
+    else:
+        broken_dash_S_filter_action = 'default'
+    warnings.filterwarnings(
+        broken_dash_S_filter_action,
+        re.escape(
+            zc.buildout.easy_install.BROKEN_DASH_S_WARNING),
+        UserWarning)
     if verbosity:
         options.append(('buildout', 'verbosity', str(verbosity)))
 

Modified: zc.buildout/branches/gary-betafix/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/branches/gary-betafix/src/zc/buildout/easy_install.py	2010-06-18 23:56:12 UTC (rev 113626)
+++ zc.buildout/branches/gary-betafix/src/zc/buildout/easy_install.py	2010-06-18 23:56:15 UTC (rev 113627)
@@ -33,6 +33,7 @@
 import subprocess
 import sys
 import tempfile
+import warnings
 import zc.buildout
 import zipimport
 
@@ -54,6 +55,18 @@
 is_distribute = (
     pkg_resources.Requirement.parse('setuptools').key=='distribute')
 
+BROKEN_DASH_S_WARNING = (
+    'Buildout has been asked to exclude or limit site-packages so that '
+    'builds can be repeatable when using a system Python.  However, '
+    'the chosen Python executable has a broken implementation of -S (see '
+    'https://bugs.launchpad.net/virtualenv/+bug/572545 for an example '
+    "problem) and this breaks buildout's ability to isolate site-packages.  "
+    "If the executable already has a clean site-packages (e.g., "
+    "using virtualenv's ``--no-site-packages`` option) you may be getting "
+    'equivalent repeatability.  To silence this warning, use the -s argument '
+    'to the buildout script.  Alternatively, use a Python executable with a '
+    'working -S (such as a standard Python binary).')
+
 if is_jython:
     import java.lang.System
     jython_os_name = (java.lang.System.getProperties()['os.name']).lower()
@@ -70,6 +83,14 @@
 if os.path.normpath(setuptools_loc) != os.path.normpath(buildout_loc):
     buildout_and_setuptools_path.append(buildout_loc)
 
+def _has_broken_dash_S(executable):
+    """Detect https://bugs.launchpad.net/virtualenv/+bug/572545 ."""
+    proc = subprocess.Popen(
+        [executable, '-Sc', 'import ConfigParser'],
+        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    proc.communicate()
+    return bool(proc.returncode)
+
 def _get_system_paths(executable):
     """Return lists of standard lib and site paths for executable.
     """
@@ -209,10 +230,10 @@
     _safe_arg = str
 
 # The following string is used to run easy_install in
-# Installer._call_easy_install.  It is started with python -S (that is,
-# don't import site at start).  That flag, and all of the code in this
-# snippet above the last two lines, exist to work around a relatively rare
-# problem.  If
+# Installer._call_easy_install.  It is usually started with python -S
+# (that is, don't import site at start).  That flag, and all of the code
+# in this snippet above the last two lines, exist to work around a
+# relatively rare problem.  If
 #
 # - your buildout configuration is trying to install a package that is within
 #   a namespace package, and
@@ -264,17 +285,16 @@
 # unnecessary for site.py to preprocess these packages, so it should be
 # fine, as far as can be guessed as of this writing.)  Finally, it
 # imports easy_install and runs it.
-
-_easy_install_cmd = _safe_arg('''\
+_easy_install_preface = '''\
 import sys,os;\
 p = sys.path[:];\
 import site;\
 sys.path[:] = p;\
 [sys.modules.pop(k) for k, v in sys.modules.items()\
  if hasattr(v, '__path__') and len(v.__path__)==1 and\
- not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))];\
-from setuptools.command.easy_install import main;\
-main()''')
+ not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))];'''
+_easy_install_cmd = (
+    'from setuptools.command.easy_install import main;main()')
 
 
 class Installer:
@@ -321,6 +341,7 @@
 
         self._index_url = index
         self._executable = executable
+        self._has_broken_dash_S = _has_broken_dash_S(self._executable)
         if always_unzip is not None:
             self._always_unzip = always_unzip
         path = (path and path[:] or [])
@@ -329,6 +350,17 @@
         if allowed_eggs_from_site_packages is not None:
             self._allowed_eggs_from_site_packages = tuple(
                 allowed_eggs_from_site_packages)
+        if self._has_broken_dash_S:
+            if (not self._include_site_packages or
+                self._allowed_eggs_from_site_packages != ('*',)):
+                # We can't do this if the executable has a broken -S.
+                warnings.warn(BROKEN_DASH_S_WARNING)
+                self._include_site_packages = True
+                self._allowed_eggs_from_site_packages = ('*',)
+            self._easy_install_cmd = _easy_install_preface + _easy_install_cmd
+        else:
+            self._easy_install_cmd = _easy_install_cmd
+        self._easy_install_cmd = _safe_arg(self._easy_install_cmd)
         stdlib, self._site_packages = _get_system_paths(executable)
         version_info = _get_version_info(executable)
         if version_info == sys.version_info:
@@ -487,7 +519,9 @@
         try:
             path = setuptools_loc
 
-            args = ('-Sc', _easy_install_cmd, '-mUNxd', _safe_arg(tmp))
+            args = ('-c', self._easy_install_cmd, '-mUNxd', _safe_arg(tmp))
+            if not self._has_broken_dash_S:
+                args = ('-S',) + args
             if self._always_unzip:
                 args += ('-Z', )
             level = logger.getEffectiveLevel()
@@ -1176,6 +1210,11 @@
             _pyscript(spath, sname, executable, rpsetup))
     return generated
 
+# We need to give an alternate name to the ``scripts`` function so that it
+# can be referenced within sitepackage_safe_scripts, which uses ``scripts``
+# as an argument name.
+_original_scripts_function = scripts
+
 def sitepackage_safe_scripts(
     dest, working_set, executable, site_py_dest,
     reqs=(), scripts=None, interpreter=None, extra_paths=(),
@@ -1188,6 +1227,12 @@
     Python site packages, if desired, and  choosing to execute the Python's
     sitecustomize.
     """
+    if _has_broken_dash_S(executable):
+        if not include_site_packages:
+            warnings.warn(BROKEN_DASH_S_WARNING)
+        return _original_scripts_function(
+            reqs, working_set, executable, dest, scripts, extra_paths,
+            script_arguments, interpreter, initialization, relative_paths)
     generated = []
     generated.append(_generate_sitecustomize(
         site_py_dest, executable, initialization, exec_sitecustomize))

Modified: zc.buildout/branches/gary-betafix/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/branches/gary-betafix/src/zc/buildout/testing.py	2010-06-18 23:56:12 UTC (rev 113626)
+++ zc.buildout/branches/gary-betafix/src/zc/buildout/testing.py	2010-06-18 23:56:15 UTC (rev 113627)
@@ -596,7 +596,7 @@
     sep = re.escape(os.path.sep)
 normalize_path = (
     re.compile(
-        r'''[^'" \t\n\r]+%(sep)s_[Tt][Ee][Ss][Tt]_%(sep)s([^"' \t\n\r]+)'''
+        r'''[^'" \t\n\r!]+%(sep)s_[Tt][Ee][Ss][Tt]_%(sep)s([^"' \t\n\r]+)'''
         % dict(sep=sep)),
     _normalize_path,
     )

Modified: zc.buildout/branches/gary-betafix/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/branches/gary-betafix/src/zc/buildout/tests.py	2010-06-18 23:56:12 UTC (rev 113626)
+++ zc.buildout/branches/gary-betafix/src/zc/buildout/tests.py	2010-06-18 23:56:15 UTC (rev 113627)
@@ -3923,14 +3923,29 @@
             setUp=easy_install_SetUp,
             tearDown=zc.buildout.testing.buildoutTearDown,
             checker=renormalizing.RENormalizing([
-               zc.buildout.testing.normalize_path,
-               zc.buildout.testing.normalize_endings,
-               zc.buildout.testing.normalize_script,
-               normalize_bang,
-               (re.compile('Downloading.*setuptools.*egg\n'), ''),
-               (re.compile('options:'), 'Options:'),
-               (re.compile('usage:'), 'Usage:'),
-               ]),
+                zc.buildout.testing.normalize_path,
+                zc.buildout.testing.normalize_endings,
+                zc.buildout.testing.normalize_script,
+                normalize_bang,
+                (re.compile('Downloading.*setuptools.*egg\n'), ''),
+                (re.compile('options:'), 'Options:'),
+                (re.compile('usage:'), 'Usage:'),
+                ]),
             ))
+        test_suite.append(doctest.DocFileSuite(
+            'virtualenv.txt',
+            setUp=easy_install_SetUp,
+            tearDown=zc.buildout.testing.buildoutTearDown,
+            checker=renormalizing.RENormalizing([
+                zc.buildout.testing.normalize_path,
+                zc.buildout.testing.normalize_endings,
+                zc.buildout.testing.normalize_script,
+                zc.buildout.testing.normalize_egg_py,
+                (re.compile('(setuptools|distribute)-\S+-'),
+                 'setuptools.egg'),
+                (re.compile('zc.buildout-\S+-'),
+                 'zc.buildout.egg'),
+                ]),
+            ))
 
     return unittest.TestSuite(test_suite)

Added: zc.buildout/branches/gary-betafix/src/zc/buildout/virtualenv.txt
===================================================================
--- zc.buildout/branches/gary-betafix/src/zc/buildout/virtualenv.txt	                        (rev 0)
+++ zc.buildout/branches/gary-betafix/src/zc/buildout/virtualenv.txt	2010-06-18 23:56:15 UTC (rev 113627)
@@ -0,0 +1,240 @@
+Version 1.5.0 of buildout (and higher) provides the ability to use
+buildout directly with a system Python if you use z3c.recipe.scripts or
+other isolation-aware recipes that use the sitepackage_safe_scripts function.
+
+Some people use virtualenv to provide similar functionality.
+Unfortunately, a problem with the virtualenv executable as of this
+writing means that -S will not work properly with it (see
+https://bugs.launchpad.net/virtualenv/+bug/572545). This breaks
+buildout's approach to providing isolation.
+
+Because of this, if buildout detects an executable with a broken -S
+option, it will revert to its pre-1.5.0 behavior.  If buildout has been
+asked to provide isolation, it will warn the user that isolation will
+not be provided by buildout, but proceed.  This should give full
+backwards compatibility to virtualenv users.
+
+The only minor annoyance in the future may be recipes that explicitly
+use the new buildout functionality to provide isolation: as described
+above, the builds will proceed, but users will receive warnings that
+buildout is not providing isolation itself.  The warnings themselves can
+be squelched when running bin/buildout with the ``-s`` option or with a
+lower verbosity than usual (e.g., one or more ``-q`` options).
+
+For tests, then, we can examine several things.  We'll focus on four.
+
+- Running bootstrap with an executable broken in this way will not try to do
+  any -S tricks.
+
+- Running sitepackage_safe_scripts with a virtualenv will create an
+  old-style script.  This will affect the bin/buildout script that is
+  created, for instance.  If the sitepackage_safe_scripts function is asked
+  to provide isolation under these circumstances, it will warn that isolation
+  will not be available, but still create the desired script.
+
+- Using the easy_install Installer or install or build functions and trying
+  to request isolation will generate a warning and then the isolation request
+  will be ignored as it proceeds.
+
+- Passing -s (or -q) to the bin/buildout script will squelch warnings.
+
+Testing these involves first creating a Python that exhibits the same
+behavior as the problematic one we care about from virtualenv.  Let's do that
+first.
+
+    >>> import os, sys
+    >>> py_path, site_packages_path = make_py()
+    >>> py_file = open(py_path)
+    >>> py_lines = py_file.readlines()
+    >>> py_file.close()
+    >>> py_file = open(py_path, 'w')
+    >>> extra = '''\
+    ... new_argv = argv[:1]
+    ... for ix, val in enumerate(argv[1:]):
+    ...     if val.startswith('--'):
+    ...         new_argv.append(val)
+    ...     if val.startswith('-') and len(val) > 1:
+    ...         if 'S' in val:
+    ...             val = val.replace('S', '')
+    ...             environ['BROKEN_DASH_S'] = 'Y'
+    ...         if val != '-':
+    ...             new_argv.append(val)
+    ...         if 'c' in val:
+    ...             new_argv.extend(argv[ix+2:])
+    ...             break
+    ...     else:
+    ...         new_argv.extend(argv[ix+1:])
+    ... argv = new_argv
+    ... '''
+    >>> for line in py_lines:
+    ...     py_file.write(line)
+    ...     if line.startswith('environ = os.environ.copy()'):
+    ...         py_file.write(extra)
+    ...         print 'Rewritten.'
+    ...
+    Rewritten.
+    >>> py_file.close()
+    >>> sitecustomize_path = join(os.path.dirname(site_packages_path),
+    ...                           'parts', 'py', 'sitecustomize.py')
+    >>> sitecustomize_file = open(sitecustomize_path, 'a')
+    >>> sitecustomize_file.write('''
+    ... import os, sys
+    ... sys.executable = %r
+    ... if 'BROKEN_DASH_S' in os.environ:
+    ...     class ImportHook:
+    ...         @staticmethod
+    ...         def find_module(fullname, path=None):
+    ...             if fullname == 'ConfigParser':
+    ...                 raise ImportError()
+    ...
+    ...     sys.meta_path.append(ImportHook)
+    ...     sys.modules.pop('site', None) # Keeps site out of sys.modules.
+    ...     # This will be a close-enough approximation of site not being
+    ...     # loaded for our tests--it lets us provoke the right errors when
+    ...     # the fixes are absent, and works well enough when the fixes are
+    ...     # present.
+    ... ''' % (py_path,))
+    >>> sitecustomize_file.close()
+    >>> print call_py(
+    ...     py_path,
+    ...     "import ConfigParser")
+    <BLANKLINE>
+    >>> print 'X'; print call_py(
+    ...     py_path,
+    ...     "import ConfigParser",
+    ...     '-S') # doctest: +ELLIPSIS
+    X...Traceback (most recent call last):
+      ...
+    ImportError: No module named ConfigParser
+    <BLANKLINE>
+    >>> from zc.buildout.easy_install import _has_broken_dash_S
+    >>> _has_broken_dash_S(py_path)
+    True
+
+Well, that was ugly, but it seems to have done the trick.  The
+executable represented by py_path has the same problematic
+characteristic as the virtualenv one: -S results in a Python that does
+not allow the import of some packages from the standard library.  We'll
+test with this.
+
+First, let's try running bootstrap.
+
+    >>> from os.path import dirname, join
+    >>> import zc.buildout
+    >>> bootstrap_py = join(
+    ...    dirname(
+    ...     dirname(
+    ...      dirname(
+    ...       dirname(zc.buildout.__file__)
+    ...        )
+    ...      )
+    ...    ),
+    ...   'bootstrap', 'bootstrap.py')
+    >>> broken_S_buildout = tmpdir('broken_S')
+    >>> os.chdir(broken_S_buildout)
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts =
+    ... ''')
+    >>> write('bootstrap.py', open(bootstrap_py).read())
+    >>> print 'X'; print system(
+    ...     zc.buildout.easy_install._safe_arg(py_path)+' '+
+    ...     'bootstrap.py'); print 'X' # doctest: +ELLIPSIS
+    X...
+    Generated script '/broken_S/bin/buildout'.
+    ...
+
+If bootstrap didn't look out for a broken -S, that would have failed.  Moreover,
+take a look at bin/buildout:
+
+    >>> cat('bin', 'buildout')
+    #!/executable_buildout/bin/py
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/broken_S/eggs/setuptools-0.0-pyN.N.egg',
+      '/broken_S/eggs/zc.buildout-0.0-pyN.N.egg',
+      ]
+    <BLANKLINE>
+    import zc.buildout.buildout
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zc.buildout.buildout.main()
+
+That's the old-style buildout script: no changes for users with this issue.
+
+Of course, they don't get the new features either, presumably because
+they don't need or want them.  This means that if they use a recipe that
+tries to use a new feature, the behavior needs to degrade gracefully.
+
+Here's an example.  We'll switch to another buildout in which it is easier to
+use local dev versions of zc.buildout and z3c.recipe.scripts.
+
+    >>> os.chdir(dirname(dirname(buildout)))
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = eggs
+    ... find-links = %(link_server)s
+    ...
+    ... [primed_python]
+    ... executable = %(py_path)s
+    ...
+    ... [eggs]
+    ... recipe = z3c.recipe.scripts
+    ... python = primed_python
+    ... interpreter = py
+    ... eggs = demo
+    ... ''' % globals())
+
+    >>> print system(buildout) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+    Installing eggs.
+    Getting distribution for 'demo'.
+    Got demo 0.4c1.
+    Getting distribution for 'demoneeded'.
+    Got demoneeded 1.2c1.
+    Generated script '/sample-buildout/bin/demo'.
+    Generated interpreter '/sample-buildout/bin/py'.
+    ...UserWarning: Buildout has been asked to exclude or limit site-packages
+       so that builds can be repeatable when using a system Python.  However,
+       the chosen Python executable has a broken implementation of -S (see
+       https://bugs.launchpad.net/virtualenv/+bug/572545 for an example
+       problem) and this breaks buildout's ability to isolate site-packages.
+       If the executable already has a clean site-packages (e.g., using
+       virtualenv's ``--no-site-packages`` option) you may be getting
+       equivalent repeatability.  To silence this warning, use the -s argument
+       to the buildout script.  Alternatively, use a Python executable with a
+       working -S (such as a standard Python binary).
+      warnings.warn(BROKEN_DASH_S_WARNING)
+    <BLANKLINE>
+
+So, it did what we asked as best it could, but gave a big warning.  If
+you don't want those warnings for those particular recipes that use the
+new features, you can use the "-s" option to squelch the warnings.
+
+    >>> print system(buildout + ' -s')
+    Updating eggs.
+    <BLANKLINE>
+
+A lower verbosity (one or more -q options) also quiets the warning.
+
+    >>> print system(buildout + ' -q')
+    <BLANKLINE>
+
+Notice that, as we saw before with bin/buildout, the generated scripts
+are old-style, because the new-style feature gracefully degrades to the
+previous implementation when it encounters an executable with a broken
+dash-S.
+
+    >>> print 'X'; cat('bin', 'py') # doctest: +ELLIPSIS
+    X...
+    <BLANKLINE>
+    import sys
+    <BLANKLINE>
+    sys.path[0:0] = [
+        '/sample-buildout/eggs/demo-0.4c1-pyN.N.egg',
+        '/sample-buildout/eggs/demoneeded-1.2c1-pyN.N.egg',
+        ]
+    ...
+



More information about the checkins mailing list