[Checkins] SVN: zc.buildout/branches/python-3-2/src/zc/buildout/ Restructure calling external interpreters.

Christian Theune ct at gocept.com
Thu Apr 7 09:43:01 EDT 2011


Log message for revision 121332:
  Restructure calling external interpreters.
  
  * make IO handling consistent on Python 2 and 3
  * simplify business code, extract into separate function
  
  

Changed:
  A   zc.buildout/branches/python-3-2/src/zc/buildout/compat23.py
  U   zc.buildout/branches/python-3-2/src/zc/buildout/easy_install.py
  U   zc.buildout/branches/python-3-2/src/zc/buildout/testing.py

-=-
Added: zc.buildout/branches/python-3-2/src/zc/buildout/compat23.py
===================================================================
--- zc.buildout/branches/python-3-2/src/zc/buildout/compat23.py	                        (rev 0)
+++ zc.buildout/branches/python-3-2/src/zc/buildout/compat23.py	2011-04-07 13:42:59 UTC (rev 121332)
@@ -0,0 +1,65 @@
+#############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Compatibility functions for handling Python 2->3 runtime issues."""
+
+import os
+import subprocess
+import sys
+import tempfile
+
+MUST_CLOSE_FDS = not sys.platform.startswith('win')
+
+
+class Result(object):
+    pass
+
+
+def call_external_python(cmd, suite_source, env=None):
+    """Quote a given code suite for consumption by `python -c '...'`
+    and ensure print output encoding in UTF-8."""
+    if isinstance(cmd, str):
+        cmd = [cmd]
+    code_file_path = tempfile.mktemp('.py')
+    cmd.append(code_file_path)
+    code_file = open(code_file_path, 'w')
+    try:
+        code_file.write(suite_source)
+        code_file.write("""
+import sys
+import os
+v = sys.version_info[0]
+if v == 2:
+    decode = True
+elif v == 3 and isinstance(result, bytes):
+    decode = True
+else:
+    decode = False
+if decode:
+    result = result.decode(sys.getfilesystemencoding())
+os.write(1, result.encode('utf-8'))
+""")
+        code_file.close()
+        process = subprocess.Popen(cmd,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   env=env,
+                                   close_fds=MUST_CLOSE_FDS)
+        out, err = process.communicate()
+    finally:
+        os.unlink(code_file_path)
+    result = Result()
+    result.out = out.decode('utf-8').strip()
+    result.err = err
+    result.returncode = process.returncode
+    return result

Modified: zc.buildout/branches/python-3-2/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/branches/python-3-2/src/zc/buildout/easy_install.py	2011-04-07 13:38:13 UTC (rev 121331)
+++ zc.buildout/branches/python-3-2/src/zc/buildout/easy_install.py	2011-04-07 13:42:59 UTC (rev 121332)
@@ -37,6 +37,7 @@
 import warnings
 import zc.buildout
 import zipimport
+from zc.buildout.compat23 import call_external_python
 
 _oprp = getattr(os.path, 'realpath', lambda path: path)
 def realpath(path):
@@ -1593,33 +1594,32 @@
     - executable is a path to the desired Python executable.
     - name is the name of the (pure, not C) Python module.
     """
-    cmd = [executable, "-Sc",
-           "import imp; "
-           "fp, path, desc = imp.find_module(%r); "
-           "fp.close(); "
-           "print(path)" % (name,)]
     env = os.environ.copy()
     # We need to make sure that PYTHONPATH, which will often be set to
     # include a custom buildout-generated site.py, is not set, or else
     # we will not get an accurate value for the "real" site.py and
     # sitecustomize.py.
     env.pop('PYTHONPATH', None)
-    _proc = subprocess.Popen(
-        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
-    stdout, stderr = _proc.communicate();
-    if _proc.returncode:
+    r = call_external_python([executable, "-S"],
+                         """\
+import imp
+fp, path, desc = imp.find_module(%r)
+fp.close()
+result = path""" % (name,),
+                         env)
+    if r.returncode:
         if not silent:
             logger.info(
-                'Could not find file for module %s:\n%s', name, stderr)
+                'Could not find file for module %s:\n%s', name, r.err)
         return None
     # else: ...
-    res = stdout.strip()
-    if res.endswith('.pyc'.encode()) or res.endswith('.pyo'.encode()):
+    candidate = r.out
+    if candidate.endswith('.pyc') or candidate.endswith('.pyo'):
         raise RuntimeError('Cannot find uncompiled version of %s' % (name,))
-    if not os.path.exists(res):
+    if not os.path.exists(candidate):
         raise RuntimeError(
-            'File does not exist for module %s:\n%s' % (name, res))
-    return res
+            'File does not exist for module %s:\n%s' % (name, candidate))
+    return candidate
 
 def _generate_sitecustomize(dest, executable, initialization='',
                             exec_sitecustomize=False):

Modified: zc.buildout/branches/python-3-2/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/branches/python-3-2/src/zc/buildout/testing.py	2011-04-07 13:38:13 UTC (rev 121331)
+++ zc.buildout/branches/python-3-2/src/zc/buildout/testing.py	2011-04-07 13:42:59 UTC (rev 121332)
@@ -41,6 +41,7 @@
 import zc.buildout.easy_install
 from zc.buildout.easy_install import setuptools_key
 from zc.buildout.rmtree import rmtree
+from zc.buildout.compat23 import call_external_python
 
 fsync = getattr(os, 'fsync', lambda fileno: None)
 is_win32 = sys.platform == 'win32'
@@ -189,66 +190,39 @@
               '--single-version-externally-managed')
 
 def find_python(version):
+    """Return the path to a Python executable for a given version of Python.
+
+    The version is given as a 2-component string: '2.4', '3.2', ...
+
+    """
     env_friendly_version = ''.join(version.split('.'))
 
-    e = os.environ.get('PYTHON%s' % env_friendly_version)
-    if e is not None:
-        return e
+    candidates = []
+    candidates.append(os.environ.get('PYTHON%s' % env_friendly_version))
+
     if is_win32:
-        e = '\Python%s\python.exe' % env_friendly_version
-        if os.path.exists(e):
-            return e
+        candidates.append('\Python%s\python.exe' % env_friendly_version)
     else:
-        if version.startswith('2.'):
-            cmd = ('python%s -c "import sys; print(sys.executable.decode('
-                   'sys.getfilesystemencoding()).encode(\'utf-8\'))"' %
-                   version)
-        else:
-            cmd = ('python%s -c "import sys; print('
-                   'sys.executable.encode(\'utf-8\'))"' % version)
-        p = subprocess.Popen(cmd,
-                             shell=True,
-                             stdin=subprocess.PIPE,
-                             stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT,
-                             close_fds=MUST_CLOSE_FDS)
-        i, o = (p.stdin, p.stdout)
-        i.close()
-        e = o.read().decode('utf-8').strip()
-        o.close()
-        if os.path.exists(e):
-            return e
-        cmd = 'python -c "import sys; print(\'%s.%s\' % sys.version_info[:2])"'
-        p = subprocess.Popen(cmd,
-                             shell=True,
-                             stdin=subprocess.PIPE,
-                             stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT,
-                             close_fds=MUST_CLOSE_FDS)
-        i, o = (p.stdin, p.stdout)
-        i.close()
-        e = o.read().decode('ascii').strip()
-        o.close()
-        if e == version:
-            if version.startswith('2.'):
-                cmd = ('python -c "import sys; print(sys.executable.decode('
-                       'sys.getfilesystemencoding()).encode(\'utf-8\'))"')
-            else:
-                cmd = ('python -c "import sys; print('
-                       'sys.executable.encode(\'utf-8\'))"')
-            p = subprocess.Popen(cmd,
-                                shell=True,
-                                stdin=subprocess.PIPE,
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT,
-                                close_fds=MUST_CLOSE_FDS)
-            i, o = (p.stdin, p.stdout)
-            i.close()
-            e = o.read().decode('utf-8').strip()
-            o.close()
-            if os.path.exists(e):
-                return e
+        GET_EXECUTABLE_PATH = 'import sys; result = sys.executable'
+        # Try Python executable with version encoded in filename
+        r = call_external_python(
+            'python%s' % version, GET_EXECUTABLE_PATH)
+        candidates.append(r.out)
 
+        # Try Python executable without version encoded in filename
+        r = call_external_python(
+            'python', 'import sys; result = "%s.%s\" % sys.version_info[:2]')
+
+        if r.out == version:
+            r = call_external_python('python', GET_EXECUTABLE_PATH)
+            candidates.append(r.out)
+
+    for candidate in candidates:
+        if not candidate:
+            continue
+        if os.path.exists(candidate):
+            return candidate
+
     raise ValueError(
         "Couldn't figure out the executable for Python %(version)s.\n"
         "Set the environment variable PYTHON%(envversion)s to the location\n"



More information about the checkins mailing list