ยปCore Development>Code coverage>Lib/_osx_support.py

Python code coverage for Lib/_osx_support.py

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