blob: 40fc78b6984bda926434f681e21b29aa7fcdee42 [file] [log] [blame]
Ned Deilydf8aa2b2012-07-21 05:36:30 -07001"""Shared OS X support functions."""
2
3import os
4import re
5import sys
6
7__all__ = [
8 'compiler_fixup',
9 'customize_config_vars',
10 'customize_compiler',
11 'get_platform_osx',
12]
13
14# configuration variables that may contain universal build flags,
15# like "-arch" or "-isdkroot", that may need customization for
16# the user environment
17_UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS',
18 'BLDSHARED', 'LDSHARED', 'CC', 'CXX',
19 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS',
20 'PY_CORE_CFLAGS')
21
22# configuration variables that may contain compiler calls
23_COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX')
24
25# prefix added to original configuration variable names
26_INITPRE = '_OSX_SUPPORT_INITIAL_'
27
28
29def _find_executable(executable, path=None):
30 """Tries to find 'executable' in the directories listed in 'path'.
31
32 A string listing directories separated by 'os.pathsep'; defaults to
33 os.environ['PATH']. Returns the complete filename or None if not found.
34 """
35 if path is None:
36 path = os.environ['PATH']
37
38 paths = path.split(os.pathsep)
39 base, ext = os.path.splitext(executable)
40
Jesus Cea4791a242012-10-05 03:15:39 +020041 if (sys.platform == 'win32') and (ext != '.exe'):
Ned Deilydf8aa2b2012-07-21 05:36:30 -070042 executable = executable + '.exe'
43
44 if not os.path.isfile(executable):
45 for p in paths:
46 f = os.path.join(p, executable)
47 if os.path.isfile(f):
48 # the file exists, we have a shot at spawn working
49 return f
50 return None
51 else:
52 return executable
53
54
55def _read_output(commandstring):
Ezio Melotti30b9d5d2013-08-17 15:50:46 +030056 """Output from successful command execution or None"""
Ned Deilydf8aa2b2012-07-21 05:36:30 -070057 # Similar to os.popen(commandstring, "r").read(),
58 # but without actually using os.popen because that
59 # function is not usable during python bootstrap.
60 # tempfile is also not available then.
61 import contextlib
62 try:
63 import tempfile
64 fp = tempfile.NamedTemporaryFile()
Brett Cannoncd171c82013-07-04 17:43:24 -040065 except ImportError:
Ned Deilydf8aa2b2012-07-21 05:36:30 -070066 fp = open("/tmp/_osx_support.%s"%(
67 os.getpid(),), "w+b")
68
69 with contextlib.closing(fp) as fp:
70 cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
71 return fp.read().decode('utf-8').strip() if not os.system(cmd) else None
72
73
74def _find_build_tool(toolname):
75 """Find a build tool on current path or using xcrun"""
76 return (_find_executable(toolname)
77 or _read_output("/usr/bin/xcrun -find %s" % (toolname,))
78 or ''
79 )
80
81_SYSTEM_VERSION = None
82
83def _get_system_version():
84 """Return the OS X system version as a string"""
85 # Reading this plist is a documented way to get the system
86 # version (see the documentation for the Gestalt Manager)
87 # We avoid using platform.mac_ver to avoid possible bootstrap issues during
88 # the build of Python itself (distutils is used to build standard library
89 # extensions).
90
91 global _SYSTEM_VERSION
92
93 if _SYSTEM_VERSION is None:
94 _SYSTEM_VERSION = ''
95 try:
96 f = open('/System/Library/CoreServices/SystemVersion.plist')
Andrew Svetlovf7a17b42012-12-25 16:47:37 +020097 except OSError:
Ned Deilydf8aa2b2012-07-21 05:36:30 -070098 # We're on a plain darwin box, fall back to the default
99 # behaviour.
100 pass
101 else:
102 try:
103 m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
104 r'<string>(.*?)</string>', f.read())
105 finally:
106 f.close()
107 if m is not None:
108 _SYSTEM_VERSION = '.'.join(m.group(1).split('.')[:2])
109 # else: fall back to the default behaviour
110
111 return _SYSTEM_VERSION
112
113def _remove_original_values(_config_vars):
114 """Remove original unmodified values for testing"""
115 # This is needed for higher-level cross-platform tests of get_platform.
116 for k in list(_config_vars):
117 if k.startswith(_INITPRE):
118 del _config_vars[k]
119
120def _save_modified_value(_config_vars, cv, newvalue):
121 """Save modified and original unmodified value of configuration var"""
122
123 oldvalue = _config_vars.get(cv, '')
124 if (oldvalue != newvalue) and (_INITPRE + cv not in _config_vars):
125 _config_vars[_INITPRE + cv] = oldvalue
126 _config_vars[cv] = newvalue
127
128def _supports_universal_builds():
129 """Returns True if universal builds are supported on this system"""
130 # As an approximation, we assume that if we are running on 10.4 or above,
131 # then we are running with an Xcode environment that supports universal
132 # builds, in particular -isysroot and -arch arguments to the compiler. This
133 # is in support of allowing 10.4 universal builds to run on 10.3.x systems.
134
135 osx_version = _get_system_version()
136 if osx_version:
137 try:
138 osx_version = tuple(int(i) for i in osx_version.split('.'))
139 except ValueError:
140 osx_version = ''
141 return bool(osx_version >= (10, 4)) if osx_version else False
142
143
144def _find_appropriate_compiler(_config_vars):
145 """Find appropriate C compiler for extension module builds"""
146
147 # Issue #13590:
148 # The OSX location for the compiler varies between OSX
149 # (or rather Xcode) releases. With older releases (up-to 10.5)
150 # the compiler is in /usr/bin, with newer releases the compiler
151 # can only be found inside Xcode.app if the "Command Line Tools"
152 # are not installed.
153 #
154 # Futhermore, the compiler that can be used varies between
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300155 # Xcode releases. Up to Xcode 4 it was possible to use 'gcc-4.2'
Ned Deilydf8aa2b2012-07-21 05:36:30 -0700156 # as the compiler, after that 'clang' should be used because
157 # gcc-4.2 is either not present, or a copy of 'llvm-gcc' that
158 # miscompiles Python.
159
160 # skip checks if the compiler was overriden with a CC env variable
161 if 'CC' in os.environ:
162 return _config_vars
163
164 # The CC config var might contain additional arguments.
165 # Ignore them while searching.
166 cc = oldcc = _config_vars['CC'].split()[0]
167 if not _find_executable(cc):
168 # Compiler is not found on the shell search PATH.
169 # Now search for clang, first on PATH (if the Command LIne
170 # Tools have been installed in / or if the user has provided
171 # another location via CC). If not found, try using xcrun
172 # to find an uninstalled clang (within a selected Xcode).
173
174 # NOTE: Cannot use subprocess here because of bootstrap
175 # issues when building Python itself (and os.popen is
176 # implemented on top of subprocess and is therefore not
177 # usable as well)
178
179 cc = _find_build_tool('clang')
180
181 elif os.path.basename(cc).startswith('gcc'):
182 # Compiler is GCC, check if it is LLVM-GCC
183 data = _read_output("'%s' --version"
184 % (cc.replace("'", "'\"'\"'"),))
185 if 'llvm-gcc' in data:
186 # Found LLVM-GCC, fall back to clang
187 cc = _find_build_tool('clang')
188
189 if not cc:
190 raise SystemError(
191 "Cannot locate working compiler")
192
193 if cc != oldcc:
194 # Found a replacement compiler.
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300195 # Modify config vars using new compiler, if not already explicitly
Ned Deilydf8aa2b2012-07-21 05:36:30 -0700196 # overriden by an env variable, preserving additional arguments.
197 for cv in _COMPILER_CONFIG_VARS:
198 if cv in _config_vars and cv not in os.environ:
199 cv_split = _config_vars[cv].split()
200 cv_split[0] = cc if cv != 'CXX' else cc + '++'
201 _save_modified_value(_config_vars, cv, ' '.join(cv_split))
202
203 return _config_vars
204
205
206def _remove_universal_flags(_config_vars):
207 """Remove all universal build arguments from config vars"""
208
209 for cv in _UNIVERSAL_CONFIG_VARS:
210 # Do not alter a config var explicitly overriden by env var
211 if cv in _config_vars and cv not in os.environ:
212 flags = _config_vars[cv]
213 flags = re.sub('-arch\s+\w+\s', ' ', flags, re.ASCII)
214 flags = re.sub('-isysroot [^ \t]*', ' ', flags)
215 _save_modified_value(_config_vars, cv, flags)
216
217 return _config_vars
218
219
220def _remove_unsupported_archs(_config_vars):
221 """Remove any unsupported archs from config vars"""
222 # Different Xcode releases support different sets for '-arch'
223 # flags. In particular, Xcode 4.x no longer supports the
224 # PPC architectures.
225 #
226 # This code automatically removes '-arch ppc' and '-arch ppc64'
227 # when these are not supported. That makes it possible to
228 # build extensions on OSX 10.7 and later with the prebuilt
229 # 32-bit installer on the python.org website.
230
231 # skip checks if the compiler was overriden with a CC env variable
232 if 'CC' in os.environ:
233 return _config_vars
234
235 if re.search('-arch\s+ppc', _config_vars['CFLAGS']) is not None:
236 # NOTE: Cannot use subprocess here because of bootstrap
237 # issues when building Python itself
Ned Deily87adb6e2013-10-18 21:09:56 -0700238 status = os.system(
239 """echo 'int main{};' | """
240 """'%s' -c -arch ppc -x c -o /dev/null /dev/null 2>/dev/null"""
241 %(_config_vars['CC'].replace("'", "'\"'\"'"),))
242 if status:
243 # The compile failed for some reason. Because of differences
244 # across Xcode and compiler versions, there is no reliable way
245 # to be sure why it failed. Assume here it was due to lack of
246 # PPC support and remove the related '-arch' flags from each
247 # config variables not explicitly overriden by an environment
248 # variable. If the error was for some other reason, we hope the
249 # failure will show up again when trying to compile an extension
250 # module.
Ned Deilydf8aa2b2012-07-21 05:36:30 -0700251 for cv in _UNIVERSAL_CONFIG_VARS:
252 if cv in _config_vars and cv not in os.environ:
253 flags = _config_vars[cv]
254 flags = re.sub('-arch\s+ppc\w*\s', ' ', flags)
255 _save_modified_value(_config_vars, cv, flags)
256
257 return _config_vars
258
259
260def _override_all_archs(_config_vars):
261 """Allow override of all archs with ARCHFLAGS env var"""
262 # NOTE: This name was introduced by Apple in OSX 10.5 and
263 # is used by several scripting languages distributed with
264 # that OS release.
265 if 'ARCHFLAGS' in os.environ:
266 arch = os.environ['ARCHFLAGS']
267 for cv in _UNIVERSAL_CONFIG_VARS:
268 if cv in _config_vars and '-arch' in _config_vars[cv]:
269 flags = _config_vars[cv]
270 flags = re.sub('-arch\s+\w+\s', ' ', flags)
271 flags = flags + ' ' + arch
272 _save_modified_value(_config_vars, cv, flags)
273
274 return _config_vars
275
276
277def _check_for_unavailable_sdk(_config_vars):
278 """Remove references to any SDKs not available"""
279 # If we're on OSX 10.5 or later and the user tries to
280 # compile an extension using an SDK that is not present
281 # on the current machine it is better to not use an SDK
282 # than to fail. This is particularly important with
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300283 # the standalone Command Line Tools alternative to a
Ned Deilydf8aa2b2012-07-21 05:36:30 -0700284 # full-blown Xcode install since the CLT packages do not
285 # provide SDKs. If the SDK is not present, it is assumed
286 # that the header files and dev libs have been installed
287 # to /usr and /System/Library by either a standalone CLT
288 # package or the CLT component within Xcode.
289 cflags = _config_vars.get('CFLAGS', '')
290 m = re.search(r'-isysroot\s+(\S+)', cflags)
291 if m is not None:
292 sdk = m.group(1)
293 if not os.path.exists(sdk):
294 for cv in _UNIVERSAL_CONFIG_VARS:
295 # Do not alter a config var explicitly overriden by env var
296 if cv in _config_vars and cv not in os.environ:
297 flags = _config_vars[cv]
298 flags = re.sub(r'-isysroot\s+\S+(?:\s|$)', ' ', flags)
299 _save_modified_value(_config_vars, cv, flags)
300
301 return _config_vars
302
303
304def compiler_fixup(compiler_so, cc_args):
305 """
306 This function will strip '-isysroot PATH' and '-arch ARCH' from the
307 compile flags if the user has specified one them in extra_compile_flags.
308
309 This is needed because '-arch ARCH' adds another architecture to the
310 build, without a way to remove an architecture. Furthermore GCC will
311 barf if multiple '-isysroot' arguments are present.
312 """
313 stripArch = stripSysroot = False
314
315 compiler_so = list(compiler_so)
316
317 if not _supports_universal_builds():
318 # OSX before 10.4.0, these don't support -arch and -isysroot at
319 # all.
320 stripArch = stripSysroot = True
321 else:
322 stripArch = '-arch' in cc_args
323 stripSysroot = '-isysroot' in cc_args
324
325 if stripArch or 'ARCHFLAGS' in os.environ:
326 while True:
327 try:
328 index = compiler_so.index('-arch')
329 # Strip this argument and the next one:
330 del compiler_so[index:index+2]
331 except ValueError:
332 break
333
334 if 'ARCHFLAGS' in os.environ and not stripArch:
335 # User specified different -arch flags in the environ,
336 # see also distutils.sysconfig
337 compiler_so = compiler_so + os.environ['ARCHFLAGS'].split()
338
339 if stripSysroot:
340 while True:
341 try:
342 index = compiler_so.index('-isysroot')
343 # Strip this argument and the next one:
344 del compiler_so[index:index+2]
345 except ValueError:
346 break
347
348 # Check if the SDK that is used during compilation actually exists,
349 # the universal build requires the usage of a universal SDK and not all
350 # users have that installed by default.
351 sysroot = None
352 if '-isysroot' in cc_args:
353 idx = cc_args.index('-isysroot')
354 sysroot = cc_args[idx+1]
355 elif '-isysroot' in compiler_so:
356 idx = compiler_so.index('-isysroot')
357 sysroot = compiler_so[idx+1]
358
359 if sysroot and not os.path.isdir(sysroot):
360 from distutils import log
361 log.warn("Compiling with an SDK that doesn't seem to exist: %s",
362 sysroot)
363 log.warn("Please check your Xcode installation")
364
365 return compiler_so
366
367
368def customize_config_vars(_config_vars):
369 """Customize Python build configuration variables.
370
371 Called internally from sysconfig with a mutable mapping
372 containing name/value pairs parsed from the configured
373 makefile used to build this interpreter. Returns
374 the mapping updated as needed to reflect the environment
375 in which the interpreter is running; in the case of
376 a Python from a binary installer, the installed
377 environment may be very different from the build
378 environment, i.e. different OS levels, different
379 built tools, different available CPU architectures.
380
381 This customization is performed whenever
382 distutils.sysconfig.get_config_vars() is first
383 called. It may be used in environments where no
384 compilers are present, i.e. when installing pure
385 Python dists. Customization of compiler paths
386 and detection of unavailable archs is deferred
Ezio Melotti30b9d5d2013-08-17 15:50:46 +0300387 until the first extension module build is
Ned Deilydf8aa2b2012-07-21 05:36:30 -0700388 requested (in distutils.sysconfig.customize_compiler).
389
390 Currently called from distutils.sysconfig
391 """
392
393 if not _supports_universal_builds():
394 # On Mac OS X before 10.4, check if -arch and -isysroot
395 # are in CFLAGS or LDFLAGS and remove them if they are.
396 # This is needed when building extensions on a 10.3 system
397 # using a universal build of python.
398 _remove_universal_flags(_config_vars)
399
400 # Allow user to override all archs with ARCHFLAGS env var
401 _override_all_archs(_config_vars)
402
403 # Remove references to sdks that are not found
404 _check_for_unavailable_sdk(_config_vars)
405
406 return _config_vars
407
408
409def customize_compiler(_config_vars):
410 """Customize compiler path and configuration variables.
411
412 This customization is performed when the first
413 extension module build is requested
414 in distutils.sysconfig.customize_compiler).
415 """
416
417 # Find a compiler to use for extension module builds
418 _find_appropriate_compiler(_config_vars)
419
420 # Remove ppc arch flags if not supported here
421 _remove_unsupported_archs(_config_vars)
422
423 # Allow user to override all archs with ARCHFLAGS env var
424 _override_all_archs(_config_vars)
425
426 return _config_vars
427
428
429def get_platform_osx(_config_vars, osname, release, machine):
430 """Filter values for get_platform()"""
431 # called from get_platform() in sysconfig and distutils.util
432 #
433 # For our purposes, we'll assume that the system version from
434 # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
435 # to. This makes the compatibility story a bit more sane because the
436 # machine is going to compile and link as if it were
437 # MACOSX_DEPLOYMENT_TARGET.
438
439 macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
440 macrelease = _get_system_version() or macver
441 macver = macver or macrelease
442
443 if macver:
444 release = macver
445 osname = "macosx"
446
447 # Use the original CFLAGS value, if available, so that we
448 # return the same machine type for the platform string.
449 # Otherwise, distutils may consider this a cross-compiling
450 # case and disallow installs.
451 cflags = _config_vars.get(_INITPRE+'CFLAGS',
452 _config_vars.get('CFLAGS', ''))
453 if ((macrelease + '.') >= '10.4.' and
454 '-arch' in cflags.strip()):
455 # The universal build will build fat binaries, but not on
456 # systems before 10.4
457
458 machine = 'fat'
459
460 archs = re.findall('-arch\s+(\S+)', cflags)
461 archs = tuple(sorted(set(archs)))
462
463 if len(archs) == 1:
464 machine = archs[0]
465 elif archs == ('i386', 'ppc'):
466 machine = 'fat'
467 elif archs == ('i386', 'x86_64'):
468 machine = 'intel'
469 elif archs == ('i386', 'ppc', 'x86_64'):
470 machine = 'fat3'
471 elif archs == ('ppc64', 'x86_64'):
472 machine = 'fat64'
473 elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
474 machine = 'universal'
475 else:
476 raise ValueError(
477 "Don't know machine value for archs=%r" % (archs,))
478
479 elif machine == 'i386':
480 # On OSX the machine type returned by uname is always the
481 # 32-bit variant, even if the executable architecture is
482 # the 64-bit variant
483 if sys.maxsize >= 2**32:
484 machine = 'x86_64'
485
486 elif machine in ('PowerPC', 'Power_Macintosh'):
487 # Pick a sane name for the PPC architecture.
488 # See 'i386' case
489 if sys.maxsize >= 2**32:
490 machine = 'ppc64'
491 else:
492 machine = 'ppc'
493
494 return (osname, release, machine)