Skip to content

Commit 18fae3f

Browse files
committed
Issue #13590: OS X Xcode 4 - improve support for universal extension modules
In particular, fix extension module build failures when trying to use 32-bit-only installer Pythons on systems with Xcode 4 (currently OS X 10.8, 10.7, and optionally 10.6). * Backport 3.3.0 fixes to 2.7 branch (for release in 2.7.4) * Since Xcode 4 removes ppc support, extension module builds now check for ppc compiler support and by default remove ppc and ppc64 archs when they are not available. * Extension module builds now revert to using system installed headers and libs (/usr and /System/Library) if the SDK used to build the interpreter is not installed or has moved. * Try to avoid building extension modules with deprecated and problematic Apple llvm-gcc compiler. If original compiler is not available, use clang instead by default.
1 parent 77cd8aa commit 18fae3f

File tree

9 files changed

+859
-384
lines changed

9 files changed

+859
-384
lines changed

Lib/_osx_support.py

Lines changed: 488 additions & 0 deletions
Large diffs are not rendered by default.

Lib/distutils/sysconfig.py

Lines changed: 21 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
141141
"I don't know where Python installs its library "
142142
"on platform '%s'" % os.name)
143143

144-
_USE_CLANG = None
144+
145145

146146
def customize_compiler(compiler):
147147
"""Do any platform-specific customization of a CCompiler instance.
@@ -150,43 +150,29 @@ def customize_compiler(compiler):
150150
varies across Unices and is stored in Python's Makefile.
151151
"""
152152
if compiler.compiler_type == "unix":
153+
if sys.platform == "darwin":
154+
# Perform first-time customization of compiler-related
155+
# config vars on OS X now that we know we need a compiler.
156+
# This is primarily to support Pythons from binary
157+
# installers. The kind and paths to build tools on
158+
# the user system may vary significantly from the system
159+
# that Python itself was built on. Also the user OS
160+
# version and build tools may not support the same set
161+
# of CPU architectures for universal builds.
162+
global _config_vars
163+
if not _config_vars.get('CUSTOMIZED_OSX_COMPILER', ''):
164+
import _osx_support
165+
_osx_support.customize_compiler(_config_vars)
166+
_config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
167+
153168
(cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \
154169
get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS',
155170
'CCSHARED', 'LDSHARED', 'SO', 'AR',
156171
'ARFLAGS')
157172

158173
newcc = None
159174
if 'CC' in os.environ:
160-
newcc = os.environ['CC']
161-
elif sys.platform == 'darwin' and cc == 'gcc-4.2':
162-
# Issue #13590:
163-
# Since Apple removed gcc-4.2 in Xcode 4.2, we can no
164-
# longer assume it is available for extension module builds.
165-
# If Python was built with gcc-4.2, check first to see if
166-
# it is available on this system; if not, try to use clang
167-
# instead unless the caller explicitly set CC.
168-
global _USE_CLANG
169-
if _USE_CLANG is None:
170-
from distutils import log
171-
from subprocess import Popen, PIPE
172-
p = Popen("! type gcc-4.2 && type clang && exit 2",
173-
shell=True, stdout=PIPE, stderr=PIPE)
174-
p.wait()
175-
if p.returncode == 2:
176-
_USE_CLANG = True
177-
log.warn("gcc-4.2 not found, using clang instead")
178-
else:
179-
_USE_CLANG = False
180-
if _USE_CLANG:
181-
newcc = 'clang'
182-
if newcc:
183-
# On OS X, if CC is overridden, use that as the default
184-
# command for LDSHARED as well
185-
if (sys.platform == 'darwin'
186-
and 'LDSHARED' not in os.environ
187-
and ldshared.startswith(cc)):
188-
ldshared = newcc + ldshared[len(cc):]
189-
cc = newcc
175+
cc = os.environ['CC']
190176
if 'CXX' in os.environ:
191177
cxx = os.environ['CXX']
192178
if 'LDSHARED' in os.environ:
@@ -518,66 +504,11 @@ def get_config_vars(*args):
518504
_config_vars['prefix'] = PREFIX
519505
_config_vars['exec_prefix'] = EXEC_PREFIX
520506

507+
# OS X platforms require special customization to handle
508+
# multi-architecture, multi-os-version installers
521509
if sys.platform == 'darwin':
522-
kernel_version = os.uname()[2] # Kernel version (8.4.3)
523-
major_version = int(kernel_version.split('.')[0])
524-
525-
if major_version < 8:
526-
# On Mac OS X before 10.4, check if -arch and -isysroot
527-
# are in CFLAGS or LDFLAGS and remove them if they are.
528-
# This is needed when building extensions on a 10.3 system
529-
# using a universal build of python.
530-
for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
531-
# a number of derived variables. These need to be
532-
# patched up as well.
533-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
534-
flags = _config_vars[key]
535-
flags = re.sub('-arch\s+\w+\s', ' ', flags)
536-
flags = re.sub('-isysroot [^ \t]*', ' ', flags)
537-
_config_vars[key] = flags
538-
539-
else:
540-
541-
# Allow the user to override the architecture flags using
542-
# an environment variable.
543-
# NOTE: This name was introduced by Apple in OSX 10.5 and
544-
# is used by several scripting languages distributed with
545-
# that OS release.
546-
547-
if 'ARCHFLAGS' in os.environ:
548-
arch = os.environ['ARCHFLAGS']
549-
for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
550-
# a number of derived variables. These need to be
551-
# patched up as well.
552-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
553-
554-
flags = _config_vars[key]
555-
flags = re.sub('-arch\s+\w+\s', ' ', flags)
556-
flags = flags + ' ' + arch
557-
_config_vars[key] = flags
558-
559-
# If we're on OSX 10.5 or later and the user tries to
560-
# compiles an extension using an SDK that is not present
561-
# on the current machine it is better to not use an SDK
562-
# than to fail.
563-
#
564-
# The major usecase for this is users using a Python.org
565-
# binary installer on OSX 10.6: that installer uses
566-
# the 10.4u SDK, but that SDK is not installed by default
567-
# when you install Xcode.
568-
#
569-
m = re.search('-isysroot\s+(\S+)', _config_vars['CFLAGS'])
570-
if m is not None:
571-
sdk = m.group(1)
572-
if not os.path.exists(sdk):
573-
for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
574-
# a number of derived variables. These need to be
575-
# patched up as well.
576-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
577-
578-
flags = _config_vars[key]
579-
flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
580-
_config_vars[key] = flags
510+
import _osx_support
511+
_osx_support.customize_config_vars(_config_vars)
581512

582513
if args:
583514
vals = []

Lib/distutils/tests/test_sysconfig.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ def test_parse_makefile_literal_dollar(self):
7272
'OTHER': 'foo'})
7373

7474

75+
def test_sysconfig_module(self):
76+
import sysconfig as global_sysconfig
77+
self.assertEqual(global_sysconfig.get_config_var('CFLAGS'), sysconfig.get_config_var('CFLAGS'))
78+
self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'), sysconfig.get_config_var('LDFLAGS'))
79+
80+
@unittest.skipIf(sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'),'compiler flags customized')
81+
def test_sysconfig_compiler_vars(self):
82+
# On OS X, binary installers support extension module building on
83+
# various levels of the operating system with differing Xcode
84+
# configurations. This requires customization of some of the
85+
# compiler configuration directives to suit the environment on
86+
# the installed machine. Some of these customizations may require
87+
# running external programs and, so, are deferred until needed by
88+
# the first extension module build. With Python 3.3, only
89+
# the Distutils version of sysconfig is used for extension module
90+
# builds, which happens earlier in the Distutils tests. This may
91+
# cause the following tests to fail since no tests have caused
92+
# the global version of sysconfig to call the customization yet.
93+
# The solution for now is to simply skip this test in this case.
94+
# The longer-term solution is to only have one version of sysconfig.
95+
96+
import sysconfig as global_sysconfig
97+
if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'):
98+
return
99+
self.assertEqual(global_sysconfig.get_config_var('LDSHARED'), sysconfig.get_config_var('LDSHARED'))
100+
self.assertEqual(global_sysconfig.get_config_var('CC'), sysconfig.get_config_var('CC'))
101+
102+
103+
75104
def test_suite():
76105
suite = unittest.TestSuite()
77106
suite.addTest(unittest.makeSuite(SysconfigTestCase))

Lib/distutils/unixccompiler.py

Lines changed: 6 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
DistutilsExecError, CompileError, LibError, LinkError
2727
from distutils import log
2828

29+
if sys.platform == 'darwin':
30+
import _osx_support
31+
2932
# XXX Things not currently handled:
3033
# * optimization/debug/warning flags; we just use whatever's in Python's
3134
# Makefile and live with it. Is this adequate? If not, we might
@@ -41,68 +44,6 @@
4144
# should just happily stuff them into the preprocessor/compiler/linker
4245
# options and carry on.
4346

44-
def _darwin_compiler_fixup(compiler_so, cc_args):
45-
"""
46-
This function will strip '-isysroot PATH' and '-arch ARCH' from the
47-
compile flags if the user has specified one them in extra_compile_flags.
48-
49-
This is needed because '-arch ARCH' adds another architecture to the
50-
build, without a way to remove an architecture. Furthermore GCC will
51-
barf if multiple '-isysroot' arguments are present.
52-
"""
53-
stripArch = stripSysroot = 0
54-
55-
compiler_so = list(compiler_so)
56-
kernel_version = os.uname()[2] # 8.4.3
57-
major_version = int(kernel_version.split('.')[0])
58-
59-
if major_version < 8:
60-
# OSX before 10.4.0, these don't support -arch and -isysroot at
61-
# all.
62-
stripArch = stripSysroot = True
63-
else:
64-
stripArch = '-arch' in cc_args
65-
stripSysroot = '-isysroot' in cc_args
66-
67-
if stripArch or 'ARCHFLAGS' in os.environ:
68-
while 1:
69-
try:
70-
index = compiler_so.index('-arch')
71-
# Strip this argument and the next one:
72-
del compiler_so[index:index+2]
73-
except ValueError:
74-
break
75-
76-
if 'ARCHFLAGS' in os.environ and not stripArch:
77-
# User specified different -arch flags in the environ,
78-
# see also distutils.sysconfig
79-
compiler_so = compiler_so + os.environ['ARCHFLAGS'].split()
80-
81-
if stripSysroot:
82-
try:
83-
index = compiler_so.index('-isysroot')
84-
# Strip this argument and the next one:
85-
del compiler_so[index:index+2]
86-
except ValueError:
87-
pass
88-
89-
# Check if the SDK that is used during compilation actually exists,
90-
# the universal build requires the usage of a universal SDK and not all
91-
# users have that installed by default.
92-
sysroot = None
93-
if '-isysroot' in cc_args:
94-
idx = cc_args.index('-isysroot')
95-
sysroot = cc_args[idx+1]
96-
elif '-isysroot' in compiler_so:
97-
idx = compiler_so.index('-isysroot')
98-
sysroot = compiler_so[idx+1]
99-
100-
if sysroot and not os.path.isdir(sysroot):
101-
log.warn("Compiling with an SDK that doesn't seem to exist: %s",
102-
sysroot)
103-
log.warn("Please check your Xcode installation")
104-
105-
return compiler_so
10647

10748
class UnixCCompiler(CCompiler):
10849

@@ -172,7 +113,8 @@ def preprocess(self, source,
172113
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
173114
compiler_so = self.compiler_so
174115
if sys.platform == 'darwin':
175-
compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs)
116+
compiler_so = _osx_support.compiler_fixup(compiler_so,
117+
cc_args + extra_postargs)
176118
try:
177119
self.spawn(compiler_so + cc_args + [src, '-o', obj] +
178120
extra_postargs)
@@ -251,7 +193,7 @@ def link(self, target_desc, objects,
251193
linker[i] = self.compiler_cxx[i]
252194

253195
if sys.platform == 'darwin':
254-
linker = _darwin_compiler_fixup(linker, ld_args)
196+
linker = _osx_support.compiler_fixup(linker, ld_args)
255197

256198
self.spawn(linker + ld_args)
257199
except DistutilsExecError, msg:

Lib/distutils/util.py

Lines changed: 4 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -93,94 +93,10 @@ def get_platform ():
9393
if m:
9494
release = m.group()
9595
elif osname[:6] == "darwin":
96-
#
97-
# For our purposes, we'll assume that the system version from
98-
# distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
99-
# to. This makes the compatibility story a bit more sane because the
100-
# machine is going to compile and link as if it were
101-
# MACOSX_DEPLOYMENT_TARGET.
102-
from distutils.sysconfig import get_config_vars
103-
cfgvars = get_config_vars()
104-
105-
macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
106-
107-
if 1:
108-
# Always calculate the release of the running machine,
109-
# needed to determine if we can build fat binaries or not.
110-
111-
macrelease = macver
112-
# Get the system version. Reading this plist is a documented
113-
# way to get the system version (see the documentation for
114-
# the Gestalt Manager)
115-
try:
116-
f = open('/System/Library/CoreServices/SystemVersion.plist')
117-
except IOError:
118-
# We're on a plain darwin box, fall back to the default
119-
# behaviour.
120-
pass
121-
else:
122-
try:
123-
m = re.search(
124-
r'<key>ProductUserVisibleVersion</key>\s*' +
125-
r'<string>(.*?)</string>', f.read())
126-
if m is not None:
127-
macrelease = '.'.join(m.group(1).split('.')[:2])
128-
# else: fall back to the default behaviour
129-
finally:
130-
f.close()
131-
132-
if not macver:
133-
macver = macrelease
134-
135-
if macver:
136-
from distutils.sysconfig import get_config_vars
137-
release = macver
138-
osname = "macosx"
139-
140-
if (macrelease + '.') >= '10.4.' and \
141-
'-arch' in get_config_vars().get('CFLAGS', '').strip():
142-
# The universal build will build fat binaries, but not on
143-
# systems before 10.4
144-
#
145-
# Try to detect 4-way universal builds, those have machine-type
146-
# 'universal' instead of 'fat'.
147-
148-
machine = 'fat'
149-
cflags = get_config_vars().get('CFLAGS')
150-
151-
archs = re.findall('-arch\s+(\S+)', cflags)
152-
archs = tuple(sorted(set(archs)))
153-
154-
if len(archs) == 1:
155-
machine = archs[0]
156-
elif archs == ('i386', 'ppc'):
157-
machine = 'fat'
158-
elif archs == ('i386', 'x86_64'):
159-
machine = 'intel'
160-
elif archs == ('i386', 'ppc', 'x86_64'):
161-
machine = 'fat3'
162-
elif archs == ('ppc64', 'x86_64'):
163-
machine = 'fat64'
164-
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
165-
machine = 'universal'
166-
else:
167-
raise ValueError(
168-
"Don't know machine value for archs=%r"%(archs,))
169-
170-
elif machine == 'i386':
171-
# On OSX the machine type returned by uname is always the
172-
# 32-bit variant, even if the executable architecture is
173-
# the 64-bit variant
174-
if sys.maxint >= 2**32:
175-
machine = 'x86_64'
176-
177-
elif machine in ('PowerPC', 'Power_Macintosh'):
178-
# Pick a sane name for the PPC architecture.
179-
machine = 'ppc'
180-
181-
# See 'i386' case
182-
if sys.maxint >= 2**32:
183-
machine = 'ppc64'
96+
import _osx_support, distutils.sysconfig
97+
osname, release, machine = _osx_support.get_platform_osx(
98+
distutils.sysconfig.get_config_vars(),
99+
osname, release, machine)
184100

185101
return "%s-%s-%s" % (osname, release, machine)
186102

0 commit comments

Comments
 (0)