[Python-checkins] cpython: Issue #15184: Ensure consistent results of OS X configuration

ned.deily python-checkins at python.org
Sat Jul 21 14:44:25 CEST 2012


http://hg.python.org/cpython/rev/d76b83803e7e
changeset:   78216:d76b83803e7e
user:        Ned Deily <nad at acm.org>
date:        Sat Jul 21 05:36:30 2012 -0700
summary:
  Issue #15184: Ensure consistent results of OS X configuration
tailoring for universal builds by factoring out common OS X-specific
customizations from sysconfig, distutils.sysconfig, distutils.util,
and distutils.unixccompiler into a new module _osx_support that can
eventually also be used by packaging.

files:
  Lib/_osx_support.py              |  488 +++++++++++++++++++
  Lib/distutils/sysconfig.py       |  190 +------
  Lib/distutils/tests/test_util.py |    9 +
  Lib/distutils/unixccompiler.py   |   70 +--
  Lib/distutils/util.py            |   92 +---
  Lib/sysconfig.py                 |  154 +-----
  Lib/test/test__osx_support.py    |  267 ++++++++++
  Lib/test/test_sysconfig.py       |   10 +-
  Misc/NEWS                        |    5 +
  9 files changed, 818 insertions(+), 467 deletions(-)


diff --git a/Lib/_osx_support.py b/Lib/_osx_support.py
new file mode 100644
--- /dev/null
+++ b/Lib/_osx_support.py
@@ -0,0 +1,488 @@
+"""Shared OS X support functions."""
+
+import os
+import re
+import sys
+
+__all__ = [
+    'compiler_fixup',
+    'customize_config_vars',
+    'customize_compiler',
+    'get_platform_osx',
+]
+
+# configuration variables that may contain universal build flags,
+# like "-arch" or "-isdkroot", that may need customization for
+# the user environment
+_UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS',
+                            'BLDSHARED', 'LDSHARED', 'CC', 'CXX',
+                            'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS',
+                            'PY_CORE_CFLAGS')
+
+# configuration variables that may contain compiler calls
+_COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX')
+
+# prefix added to original configuration variable names
+_INITPRE = '_OSX_SUPPORT_INITIAL_'
+
+
+def _find_executable(executable, path=None):
+    """Tries to find 'executable' in the directories listed in 'path'.
+
+    A string listing directories separated by 'os.pathsep'; defaults to
+    os.environ['PATH'].  Returns the complete filename or None if not found.
+    """
+    if path is None:
+        path = os.environ['PATH']
+
+    paths = path.split(os.pathsep)
+    base, ext = os.path.splitext(executable)
+
+    if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
+        executable = executable + '.exe'
+
+    if not os.path.isfile(executable):
+        for p in paths:
+            f = os.path.join(p, executable)
+            if os.path.isfile(f):
+                # the file exists, we have a shot at spawn working
+                return f
+        return None
+    else:
+        return executable
+
+
+def _read_output(commandstring):
+    """Output from succesful command execution or None"""
+    # Similar to os.popen(commandstring, "r").read(),
+    # but without actually using os.popen because that
+    # function is not usable during python bootstrap.
+    # tempfile is also not available then.
+    import contextlib
+    try:
+        import tempfile
+        fp = tempfile.NamedTemporaryFile()
+    except ImportError:
+        fp = open("/tmp/_osx_support.%s"%(
+            os.getpid(),), "w+b")
+
+    with contextlib.closing(fp) as fp:
+        cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
+        return fp.read().decode('utf-8').strip() if not os.system(cmd) else None
+
+
+def _find_build_tool(toolname):
+    """Find a build tool on current path or using xcrun"""
+    return (_find_executable(toolname)
+                or _read_output("/usr/bin/xcrun -find %s" % (toolname,))
+                or ''
+            )
+
+_SYSTEM_VERSION = None
+
+def _get_system_version():
+    """Return the OS X system version as a string"""
+    # Reading this plist is a documented way to get the system
+    # version (see the documentation for the Gestalt Manager)
+    # We avoid using platform.mac_ver to avoid possible bootstrap issues during
+    # the build of Python itself (distutils is used to build standard library
+    # extensions).
+
+    global _SYSTEM_VERSION
+
+    if _SYSTEM_VERSION is None:
+        _SYSTEM_VERSION = ''
+        try:
+            f = open('/System/Library/CoreServices/SystemVersion.plist')
+        except IOError:
+            # We're on a plain darwin box, fall back to the default
+            # behaviour.
+            pass
+        else:
+            try:
+                m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
+                              r'<string>(.*?)</string>', f.read())
+            finally:
+                f.close()
+            if m is not None:
+                _SYSTEM_VERSION = '.'.join(m.group(1).split('.')[:2])
+            # else: fall back to the default behaviour
+
+    return _SYSTEM_VERSION
+
+def _remove_original_values(_config_vars):
+    """Remove original unmodified values for testing"""
+    # This is needed for higher-level cross-platform tests of get_platform.
+    for k in list(_config_vars):
+        if k.startswith(_INITPRE):
+            del _config_vars[k]
+
+def _save_modified_value(_config_vars, cv, newvalue):
+    """Save modified and original unmodified value of configuration var"""
+
+    oldvalue = _config_vars.get(cv, '')
+    if (oldvalue != newvalue) and (_INITPRE + cv not in _config_vars):
+        _config_vars[_INITPRE + cv] = oldvalue
+    _config_vars[cv] = newvalue
+
+def _supports_universal_builds():
+    """Returns True if universal builds are supported on this system"""
+    # As an approximation, we assume that if we are running on 10.4 or above,
+    # then we are running with an Xcode environment that supports universal
+    # builds, in particular -isysroot and -arch arguments to the compiler. This
+    # is in support of allowing 10.4 universal builds to run on 10.3.x systems.
+
+    osx_version = _get_system_version()
+    if osx_version:
+        try:
+            osx_version = tuple(int(i) for i in osx_version.split('.'))
+        except ValueError:
+            osx_version = ''
+    return bool(osx_version >= (10, 4)) if osx_version else False
+
+
+def _find_appropriate_compiler(_config_vars):
+    """Find appropriate C compiler for extension module builds"""
+
+    # Issue #13590:
+    #    The OSX location for the compiler varies between OSX
+    #    (or rather Xcode) releases.  With older releases (up-to 10.5)
+    #    the compiler is in /usr/bin, with newer releases the compiler
+    #    can only be found inside Xcode.app if the "Command Line Tools"
+    #    are not installed.
+    #
+    #    Futhermore, the compiler that can be used varies between
+    #    Xcode releases. Upto Xcode 4 it was possible to use 'gcc-4.2'
+    #    as the compiler, after that 'clang' should be used because
+    #    gcc-4.2 is either not present, or a copy of 'llvm-gcc' that
+    #    miscompiles Python.
+
+    # skip checks if the compiler was overriden with a CC env variable
+    if 'CC' in os.environ:
+        return _config_vars
+
+    # The CC config var might contain additional arguments.
+    # Ignore them while searching.
+    cc = oldcc = _config_vars['CC'].split()[0]
+    if not _find_executable(cc):
+        # Compiler is not found on the shell search PATH.
+        # Now search for clang, first on PATH (if the Command LIne
+        # Tools have been installed in / or if the user has provided
+        # another location via CC).  If not found, try using xcrun
+        # to find an uninstalled clang (within a selected Xcode).
+
+        # NOTE: Cannot use subprocess here because of bootstrap
+        # issues when building Python itself (and os.popen is
+        # implemented on top of subprocess and is therefore not
+        # usable as well)
+
+        cc = _find_build_tool('clang')
+
+    elif os.path.basename(cc).startswith('gcc'):
+        # Compiler is GCC, check if it is LLVM-GCC
+        data = _read_output("'%s' --version"
+                             % (cc.replace("'", "'\"'\"'"),))
+        if 'llvm-gcc' in data:
+            # Found LLVM-GCC, fall back to clang
+            cc = _find_build_tool('clang')
+
+    if not cc:
+        raise SystemError(
+               "Cannot locate working compiler")
+
+    if cc != oldcc:
+        # Found a replacement compiler.
+        # Modify config vars using new compiler, if not already explictly
+        # overriden by an env variable, preserving additional arguments.
+        for cv in _COMPILER_CONFIG_VARS:
+            if cv in _config_vars and cv not in os.environ:
+                cv_split = _config_vars[cv].split()
+                cv_split[0] = cc if cv != 'CXX' else cc + '++'
+                _save_modified_value(_config_vars, cv, ' '.join(cv_split))
+
+    return _config_vars
+
+
+def _remove_universal_flags(_config_vars):
+    """Remove all universal build arguments from config vars"""
+
+    for cv in _UNIVERSAL_CONFIG_VARS:
+        # Do not alter a config var explicitly overriden by env var
+        if cv in _config_vars and cv not in os.environ:
+            flags = _config_vars[cv]
+            flags = re.sub('-arch\s+\w+\s', ' ', flags, re.ASCII)
+            flags = re.sub('-isysroot [^ \t]*', ' ', flags)
+            _save_modified_value(_config_vars, cv, flags)
+
+    return _config_vars
+
+
+def _remove_unsupported_archs(_config_vars):
+    """Remove any unsupported archs from config vars"""
+    # Different Xcode releases support different sets for '-arch'
+    # flags. In particular, Xcode 4.x no longer supports the
+    # PPC architectures.
+    #
+    # This code automatically removes '-arch ppc' and '-arch ppc64'
+    # when these are not supported. That makes it possible to
+    # build extensions on OSX 10.7 and later with the prebuilt
+    # 32-bit installer on the python.org website.
+
+    # skip checks if the compiler was overriden with a CC env variable
+    if 'CC' in os.environ:
+        return _config_vars
+
+    if re.search('-arch\s+ppc', _config_vars['CFLAGS']) is not None:
+        # NOTE: Cannot use subprocess here because of bootstrap
+        # issues when building Python itself
+        status = os.system("'%s' -arch ppc -x c /dev/null 2>/dev/null"%(
+            _config_vars['CC'].replace("'", "'\"'\"'"),))
+        # The Apple compiler drivers return status 255 if no PPC
+        if (status >> 8) == 255:
+            # Compiler doesn't support PPC, remove the related
+            # '-arch' flags if not explicitly overridden by an
+            # environment variable
+            for cv in _UNIVERSAL_CONFIG_VARS:
+                if cv in _config_vars and cv not in os.environ:
+                    flags = _config_vars[cv]
+                    flags = re.sub('-arch\s+ppc\w*\s', ' ', flags)
+                    _save_modified_value(_config_vars, cv, flags)
+
+    return _config_vars
+
+
+def _override_all_archs(_config_vars):
+    """Allow override of all archs with ARCHFLAGS env var"""
+    # NOTE: This name was introduced by Apple in OSX 10.5 and
+    # is used by several scripting languages distributed with
+    # that OS release.
+    if 'ARCHFLAGS' in os.environ:
+        arch = os.environ['ARCHFLAGS']
+        for cv in _UNIVERSAL_CONFIG_VARS:
+            if cv in _config_vars and '-arch' in _config_vars[cv]:
+                flags = _config_vars[cv]
+                flags = re.sub('-arch\s+\w+\s', ' ', flags)
+                flags = flags + ' ' + arch
+                _save_modified_value(_config_vars, cv, flags)
+
+    return _config_vars
+
+
+def _check_for_unavailable_sdk(_config_vars):
+    """Remove references to any SDKs not available"""
+    # If we're on OSX 10.5 or later and the user tries to
+    # compile an extension using an SDK that is not present
+    # on the current machine it is better to not use an SDK
+    # than to fail.  This is particularly important with
+    # the standalong Command Line Tools alternative to a
+    # full-blown Xcode install since the CLT packages do not
+    # provide SDKs.  If the SDK is not present, it is assumed
+    # that the header files and dev libs have been installed
+    # to /usr and /System/Library by either a standalone CLT
+    # package or the CLT component within Xcode.
+    cflags = _config_vars.get('CFLAGS', '')
+    m = re.search(r'-isysroot\s+(\S+)', cflags)
+    if m is not None:
+        sdk = m.group(1)
+        if not os.path.exists(sdk):
+            for cv in _UNIVERSAL_CONFIG_VARS:
+                # Do not alter a config var explicitly overriden by env var
+                if cv in _config_vars and cv not in os.environ:
+                    flags = _config_vars[cv]
+                    flags = re.sub(r'-isysroot\s+\S+(?:\s|$)', ' ', flags)
+                    _save_modified_value(_config_vars, cv, flags)
+
+    return _config_vars
+
+
+def compiler_fixup(compiler_so, cc_args):
+    """
+    This function will strip '-isysroot PATH' and '-arch ARCH' from the
+    compile flags if the user has specified one them in extra_compile_flags.
+
+    This is needed because '-arch ARCH' adds another architecture to the
+    build, without a way to remove an architecture. Furthermore GCC will
+    barf if multiple '-isysroot' arguments are present.
+    """
+    stripArch = stripSysroot = False
+
+    compiler_so = list(compiler_so)
+
+    if not _supports_universal_builds():
+        # OSX before 10.4.0, these don't support -arch and -isysroot at
+        # all.
+        stripArch = stripSysroot = True
+    else:
+        stripArch = '-arch' in cc_args
+        stripSysroot = '-isysroot' in cc_args
+
+    if stripArch or 'ARCHFLAGS' in os.environ:
+        while True:
+            try:
+                index = compiler_so.index('-arch')
+                # Strip this argument and the next one:
+                del compiler_so[index:index+2]
+            except ValueError:
+                break
+
+    if 'ARCHFLAGS' in os.environ and not stripArch:
+        # User specified different -arch flags in the environ,
+        # see also distutils.sysconfig
+        compiler_so = compiler_so + os.environ['ARCHFLAGS'].split()
+
+    if stripSysroot:
+        while True:
+            try:
+                index = compiler_so.index('-isysroot')
+                # Strip this argument and the next one:
+                del compiler_so[index:index+2]
+            except ValueError:
+                break
+
+    # Check if the SDK that is used during compilation actually exists,
+    # the universal build requires the usage of a universal SDK and not all
+    # users have that installed by default.
+    sysroot = None
+    if '-isysroot' in cc_args:
+        idx = cc_args.index('-isysroot')
+        sysroot = cc_args[idx+1]
+    elif '-isysroot' in compiler_so:
+        idx = compiler_so.index('-isysroot')
+        sysroot = compiler_so[idx+1]
+
+    if sysroot and not os.path.isdir(sysroot):
+        from distutils import log
+        log.warn("Compiling with an SDK that doesn't seem to exist: %s",
+                sysroot)
+        log.warn("Please check your Xcode installation")
+
+    return compiler_so
+
+
+def customize_config_vars(_config_vars):
+    """Customize Python build configuration variables.
+
+    Called internally from sysconfig with a mutable mapping
+    containing name/value pairs parsed from the configured
+    makefile used to build this interpreter.  Returns
+    the mapping updated as needed to reflect the environment
+    in which the interpreter is running; in the case of
+    a Python from a binary installer, the installed
+    environment may be very different from the build
+    environment, i.e. different OS levels, different
+    built tools, different available CPU architectures.
+
+    This customization is performed whenever
+    distutils.sysconfig.get_config_vars() is first
+    called.  It may be used in environments where no
+    compilers are present, i.e. when installing pure
+    Python dists.  Customization of compiler paths
+    and detection of unavailable archs is deferred
+    until the first extention module build is
+    requested (in distutils.sysconfig.customize_compiler).
+
+    Currently called from distutils.sysconfig
+    """
+
+    if not _supports_universal_builds():
+        # On Mac OS X before 10.4, check if -arch and -isysroot
+        # are in CFLAGS or LDFLAGS and remove them if they are.
+        # This is needed when building extensions on a 10.3 system
+        # using a universal build of python.
+        _remove_universal_flags(_config_vars)
+
+    # Allow user to override all archs with ARCHFLAGS env var
+    _override_all_archs(_config_vars)
+
+    # Remove references to sdks that are not found
+    _check_for_unavailable_sdk(_config_vars)
+
+    return _config_vars
+
+
+def customize_compiler(_config_vars):
+    """Customize compiler path and configuration variables.
+
+    This customization is performed when the first
+    extension module build is requested
+    in distutils.sysconfig.customize_compiler).
+    """
+
+    # Find a compiler to use for extension module builds
+    _find_appropriate_compiler(_config_vars)
+
+    # Remove ppc arch flags if not supported here
+    _remove_unsupported_archs(_config_vars)
+
+    # Allow user to override all archs with ARCHFLAGS env var
+    _override_all_archs(_config_vars)
+
+    return _config_vars
+
+
+def get_platform_osx(_config_vars, osname, release, machine):
+    """Filter values for get_platform()"""
+    # called from get_platform() in sysconfig and distutils.util
+    #
+    # For our purposes, we'll assume that the system version from
+    # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
+    # to. This makes the compatibility story a bit more sane because the
+    # machine is going to compile and link as if it were
+    # MACOSX_DEPLOYMENT_TARGET.
+
+    macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
+    macrelease = _get_system_version() or macver
+    macver = macver or macrelease
+
+    if macver:
+        release = macver
+        osname = "macosx"
+
+        # Use the original CFLAGS value, if available, so that we
+        # return the same machine type for the platform string.
+        # Otherwise, distutils may consider this a cross-compiling
+        # case and disallow installs.
+        cflags = _config_vars.get(_INITPRE+'CFLAGS',
+                                    _config_vars.get('CFLAGS', ''))
+        if ((macrelease + '.') >= '10.4.' and
+            '-arch' in cflags.strip()):
+            # The universal build will build fat binaries, but not on
+            # systems before 10.4
+
+            machine = 'fat'
+
+            archs = re.findall('-arch\s+(\S+)', cflags)
+            archs = tuple(sorted(set(archs)))
+
+            if len(archs) == 1:
+                machine = archs[0]
+            elif archs == ('i386', 'ppc'):
+                machine = 'fat'
+            elif archs == ('i386', 'x86_64'):
+                machine = 'intel'
+            elif archs == ('i386', 'ppc', 'x86_64'):
+                machine = 'fat3'
+            elif archs == ('ppc64', 'x86_64'):
+                machine = 'fat64'
+            elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
+                machine = 'universal'
+            else:
+                raise ValueError(
+                   "Don't know machine value for archs=%r" % (archs,))
+
+        elif machine == 'i386':
+            # On OSX the machine type returned by uname is always the
+            # 32-bit variant, even if the executable architecture is
+            # the 64-bit variant
+            if sys.maxsize >= 2**32:
+                machine = 'x86_64'
+
+        elif machine in ('PowerPC', 'Power_Macintosh'):
+            # Pick a sane name for the PPC architecture.
+            # See 'i386' case
+            if sys.maxsize >= 2**32:
+                machine = 'ppc64'
+            else:
+                machine = 'ppc'
+
+    return (osname, release, machine)
diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -172,6 +172,21 @@
     varies across Unices and is stored in Python's Makefile.
     """
     if compiler.compiler_type == "unix":
+        if sys.platform == "darwin":
+            # Perform first-time customization of compiler-related
+            # config vars on OS X now that we know we need a compiler.
+            # This is primarily to support Pythons from binary
+            # installers.  The kind and paths to build tools on
+            # the user system may vary significantly from the system
+            # that Python itself was built on.  Also the user OS
+            # version and build tools may not support the same set
+            # of CPU architectures for universal builds.
+            global _config_vars
+            if not _config_vars.get('CUSTOMIZED_OSX_COMPILER', ''):
+                import _osx_support
+                _osx_support.customize_compiler(_config_vars)
+                _config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
+
         (cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \
             get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS',
                             'CCSHARED', 'LDSHARED', 'SO', 'AR', 'ARFLAGS')
@@ -494,35 +509,12 @@
     _config_vars = g
 
 
-def _read_output(commandstring):
-    """
-    Returns os.popen(commandstring, "r").read(), but
-    without actually using os.popen because that
-    function is not usable during python bootstrap
-    """
-    # NOTE: tempfile is also not useable during
-    # bootstrap
-    import contextlib
-    try:
-        import tempfile
-        fp = tempfile.NamedTemporaryFile()
-    except ImportError:
-        fp = open("/tmp/distutils.%s"%(
-            os.getpid(),), "w+b")
-
-    with contextlib.closing(fp) as fp:
-        cmd = "%s >'%s'"%(commandstring, fp.name)
-        os.system(cmd)
-        data = fp.read()
-
-    return data.decode('utf-8')
-
 def get_config_vars(*args):
     """With no arguments, return a dictionary of all configuration
     variables relevant for the current platform.  Generally this includes
     everything needed to build extensions and install both pure modules and
     extensions.  On Unix, this means every variable defined in Python's
-    installed Makefile; on Windows and Mac OS it's a much smaller set.
+    installed Makefile; on Windows it's a much smaller set.
 
     With arguments, return a list of values that result from looking up
     each argument in the configuration variable dictionary.
@@ -555,153 +547,11 @@
                 srcdir = os.path.join(base, _config_vars['srcdir'])
                 _config_vars['srcdir'] = os.path.normpath(srcdir)
 
+        # OS X platforms require special customization to handle
+        # multi-architecture, multi-os-version installers
         if sys.platform == 'darwin':
-            from distutils.spawn import find_executable
-
-            kernel_version = os.uname()[2] # Kernel version (8.4.3)
-            major_version = int(kernel_version.split('.')[0])
-
-            # Issue #13590:
-            #    The OSX location for the compiler varies between OSX
-            #    (or rather Xcode) releases.  With older releases (up-to 10.5)
-            #    the compiler is in /usr/bin, with newer releases the compiler
-            #    can only be found inside Xcode.app if the "Command Line Tools"
-            #    are not installed.
-            #
-            #    Futhermore, the compiler that can be used varies between
-            #    Xcode releases. Upto Xcode 4 it was possible to use 'gcc-4.2'
-            #    as the compiler, after that 'clang' should be used because
-            #    gcc-4.2 is either not present, or a copy of 'llvm-gcc' that
-            #    miscompiles Python.
-
-            # skip checks if the compiler was overriden with a CC env variable
-            if 'CC' not in os.environ:
-                cc = oldcc = _config_vars['CC']
-                if not find_executable(cc):
-                    # Compiler is not found on the shell search PATH.
-                    # Now search for clang, first on PATH (if the Command LIne
-                    # Tools have been installed in / or if the user has provided
-                    # another location via CC).  If not found, try using xcrun
-                    # to find an uninstalled clang (within a selected Xcode).
-
-                    # NOTE: Cannot use subprocess here because of bootstrap
-                    # issues when building Python itself (and os.popen is
-                    # implemented on top of subprocess and is therefore not
-                    # usable as well)
-
-                    data = (find_executable('clang') or
-                            _read_output(
-                                "/usr/bin/xcrun -find clang 2>/dev/null").strip())
-                    if not data:
-                        raise DistutilsPlatformError(
-                               "Cannot locate working compiler")
-
-                    _config_vars['CC'] = cc = data
-                    _config_vars['CXX'] = cc + '++'
-
-                elif os.path.basename(cc).startswith('gcc'):
-                    # Compiler is GCC, check if it is LLVM-GCC
-                    data = _read_output("'%s' --version 2>/dev/null"
-                                         % (cc.replace("'", "'\"'\"'"),))
-                    if 'llvm-gcc' in data:
-                        # Found LLVM-GCC, fall back to clang
-                        data = (find_executable('clang') or
-                                _read_output(
-                                    "/usr/bin/xcrun -find clang 2>/dev/null").strip())
-                        if find_executable(data):
-                            _config_vars['CC'] = cc = data
-                            _config_vars['CXX'] = cc + '++'
-
-                if (cc != oldcc
-                        and 'LDSHARED' in _config_vars
-                        and 'LDSHARED' not in os.environ):
-                    # modify LDSHARED if we modified CC
-                    ldshared = _config_vars['LDSHARED']
-                    if ldshared.startswith(oldcc):
-                        _config_vars['LDSHARED'] = cc + ldshared[len(oldcc):]
-
-            if major_version < 8:
-                # On Mac OS X before 10.4, check if -arch and -isysroot
-                # are in CFLAGS or LDFLAGS and remove them if they are.
-                # This is needed when building extensions on a 10.3 system
-                # using a universal build of python.
-                for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
-                        # a number of derived variables. These need to be
-                        # patched up as well.
-                        'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-                    flags = _config_vars[key]
-                    flags = re.sub('-arch\s+\w+\s', ' ', flags, re.ASCII)
-                    flags = re.sub('-isysroot [^ \t]*', ' ', flags)
-                    _config_vars[key] = flags
-
-            else:
-                # Different Xcode releases support different sets for '-arch'
-                # flags. In particular, Xcode 4.x no longer supports the
-                # PPC architectures.
-                #
-                # This code automatically removes '-arch ppc' and '-arch ppc64'
-                # when these are not supported. That makes it possible to
-                # build extensions on OSX 10.7 and later with the prebuilt
-                # 32-bit installer on the python.org website.
-                flags = _config_vars['CFLAGS']
-                if re.search('-arch\s+ppc', flags) is not None:
-                    # NOTE: Cannot use subprocess here because of bootstrap
-                    # issues when building Python itself
-                    status = os.system("'%s' -arch ppc -x c /dev/null 2>/dev/null"%(
-                        _config_vars['CC'].replace("'", "'\"'\"'"),))
-
-                    if status != 0:
-                        # Compiler doesn't support PPC, remove the related
-                        # '-arch' flags.
-                        for key in ('LDFLAGS', 'BASECFLAGS',
-                            # a number of derived variables. These need to be
-                            # patched up as well.
-                            'CFLAGS', 'PY_CFLAGS', 'BLDSHARED', 'LDSHARED'):
-
-                            flags = _config_vars[key]
-                            flags = re.sub('-arch\s+ppc\w*\s', ' ', flags)
-                            _config_vars[key] = flags
-
-
-                # Allow the user to override the architecture flags using
-                # an environment variable.
-                # NOTE: This name was introduced by Apple in OSX 10.5 and
-                # is used by several scripting languages distributed with
-                # that OS release.
-                if 'ARCHFLAGS' in os.environ:
-                    arch = os.environ['ARCHFLAGS']
-                    for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
-                        # a number of derived variables. These need to be
-                        # patched up as well.
-                        'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
-                        flags = _config_vars[key]
-                        flags = re.sub('-arch\s+\w+\s', ' ', flags)
-                        flags = flags + ' ' + arch
-                        _config_vars[key] = flags
-
-                # If we're on OSX 10.5 or later and the user tries to
-                # compiles an extension using an SDK that is not present
-                # on the current machine it is better to not use an SDK
-                # than to fail.
-                #
-                # The major usecase for this is users using a Python.org
-                # binary installer  on OSX 10.6: that installer uses
-                # the 10.4u SDK, but that SDK is not installed by default
-                # when you install Xcode.
-                #
-                m = re.search('-isysroot\s+(\S+)', _config_vars['CFLAGS'])
-                if m is not None:
-                    sdk = m.group(1)
-                    if not os.path.exists(sdk):
-                        for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
-                             # a number of derived variables. These need to be
-                             # patched up as well.
-                            'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
-                            flags = _config_vars[key]
-                            flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
-                            _config_vars[key] = flags
+            import _osx_support
+            _osx_support.customize_config_vars(_config_vars)
 
     if args:
         vals = []
diff --git a/Lib/distutils/tests/test_util.py b/Lib/distutils/tests/test_util.py
--- a/Lib/distutils/tests/test_util.py
+++ b/Lib/distutils/tests/test_util.py
@@ -13,6 +13,7 @@
 from distutils.sysconfig import get_config_vars
 from distutils import sysconfig
 from distutils.tests import support
+import _osx_support
 
 class UtilTestCase(support.EnvironGuard, unittest.TestCase):
 
@@ -92,6 +93,7 @@
                    ('Darwin Kernel Version 8.11.1: '
                     'Wed Oct 10 18:23:28 PDT 2007; '
                     'root:xnu-792.25.20~1/RELEASE_I386'), 'i386'))
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
 
         get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g '
@@ -105,6 +107,7 @@
             sys.maxsize = cursize
 
         # macbook with fat binaries (fat, universal or fat64)
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4'
         get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
@@ -113,10 +116,12 @@
 
         self.assertEqual(get_platform(), 'macosx-10.4-fat')
 
+        _osx_support._remove_original_values(get_config_vars())
         os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.1'
         self.assertEqual(get_platform(), 'macosx-10.4-fat')
 
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
@@ -124,18 +129,21 @@
 
         self.assertEqual(get_platform(), 'macosx-10.4-intel')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
                                        '-dynamic -DNDEBUG -g -O3')
         self.assertEqual(get_platform(), 'macosx-10.4-fat3')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
                                        '-dynamic -DNDEBUG -g -O3')
         self.assertEqual(get_platform(), 'macosx-10.4-universal')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
@@ -144,6 +152,7 @@
         self.assertEqual(get_platform(), 'macosx-10.4-fat64')
 
         for arch in ('ppc', 'i386', 'x86_64', 'ppc64'):
+            _osx_support._remove_original_values(get_config_vars())
             get_config_vars()['CFLAGS'] = ('-arch %s -isysroot '
                                            '/Developer/SDKs/MacOSX10.4u.sdk  '
                                            '-fno-strict-aliasing -fno-common '
diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py
--- a/Lib/distutils/unixccompiler.py
+++ b/Lib/distutils/unixccompiler.py
@@ -23,6 +23,9 @@
      DistutilsExecError, CompileError, LibError, LinkError
 from distutils import log
 
+if sys.platform == 'darwin':
+    import _osx_support
+
 # XXX Things not currently handled:
 #   * optimization/debug/warning flags; we just use whatever's in Python's
 #     Makefile and live with it.  Is this adequate?  If not, we might
@@ -38,68 +41,6 @@
 #     should just happily stuff them into the preprocessor/compiler/linker
 #     options and carry on.
 
-def _darwin_compiler_fixup(compiler_so, cc_args):
-    """
-    This function will strip '-isysroot PATH' and '-arch ARCH' from the
-    compile flags if the user has specified one them in extra_compile_flags.
-
-    This is needed because '-arch ARCH' adds another architecture to the
-    build, without a way to remove an architecture. Furthermore GCC will
-    barf if multiple '-isysroot' arguments are present.
-    """
-    stripArch = stripSysroot = False
-
-    compiler_so = list(compiler_so)
-    kernel_version = os.uname()[2] # 8.4.3
-    major_version = int(kernel_version.split('.')[0])
-
-    if major_version < 8:
-        # OSX before 10.4.0, these don't support -arch and -isysroot at
-        # all.
-        stripArch = stripSysroot = True
-    else:
-        stripArch = '-arch' in cc_args
-        stripSysroot = '-isysroot' in cc_args
-
-    if stripArch or 'ARCHFLAGS' in os.environ:
-        while True:
-            try:
-                index = compiler_so.index('-arch')
-                # Strip this argument and the next one:
-                del compiler_so[index:index+2]
-            except ValueError:
-                break
-
-    if 'ARCHFLAGS' in os.environ and not stripArch:
-        # User specified different -arch flags in the environ,
-        # see also distutils.sysconfig
-        compiler_so = compiler_so + os.environ['ARCHFLAGS'].split()
-
-    if stripSysroot:
-        try:
-            index = compiler_so.index('-isysroot')
-            # Strip this argument and the next one:
-            del compiler_so[index:index+2]
-        except ValueError:
-            pass
-
-    # Check if the SDK that is used during compilation actually exists,
-    # the universal build requires the usage of a universal SDK and not all
-    # users have that installed by default.
-    sysroot = None
-    if '-isysroot' in cc_args:
-        idx = cc_args.index('-isysroot')
-        sysroot = cc_args[idx+1]
-    elif '-isysroot' in compiler_so:
-        idx = compiler_so.index('-isysroot')
-        sysroot = compiler_so[idx+1]
-
-    if sysroot and not os.path.isdir(sysroot):
-        log.warn("Compiling with an SDK that doesn't seem to exist: %s",
-                sysroot)
-        log.warn("Please check your Xcode installation")
-
-    return compiler_so
 
 class UnixCCompiler(CCompiler):
 
@@ -168,7 +109,8 @@
     def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
         compiler_so = self.compiler_so
         if sys.platform == 'darwin':
-            compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs)
+            compiler_so = _osx_support.compiler_fixup(compiler_so,
+                                                    cc_args + extra_postargs)
         try:
             self.spawn(compiler_so + cc_args + [src, '-o', obj] +
                        extra_postargs)
@@ -247,7 +189,7 @@
                     linker[i] = self.compiler_cxx[i]
 
                 if sys.platform == 'darwin':
-                    linker = _darwin_compiler_fixup(linker, ld_args)
+                    linker = _osx_support.compiler_fixup(linker, ld_args)
 
                 self.spawn(linker + ld_args)
             except DistutilsExecError as msg:
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -98,94 +98,10 @@
         if m:
             release = m.group()
     elif osname[:6] == "darwin":
-        #
-        # For our purposes, we'll assume that the system version from
-        # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
-        # to. This makes the compatibility story a bit more sane because the
-        # machine is going to compile and link as if it were
-        # MACOSX_DEPLOYMENT_TARGET.
-        from distutils.sysconfig import get_config_vars
-        cfgvars = get_config_vars()
-
-        macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
-
-        if 1:
-            # Always calculate the release of the running machine,
-            # needed to determine if we can build fat binaries or not.
-
-            macrelease = macver
-            # Get the system version. Reading this plist is a documented
-            # way to get the system version (see the documentation for
-            # the Gestalt Manager)
-            try:
-                f = open('/System/Library/CoreServices/SystemVersion.plist')
-            except IOError:
-                # We're on a plain darwin box, fall back to the default
-                # behaviour.
-                pass
-            else:
-                try:
-                    m = re.search(
-                            r'<key>ProductUserVisibleVersion</key>\s*' +
-                            r'<string>(.*?)</string>', f.read())
-                    if m is not None:
-                        macrelease = '.'.join(m.group(1).split('.')[:2])
-                    # else: fall back to the default behaviour
-                finally:
-                    f.close()
-
-        if not macver:
-            macver = macrelease
-
-        if macver:
-            from distutils.sysconfig import get_config_vars
-            release = macver
-            osname = "macosx"
-
-            if (macrelease + '.') >= '10.4.' and \
-                    '-arch' in get_config_vars().get('CFLAGS', '').strip():
-                # The universal build will build fat binaries, but not on
-                # systems before 10.4
-                #
-                # Try to detect 4-way universal builds, those have machine-type
-                # 'universal' instead of 'fat'.
-
-                machine = 'fat'
-                cflags = get_config_vars().get('CFLAGS')
-
-                archs = re.findall('-arch\s+(\S+)', cflags)
-                archs = tuple(sorted(set(archs)))
-
-                if len(archs) == 1:
-                    machine = archs[0]
-                elif archs == ('i386', 'ppc'):
-                    machine = 'fat'
-                elif archs == ('i386', 'x86_64'):
-                    machine = 'intel'
-                elif archs == ('i386', 'ppc', 'x86_64'):
-                    machine = 'fat3'
-                elif archs == ('ppc64', 'x86_64'):
-                    machine = 'fat64'
-                elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
-                    machine = 'universal'
-                else:
-                    raise ValueError(
-                       "Don't know machine value for archs=%r"%(archs,))
-
-            elif machine == 'i386':
-                # On OSX the machine type returned by uname is always the
-                # 32-bit variant, even if the executable architecture is
-                # the 64-bit variant
-                if sys.maxsize >= 2**32:
-                    machine = 'x86_64'
-
-            elif machine in ('PowerPC', 'Power_Macintosh'):
-                # Pick a sane name for the PPC architecture.
-                machine = 'ppc'
-
-                # See 'i386' case
-                if sys.maxsize >= 2**32:
-                    machine = 'ppc64'
+        import _osx_support, distutils.sysconfig
+        osname, release, machine = _osx_support.get_platform_osx(
+                                        distutils.sysconfig.get_config_vars(),
+                                        osname, release, machine)
 
     return "%s-%s-%s" % (osname, release, machine)
 
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -497,7 +497,7 @@
     variables relevant for the current platform.
 
     On Unix, this means every variable defined in Python's installed Makefile;
-    On Windows and Mac OS it's a much smaller set.
+    On Windows it's a much smaller set.
 
     With arguments, return a list of values that result from looking up
     each argument in the configuration variable dictionary.
@@ -556,64 +556,11 @@
                 srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
                 _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
 
+        # OS X platforms require special customization to handle
+        # multi-architecture, multi-os-version installers
         if sys.platform == 'darwin':
-            kernel_version = os.uname().release  # Kernel version (8.4.3)
-            major_version = int(kernel_version.split('.')[0])
-
-            if major_version < 8:
-                # On Mac OS X before 10.4, check if -arch and -isysroot
-                # are in CFLAGS or LDFLAGS and remove them if they are.
-                # This is needed when building extensions on a 10.3 system
-                # using a universal build of python.
-                for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
-                        # a number of derived variables. These need to be
-                        # patched up as well.
-                        'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-                    flags = _CONFIG_VARS[key]
-                    flags = re.sub('-arch\s+\w+\s', ' ', flags)
-                    flags = re.sub('-isysroot [^ \t]*', ' ', flags)
-                    _CONFIG_VARS[key] = flags
-            else:
-                # Allow the user to override the architecture flags using
-                # an environment variable.
-                # NOTE: This name was introduced by Apple in OSX 10.5 and
-                # is used by several scripting languages distributed with
-                # that OS release.
-                if 'ARCHFLAGS' in os.environ:
-                    arch = os.environ['ARCHFLAGS']
-                    for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
-                        # a number of derived variables. These need to be
-                        # patched up as well.
-                        'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
-                        flags = _CONFIG_VARS[key]
-                        flags = re.sub('-arch\s+\w+\s', ' ', flags)
-                        flags = flags + ' ' + arch
-                        _CONFIG_VARS[key] = flags
-
-                # If we're on OSX 10.5 or later and the user tries to
-                # compiles an extension using an SDK that is not present
-                # on the current machine it is better to not use an SDK
-                # than to fail.
-                #
-                # The major usecase for this is users using a Python.org
-                # binary installer  on OSX 10.6: that installer uses
-                # the 10.4u SDK, but that SDK is not installed by default
-                # when you install Xcode.
-                #
-                CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
-                m = re.search('-isysroot\s+(\S+)', CFLAGS)
-                if m is not None:
-                    sdk = m.group(1)
-                    if not os.path.exists(sdk):
-                        for key in ('LDFLAGS', 'BASECFLAGS',  'LDSHARED',
-                             # a number of derived variables. These need to be
-                             # patched up as well.
-                            'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
-                            flags = _CONFIG_VARS[key]
-                            flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
-                            _CONFIG_VARS[key] = flags
+            import _osx_support
+            _osx_support.customize_config_vars(_CONFIG_VARS)
 
     if args:
         vals = []
@@ -673,8 +620,7 @@
         return sys.platform
 
     if os.name != "posix" or not hasattr(os, 'uname'):
-        # XXX what about the architecture? NT is Intel or Alpha,
-        # Mac OS is M68k or PPC, etc.
+        # XXX what about the architecture? NT is Intel or Alpha
         return sys.platform
 
     # Set for cross builds explicitly
@@ -716,90 +662,10 @@
         if m:
             release = m.group()
     elif osname[:6] == "darwin":
-        #
-        # For our purposes, we'll assume that the system version from
-        # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
-        # to. This makes the compatibility story a bit more sane because the
-        # machine is going to compile and link as if it were
-        # MACOSX_DEPLOYMENT_TARGET.
-        cfgvars = get_config_vars()
-        macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
-
-        if True:
-            # Always calculate the release of the running machine,
-            # needed to determine if we can build fat binaries or not.
-
-            macrelease = macver
-            # Get the system version. Reading this plist is a documented
-            # way to get the system version (see the documentation for
-            # the Gestalt Manager)
-            try:
-                f = open('/System/Library/CoreServices/SystemVersion.plist')
-            except IOError:
-                # We're on a plain darwin box, fall back to the default
-                # behaviour.
-                pass
-            else:
-                try:
-                    m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
-                                  r'<string>(.*?)</string>', f.read())
-                finally:
-                    f.close()
-                if m is not None:
-                    macrelease = '.'.join(m.group(1).split('.')[:2])
-                # else: fall back to the default behaviour
-
-        if not macver:
-            macver = macrelease
-
-        if macver:
-            release = macver
-            osname = "macosx"
-
-            if ((macrelease + '.') >= '10.4.' and
-                '-arch' in get_config_vars().get('CFLAGS', '').strip()):
-                # The universal build will build fat binaries, but not on
-                # systems before 10.4
-                #
-                # Try to detect 4-way universal builds, those have machine-type
-                # 'universal' instead of 'fat'.
-
-                machine = 'fat'
-                cflags = get_config_vars().get('CFLAGS')
-
-                archs = re.findall('-arch\s+(\S+)', cflags)
-                archs = tuple(sorted(set(archs)))
-
-                if len(archs) == 1:
-                    machine = archs[0]
-                elif archs == ('i386', 'ppc'):
-                    machine = 'fat'
-                elif archs == ('i386', 'x86_64'):
-                    machine = 'intel'
-                elif archs == ('i386', 'ppc', 'x86_64'):
-                    machine = 'fat3'
-                elif archs == ('ppc64', 'x86_64'):
-                    machine = 'fat64'
-                elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
-                    machine = 'universal'
-                else:
-                    raise ValueError(
-                       "Don't know machine value for archs=%r" % (archs,))
-
-            elif machine == 'i386':
-                # On OSX the machine type returned by uname is always the
-                # 32-bit variant, even if the executable architecture is
-                # the 64-bit variant
-                if sys.maxsize >= 2**32:
-                    machine = 'x86_64'
-
-            elif machine in ('PowerPC', 'Power_Macintosh'):
-                # Pick a sane name for the PPC architecture.
-                # See 'i386' case
-                if sys.maxsize >= 2**32:
-                    machine = 'ppc64'
-                else:
-                    machine = 'ppc'
+        import _osx_support
+        osname, release, machine = _osx_support.get_platform_osx(
+                                            get_config_vars(),
+                                            osname, release, machine)
 
     return "%s-%s-%s" % (osname, release, machine)
 
diff --git a/Lib/test/test__osx_support.py b/Lib/test/test__osx_support.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test__osx_support.py
@@ -0,0 +1,267 @@
+"""
+Test suite for _osx_support: shared OS X support functions.
+"""
+
+import os
+import platform
+import shutil
+import stat
+import sys
+import unittest
+
+import test.support
+
+import _osx_support
+
+ at unittest.skipUnless(sys.platform.startswith("darwin"), "requires OS X")
+class Test_OSXSupport(unittest.TestCase):
+
+    def setUp(self):
+        self.maxDiff = None
+        self.prog_name = 'bogus_program_xxxx'
+        self.temp_path_dir = os.path.abspath(os.getcwd())
+
+    def add_expected_saved_initial_values(self, config_vars, expected_vars):
+        # Ensure that the initial values for all modified config vars
+        # are also saved with modified keys.
+        expected_vars.update(('_OSX_SUPPORT_INITIAL_'+ k,
+                config_vars[k]) for k in config_vars
+                    if config_vars[k] != expected_vars[k])
+
+    def test__find_executable(self):
+        with test.support.EnvironmentVarGuard() as env:
+            if env['PATH']:
+                env['PATH'] = env['PATH'] + ':'
+            env['PATH'] = env['PATH'] + os.path.abspath(self.temp_path_dir)
+            test.support.unlink(self.prog_name)
+            self.assertIsNone(_osx_support._find_executable(self.prog_name))
+            self.addCleanup(test.support.unlink, self.prog_name)
+            with open(self.prog_name, 'w') as f:
+                f.write("#!/bin/sh\n/bin/echo OK\n")
+            os.chmod(self.prog_name, stat.S_IRWXU)
+            self.assertEqual(self.prog_name,
+                                _osx_support._find_executable(self.prog_name))
+
+    def test__read_output(self):
+        with test.support.EnvironmentVarGuard() as env:
+            if env['PATH']:
+                env['PATH'] = env['PATH'] + ':'
+            env['PATH'] = env['PATH'] + os.path.abspath(self.temp_path_dir)
+            test.support.unlink(self.prog_name)
+            self.addCleanup(test.support.unlink, self.prog_name)
+            with open(self.prog_name, 'w') as f:
+                f.write("#!/bin/sh\n/bin/echo ExpectedOutput\n")
+            os.chmod(self.prog_name, stat.S_IRWXU)
+            self.assertEqual('ExpectedOutput',
+                                _osx_support._read_output(self.prog_name))
+
+    def test__find_build_tool(self):
+        out = _osx_support._find_build_tool('cc')
+        self.assertTrue(os.path.isfile(out),
+                            'cc not found - check xcode-select')
+
+    def test__get_system_version(self):
+        self.assertTrue(platform.mac_ver()[0].startswith(
+                                    _osx_support._get_system_version()))
+
+    def test__remove_original_values(self):
+        config_vars = {
+        'CC': 'gcc-test -pthreads',
+        }
+        expected_vars = {
+        'CC': 'clang -pthreads',
+        }
+        cv = 'CC'
+        newvalue = 'clang -pthreads'
+        _osx_support._save_modified_value(config_vars, cv, newvalue)
+        self.assertNotEqual(expected_vars, config_vars)
+        _osx_support._remove_original_values(config_vars)
+        self.assertEqual(expected_vars, config_vars)
+
+    def test__save_modified_value(self):
+        config_vars = {
+        'CC': 'gcc-test -pthreads',
+        }
+        expected_vars = {
+        'CC': 'clang -pthreads',
+        }
+        self.add_expected_saved_initial_values(config_vars, expected_vars)
+        cv = 'CC'
+        newvalue = 'clang -pthreads'
+        _osx_support._save_modified_value(config_vars, cv, newvalue)
+        self.assertEqual(expected_vars, config_vars)
+
+    def test__save_modified_value_unchanged(self):
+        config_vars = {
+        'CC': 'gcc-test -pthreads',
+        }
+        expected_vars = config_vars.copy()
+        cv = 'CC'
+        newvalue = 'gcc-test -pthreads'
+        _osx_support._save_modified_value(config_vars, cv, newvalue)
+        self.assertEqual(expected_vars, config_vars)
+
+    def test__supports_universal_builds(self):
+        import platform
+        self.assertEqual(platform.mac_ver()[0].split('.') >= ['10', '4'],
+                            _osx_support._supports_universal_builds())
+
+    def test__find_appropriate_compiler(self):
+        compilers = (
+                        ('gcc-test', 'i686-apple-darwin11-llvm-gcc-4.2'),
+                        ('clang', 'clang version 3.1'),
+                    )
+        config_vars = {
+        'CC': 'gcc-test -pthreads',
+        'CXX': 'cc++-test',
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
+        'LDFLAGS': '-arch ppc -arch i386   -g',
+        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+        'BLDSHARED': 'gcc-test -bundle -arch ppc -arch i386 -g',
+        'LDSHARED': 'gcc-test -bundle -arch ppc -arch i386 '
+                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
+        }
+        expected_vars = {
+        'CC': 'clang -pthreads',
+        'CXX': 'clang++',
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
+        'LDFLAGS': '-arch ppc -arch i386   -g',
+        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+        'BLDSHARED': 'clang -bundle -arch ppc -arch i386 -g',
+        'LDSHARED': 'clang -bundle -arch ppc -arch i386 '
+                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
+        }
+        self.add_expected_saved_initial_values(config_vars, expected_vars)
+
+        with test.support.EnvironmentVarGuard() as env:
+            suffix = (':' + env['PATH']) if env['PATH'] else ''
+            env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix
+            for c_name, c_output in compilers:
+                test.support.unlink(c_name)
+                self.addCleanup(test.support.unlink, c_name)
+                with open(c_name, 'w') as f:
+                    f.write("#!/bin/sh\n/bin/echo " + c_output)
+                os.chmod(c_name, stat.S_IRWXU)
+            self.assertEqual(expected_vars,
+                                _osx_support._find_appropriate_compiler(
+                                        config_vars))
+
+    def test__remove_universal_flags(self):
+        config_vars = {
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
+        'LDFLAGS': '-arch ppc -arch i386   -g',
+        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
+        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
+                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
+        }
+        expected_vars = {
+        'CFLAGS': '-fno-strict-aliasing  -g -O3    ',
+        'LDFLAGS': '    -g',
+        'CPPFLAGS': '-I.  ',
+        'BLDSHARED': 'gcc-4.0 -bundle    -g',
+        'LDSHARED': 'gcc-4.0 -bundle      -g',
+        }
+        self.add_expected_saved_initial_values(config_vars, expected_vars)
+
+        self.assertEqual(expected_vars,
+                            _osx_support._remove_universal_flags(
+                                    config_vars))
+
+    @unittest.skipUnless(shutil.which('clang'),'test requires clang')
+    def test__remove_unsupported_archs(self):
+        config_vars = {
+        'CC': 'clang',
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
+        'LDFLAGS': '-arch ppc -arch i386   -g',
+        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
+        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
+                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
+        }
+        expected_vars = {
+        'CC': 'clang',
+        'CFLAGS': '-fno-strict-aliasing  -g -O3  -arch i386  ',
+        'LDFLAGS': ' -arch i386   -g',
+        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+        'BLDSHARED': 'gcc-4.0 -bundle   -arch i386 -g',
+        'LDSHARED': 'gcc-4.0 -bundle   -arch i386 '
+                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
+        }
+        self.add_expected_saved_initial_values(config_vars, expected_vars)
+
+        self.assertEqual(expected_vars,
+                            _osx_support._remove_unsupported_archs(
+                                    config_vars))
+
+    def test__override_all_archs(self):
+        with test.support.EnvironmentVarGuard() as env:
+            env['ARCHFLAGS'] = '-arch x86_64'
+            config_vars = {
+            'CC': 'clang',
+            'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
+            'LDFLAGS': '-arch ppc -arch i386   -g',
+            'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+            'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
+            'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 '
+                            '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
+            }
+            expected_vars = {
+            'CC': 'clang',
+            'CFLAGS': '-fno-strict-aliasing  -g -O3     -arch x86_64',
+            'LDFLAGS': '    -g -arch x86_64',
+            'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
+            'BLDSHARED': 'gcc-4.0 -bundle    -g -arch x86_64',
+            'LDSHARED': 'gcc-4.0 -bundle   -isysroot '
+                            '/Developer/SDKs/MacOSX10.4u.sdk -g -arch x86_64',
+            }
+            self.add_expected_saved_initial_values(config_vars, expected_vars)
+
+            self.assertEqual(expected_vars,
+                                _osx_support._override_all_archs(
+                                        config_vars))
+
+    def test__check_for_unavailable_sdk(self):
+        config_vars = {
+        'CC': 'clang',
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
+                        '-isysroot /Developer/SDKs/MacOSX10.1.sdk',
+        'LDFLAGS': '-arch ppc -arch i386   -g',
+        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.1.sdk',
+        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
+        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
+                        '-isysroot /Developer/SDKs/MacOSX10.1.sdk -g',
+        }
+        expected_vars = {
+        'CC': 'clang',
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
+                        ' ',
+        'LDFLAGS': '-arch ppc -arch i386   -g',
+        'CPPFLAGS': '-I.  ',
+        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
+        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
+                        ' -g',
+        }
+        self.add_expected_saved_initial_values(config_vars, expected_vars)
+
+        self.assertEqual(expected_vars,
+                            _osx_support._check_for_unavailable_sdk(
+                                    config_vars))
+
+    def test_get_platform_osx(self):
+        # Note, get_platform_osx is currently tested more extensively
+        # indirectly by test_sysconfig and test_distutils
+        config_vars = {
+        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
+                        '-isysroot /Developer/SDKs/MacOSX10.1.sdk',
+        'MACOSX_DEPLOYMENT_TARGET': '10.6',
+        }
+        result = _osx_support.get_platform_osx(config_vars, ' ', ' ', ' ')
+        self.assertEqual(('macosx', '10.6', 'fat'), result)
+
+def test_main():
+    if sys.platform == 'darwin':
+        test.support.run_unittest(Test_OSXSupport)
+
+if __name__ == "__main__":
+    test_main()
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -13,6 +13,7 @@
                        get_path, get_path_names, _INSTALL_SCHEMES,
                        _get_default_scheme, _expand_vars,
                        get_scheme_names, get_config_var, _main)
+import _osx_support
 
 class TestSysConfig(unittest.TestCase):
 
@@ -134,6 +135,7 @@
                    ('Darwin Kernel Version 8.11.1: '
                     'Wed Oct 10 18:23:28 PDT 2007; '
                     'root:xnu-792.25.20~1/RELEASE_I386'), 'PowerPC'))
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
 
         get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g '
@@ -152,7 +154,7 @@
                    ('Darwin Kernel Version 8.11.1: '
                     'Wed Oct 10 18:23:28 PDT 2007; '
                     'root:xnu-792.25.20~1/RELEASE_I386'), 'i386'))
-        get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
 
         get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g '
@@ -167,6 +169,7 @@
             sys.maxsize = maxint
 
         # macbook with fat binaries (fat, universal or fat64)
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4'
         get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
@@ -175,6 +178,7 @@
 
         self.assertEqual(get_platform(), 'macosx-10.4-fat')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
@@ -182,18 +186,21 @@
 
         self.assertEqual(get_platform(), 'macosx-10.4-intel')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
                                        '-dynamic -DNDEBUG -g -O3')
         self.assertEqual(get_platform(), 'macosx-10.4-fat3')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
                                        '-dynamic -DNDEBUG -g -O3')
         self.assertEqual(get_platform(), 'macosx-10.4-universal')
 
+        _osx_support._remove_original_values(get_config_vars())
         get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot '
                                        '/Developer/SDKs/MacOSX10.4u.sdk  '
                                        '-fno-strict-aliasing -fno-common '
@@ -202,6 +209,7 @@
         self.assertEqual(get_platform(), 'macosx-10.4-fat64')
 
         for arch in ('ppc', 'i386', 'x86_64', 'ppc64'):
+            _osx_support._remove_original_values(get_config_vars())
             get_config_vars()['CFLAGS'] = ('-arch %s -isysroot '
                                            '/Developer/SDKs/MacOSX10.4u.sdk  '
                                            '-fno-strict-aliasing -fno-common '
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -137,6 +137,11 @@
 - Issue 10924: Fixed mksalt() to use a RNG that is suitable for cryptographic
   purpose.
 
+- Issue #15184: Ensure consistent results of OS X configuration
+  tailoring for universal builds by factoring out common OS X-specific
+  customizations from sysconfig, distutils.sysconfig, distutils.util,
+  and distutils.unixccompiler into a new module _osx_support.
+
 C API
 -----
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list