Skip to content

Commit df8aa2b

Browse files
committed
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.
1 parent 0fd1062 commit df8aa2b

File tree

9 files changed

+818
-467
lines changed

9 files changed

+818
-467
lines changed

Lib/_osx_support.py

+488
Large diffs are not rendered by default.

Lib/distutils/sysconfig.py

+20-170
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ def customize_compiler(compiler):
172172
varies across Unices and is stored in Python's Makefile.
173173
"""
174174
if compiler.compiler_type == "unix":
175+
if sys.platform == "darwin":
176+
# Perform first-time customization of compiler-related
177+
# config vars on OS X now that we know we need a compiler.
178+
# This is primarily to support Pythons from binary
179+
# installers. The kind and paths to build tools on
180+
# the user system may vary significantly from the system
181+
# that Python itself was built on. Also the user OS
182+
# version and build tools may not support the same set
183+
# of CPU architectures for universal builds.
184+
global _config_vars
185+
if not _config_vars.get('CUSTOMIZED_OSX_COMPILER', ''):
186+
import _osx_support
187+
_osx_support.customize_compiler(_config_vars)
188+
_config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
189+
175190
(cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \
176191
get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS',
177192
'CCSHARED', 'LDSHARED', 'SO', 'AR', 'ARFLAGS')
@@ -494,35 +509,12 @@ def _init_os2():
494509
_config_vars = g
495510

496511

497-
def _read_output(commandstring):
498-
"""
499-
Returns os.popen(commandstring, "r").read(), but
500-
without actually using os.popen because that
501-
function is not usable during python bootstrap
502-
"""
503-
# NOTE: tempfile is also not useable during
504-
# bootstrap
505-
import contextlib
506-
try:
507-
import tempfile
508-
fp = tempfile.NamedTemporaryFile()
509-
except ImportError:
510-
fp = open("/tmp/distutils.%s"%(
511-
os.getpid(),), "w+b")
512-
513-
with contextlib.closing(fp) as fp:
514-
cmd = "%s >'%s'"%(commandstring, fp.name)
515-
os.system(cmd)
516-
data = fp.read()
517-
518-
return data.decode('utf-8')
519-
520512
def get_config_vars(*args):
521513
"""With no arguments, return a dictionary of all configuration
522514
variables relevant for the current platform. Generally this includes
523515
everything needed to build extensions and install both pure modules and
524516
extensions. On Unix, this means every variable defined in Python's
525-
installed Makefile; on Windows and Mac OS it's a much smaller set.
517+
installed Makefile; on Windows it's a much smaller set.
526518
527519
With arguments, return a list of values that result from looking up
528520
each argument in the configuration variable dictionary.
@@ -555,153 +547,11 @@ def get_config_vars(*args):
555547
srcdir = os.path.join(base, _config_vars['srcdir'])
556548
_config_vars['srcdir'] = os.path.normpath(srcdir)
557549

550+
# OS X platforms require special customization to handle
551+
# multi-architecture, multi-os-version installers
558552
if sys.platform == 'darwin':
559-
from distutils.spawn import find_executable
560-
561-
kernel_version = os.uname()[2] # Kernel version (8.4.3)
562-
major_version = int(kernel_version.split('.')[0])
563-
564-
# Issue #13590:
565-
# The OSX location for the compiler varies between OSX
566-
# (or rather Xcode) releases. With older releases (up-to 10.5)
567-
# the compiler is in /usr/bin, with newer releases the compiler
568-
# can only be found inside Xcode.app if the "Command Line Tools"
569-
# are not installed.
570-
#
571-
# Futhermore, the compiler that can be used varies between
572-
# Xcode releases. Upto Xcode 4 it was possible to use 'gcc-4.2'
573-
# as the compiler, after that 'clang' should be used because
574-
# gcc-4.2 is either not present, or a copy of 'llvm-gcc' that
575-
# miscompiles Python.
576-
577-
# skip checks if the compiler was overriden with a CC env variable
578-
if 'CC' not in os.environ:
579-
cc = oldcc = _config_vars['CC']
580-
if not find_executable(cc):
581-
# Compiler is not found on the shell search PATH.
582-
# Now search for clang, first on PATH (if the Command LIne
583-
# Tools have been installed in / or if the user has provided
584-
# another location via CC). If not found, try using xcrun
585-
# to find an uninstalled clang (within a selected Xcode).
586-
587-
# NOTE: Cannot use subprocess here because of bootstrap
588-
# issues when building Python itself (and os.popen is
589-
# implemented on top of subprocess and is therefore not
590-
# usable as well)
591-
592-
data = (find_executable('clang') or
593-
_read_output(
594-
"/usr/bin/xcrun -find clang 2>/dev/null").strip())
595-
if not data:
596-
raise DistutilsPlatformError(
597-
"Cannot locate working compiler")
598-
599-
_config_vars['CC'] = cc = data
600-
_config_vars['CXX'] = cc + '++'
601-
602-
elif os.path.basename(cc).startswith('gcc'):
603-
# Compiler is GCC, check if it is LLVM-GCC
604-
data = _read_output("'%s' --version 2>/dev/null"
605-
% (cc.replace("'", "'\"'\"'"),))
606-
if 'llvm-gcc' in data:
607-
# Found LLVM-GCC, fall back to clang
608-
data = (find_executable('clang') or
609-
_read_output(
610-
"/usr/bin/xcrun -find clang 2>/dev/null").strip())
611-
if find_executable(data):
612-
_config_vars['CC'] = cc = data
613-
_config_vars['CXX'] = cc + '++'
614-
615-
if (cc != oldcc
616-
and 'LDSHARED' in _config_vars
617-
and 'LDSHARED' not in os.environ):
618-
# modify LDSHARED if we modified CC
619-
ldshared = _config_vars['LDSHARED']
620-
if ldshared.startswith(oldcc):
621-
_config_vars['LDSHARED'] = cc + ldshared[len(oldcc):]
622-
623-
if major_version < 8:
624-
# On Mac OS X before 10.4, check if -arch and -isysroot
625-
# are in CFLAGS or LDFLAGS and remove them if they are.
626-
# This is needed when building extensions on a 10.3 system
627-
# using a universal build of python.
628-
for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
629-
# a number of derived variables. These need to be
630-
# patched up as well.
631-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
632-
flags = _config_vars[key]
633-
flags = re.sub('-arch\s+\w+\s', ' ', flags, re.ASCII)
634-
flags = re.sub('-isysroot [^ \t]*', ' ', flags)
635-
_config_vars[key] = flags
636-
637-
else:
638-
# Different Xcode releases support different sets for '-arch'
639-
# flags. In particular, Xcode 4.x no longer supports the
640-
# PPC architectures.
641-
#
642-
# This code automatically removes '-arch ppc' and '-arch ppc64'
643-
# when these are not supported. That makes it possible to
644-
# build extensions on OSX 10.7 and later with the prebuilt
645-
# 32-bit installer on the python.org website.
646-
flags = _config_vars['CFLAGS']
647-
if re.search('-arch\s+ppc', flags) is not None:
648-
# NOTE: Cannot use subprocess here because of bootstrap
649-
# issues when building Python itself
650-
status = os.system("'%s' -arch ppc -x c /dev/null 2>/dev/null"%(
651-
_config_vars['CC'].replace("'", "'\"'\"'"),))
652-
653-
if status != 0:
654-
# Compiler doesn't support PPC, remove the related
655-
# '-arch' flags.
656-
for key in ('LDFLAGS', 'BASECFLAGS',
657-
# a number of derived variables. These need to be
658-
# patched up as well.
659-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED', 'LDSHARED'):
660-
661-
flags = _config_vars[key]
662-
flags = re.sub('-arch\s+ppc\w*\s', ' ', flags)
663-
_config_vars[key] = flags
664-
665-
666-
# Allow the user to override the architecture flags using
667-
# an environment variable.
668-
# NOTE: This name was introduced by Apple in OSX 10.5 and
669-
# is used by several scripting languages distributed with
670-
# that OS release.
671-
if 'ARCHFLAGS' in os.environ:
672-
arch = os.environ['ARCHFLAGS']
673-
for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
674-
# a number of derived variables. These need to be
675-
# patched up as well.
676-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
677-
678-
flags = _config_vars[key]
679-
flags = re.sub('-arch\s+\w+\s', ' ', flags)
680-
flags = flags + ' ' + arch
681-
_config_vars[key] = flags
682-
683-
# If we're on OSX 10.5 or later and the user tries to
684-
# compiles an extension using an SDK that is not present
685-
# on the current machine it is better to not use an SDK
686-
# than to fail.
687-
#
688-
# The major usecase for this is users using a Python.org
689-
# binary installer on OSX 10.6: that installer uses
690-
# the 10.4u SDK, but that SDK is not installed by default
691-
# when you install Xcode.
692-
#
693-
m = re.search('-isysroot\s+(\S+)', _config_vars['CFLAGS'])
694-
if m is not None:
695-
sdk = m.group(1)
696-
if not os.path.exists(sdk):
697-
for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED',
698-
# a number of derived variables. These need to be
699-
# patched up as well.
700-
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
701-
702-
flags = _config_vars[key]
703-
flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
704-
_config_vars[key] = flags
553+
import _osx_support
554+
_osx_support.customize_config_vars(_config_vars)
705555

706556
if args:
707557
vals = []

Lib/distutils/tests/test_util.py

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from distutils.sysconfig import get_config_vars
1414
from distutils import sysconfig
1515
from distutils.tests import support
16+
import _osx_support
1617

1718
class UtilTestCase(support.EnvironGuard, unittest.TestCase):
1819

@@ -92,6 +93,7 @@ def test_get_platform(self):
9293
('Darwin Kernel Version 8.11.1: '
9394
'Wed Oct 10 18:23:28 PDT 2007; '
9495
'root:xnu-792.25.20~1/RELEASE_I386'), 'i386'))
96+
_osx_support._remove_original_values(get_config_vars())
9597
get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
9698

9799
get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g '
@@ -105,6 +107,7 @@ def test_get_platform(self):
105107
sys.maxsize = cursize
106108

107109
# macbook with fat binaries (fat, universal or fat64)
110+
_osx_support._remove_original_values(get_config_vars())
108111
get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4'
109112
get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot '
110113
'/Developer/SDKs/MacOSX10.4u.sdk '
@@ -113,29 +116,34 @@ def test_get_platform(self):
113116

114117
self.assertEqual(get_platform(), 'macosx-10.4-fat')
115118

119+
_osx_support._remove_original_values(get_config_vars())
116120
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.1'
117121
self.assertEqual(get_platform(), 'macosx-10.4-fat')
118122

119123

124+
_osx_support._remove_original_values(get_config_vars())
120125
get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot '
121126
'/Developer/SDKs/MacOSX10.4u.sdk '
122127
'-fno-strict-aliasing -fno-common '
123128
'-dynamic -DNDEBUG -g -O3')
124129

125130
self.assertEqual(get_platform(), 'macosx-10.4-intel')
126131

132+
_osx_support._remove_original_values(get_config_vars())
127133
get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot '
128134
'/Developer/SDKs/MacOSX10.4u.sdk '
129135
'-fno-strict-aliasing -fno-common '
130136
'-dynamic -DNDEBUG -g -O3')
131137
self.assertEqual(get_platform(), 'macosx-10.4-fat3')
132138

139+
_osx_support._remove_original_values(get_config_vars())
133140
get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot '
134141
'/Developer/SDKs/MacOSX10.4u.sdk '
135142
'-fno-strict-aliasing -fno-common '
136143
'-dynamic -DNDEBUG -g -O3')
137144
self.assertEqual(get_platform(), 'macosx-10.4-universal')
138145

146+
_osx_support._remove_original_values(get_config_vars())
139147
get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot '
140148
'/Developer/SDKs/MacOSX10.4u.sdk '
141149
'-fno-strict-aliasing -fno-common '
@@ -144,6 +152,7 @@ def test_get_platform(self):
144152
self.assertEqual(get_platform(), 'macosx-10.4-fat64')
145153

146154
for arch in ('ppc', 'i386', 'x86_64', 'ppc64'):
155+
_osx_support._remove_original_values(get_config_vars())
147156
get_config_vars()['CFLAGS'] = ('-arch %s -isysroot '
148157
'/Developer/SDKs/MacOSX10.4u.sdk '
149158
'-fno-strict-aliasing -fno-common '

Lib/distutils/unixccompiler.py

+6-64
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
DistutilsExecError, CompileError, LibError, LinkError
2424
from distutils import log
2525

26+
if sys.platform == 'darwin':
27+
import _osx_support
28+
2629
# XXX Things not currently handled:
2730
# * optimization/debug/warning flags; we just use whatever's in Python's
2831
# Makefile and live with it. Is this adequate? If not, we might
@@ -38,68 +41,6 @@
3841
# should just happily stuff them into the preprocessor/compiler/linker
3942
# options and carry on.
4043

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

10445
class UnixCCompiler(CCompiler):
10546

@@ -168,7 +109,8 @@ def preprocess(self, source, output_file=None, macros=None,
168109
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
169110
compiler_so = self.compiler_so
170111
if sys.platform == 'darwin':
171-
compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs)
112+
compiler_so = _osx_support.compiler_fixup(compiler_so,
113+
cc_args + extra_postargs)
172114
try:
173115
self.spawn(compiler_so + cc_args + [src, '-o', obj] +
174116
extra_postargs)
@@ -247,7 +189,7 @@ def link(self, target_desc, objects,
247189
linker[i] = self.compiler_cxx[i]
248190

249191
if sys.platform == 'darwin':
250-
linker = _darwin_compiler_fixup(linker, ld_args)
192+
linker = _osx_support.compiler_fixup(linker, ld_args)
251193

252194
self.spawn(linker + ld_args)
253195
except DistutilsExecError as msg:

0 commit comments

Comments
 (0)