From 5afa6f76b213f2cfd7529d2fb476fbf97aa5bd87 Mon Sep 17 00:00:00 2001 From: CPython developers <> Date: Mon, 18 Jul 2022 02:32:29 +0900 Subject: [PATCH] Update _osx_support from CPython 3.10.5 --- Lib/_osx_support.py | 132 +++++-- Lib/test/test__osx_support.py | 658 +++++++++++++++++----------------- 2 files changed, 429 insertions(+), 361 deletions(-) diff --git a/Lib/_osx_support.py b/Lib/_osx_support.py index db6674ea2..aa66c8b9f 100644 --- a/Lib/_osx_support.py +++ b/Lib/_osx_support.py @@ -52,7 +52,7 @@ def _find_executable(executable, path=None): return executable -def _read_output(commandstring): +def _read_output(commandstring, capture_stderr=False): """Output from successful command execution or None""" # Similar to os.popen(commandstring, "r").read(), # but without actually using os.popen because that @@ -67,7 +67,10 @@ def _read_output(commandstring): os.getpid(),), "w+b") with contextlib.closing(fp) as fp: - cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) + if capture_stderr: + cmd = "%s >'%s' 2>&1" % (commandstring, fp.name) + else: + cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) return fp.read().decode('utf-8').strip() if not os.system(cmd) else None @@ -93,7 +96,7 @@ def _get_system_version(): if _SYSTEM_VERSION is None: _SYSTEM_VERSION = '' try: - f = open('/System/Library/CoreServices/SystemVersion.plist') + f = open('/System/Library/CoreServices/SystemVersion.plist', encoding="utf-8") except OSError: # We're on a plain darwin box, fall back to the default # behaviour. @@ -110,6 +113,26 @@ def _get_system_version(): return _SYSTEM_VERSION +_SYSTEM_VERSION_TUPLE = None +def _get_system_version_tuple(): + """ + Return the macOS system version as a tuple + + The return value is safe to use to compare + two version numbers. + """ + global _SYSTEM_VERSION_TUPLE + if _SYSTEM_VERSION_TUPLE is None: + osx_version = _get_system_version() + if osx_version: + try: + _SYSTEM_VERSION_TUPLE = tuple(int(i) for i in osx_version.split('.')) + except ValueError: + _SYSTEM_VERSION_TUPLE = () + + return _SYSTEM_VERSION_TUPLE + + def _remove_original_values(_config_vars): """Remove original unmodified values for testing""" # This is needed for higher-level cross-platform tests of get_platform. @@ -125,6 +148,33 @@ def _save_modified_value(_config_vars, cv, newvalue): _config_vars[_INITPRE + cv] = oldvalue _config_vars[cv] = newvalue + +_cache_default_sysroot = None +def _default_sysroot(cc): + """ Returns the root of the default SDK for this system, or '/' """ + global _cache_default_sysroot + + if _cache_default_sysroot is not None: + return _cache_default_sysroot + + contents = _read_output('%s -c -E -v - "): + in_incdirs = True + elif line.startswith("End of search list"): + in_incdirs = False + elif in_incdirs: + line = line.strip() + if line == '/usr/include': + _cache_default_sysroot = '/' + elif line.endswith(".sdk/usr/include"): + _cache_default_sysroot = line[:-12] + if _cache_default_sysroot is None: + _cache_default_sysroot = '/' + + return _cache_default_sysroot + 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, @@ -132,14 +182,18 @@ def _supports_universal_builds(): # 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 = '' + osx_version = _get_system_version_tuple() return bool(osx_version >= (10, 4)) if osx_version else False +def _supports_arm64_builds(): + """Returns True if arm64 builds are supported on this system""" + # There are two sets of systems supporting macOS/arm64 builds: + # 1. macOS 11 and later, unconditionally + # 2. macOS 10.15 with Xcode 12.2 or later + # For now the second category is ignored. + osx_version = _get_system_version_tuple() + return osx_version >= (11, 0) if osx_version else False + def _find_appropriate_compiler(_config_vars): """Find appropriate C compiler for extension module builds""" @@ -211,7 +265,7 @@ def _remove_universal_flags(_config_vars): if cv in _config_vars and cv not in os.environ: flags = _config_vars[cv] flags = re.sub(r'-arch\s+\w+\s', ' ', flags, flags=re.ASCII) - flags = re.sub('-isysroot [^ \t]*', ' ', flags) + flags = re.sub(r'-isysroot\s*\S+', ' ', flags) _save_modified_value(_config_vars, cv, flags) return _config_vars @@ -287,7 +341,7 @@ def _check_for_unavailable_sdk(_config_vars): # 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) + m = re.search(r'-isysroot\s*(\S+)', cflags) if m is not None: sdk = m.group(1) if not os.path.exists(sdk): @@ -295,7 +349,7 @@ def _check_for_unavailable_sdk(_config_vars): # Do not alter a config var explicitly overridden 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) + flags = re.sub(r'-isysroot\s*\S+(?:\s|$)', ' ', flags) _save_modified_value(_config_vars, cv, flags) return _config_vars @@ -320,7 +374,7 @@ def compiler_fixup(compiler_so, cc_args): stripArch = stripSysroot = True else: stripArch = '-arch' in cc_args - stripSysroot = '-isysroot' in cc_args + stripSysroot = any(arg for arg in cc_args if arg.startswith('-isysroot')) if stripArch or 'ARCHFLAGS' in os.environ: while True: @@ -331,6 +385,12 @@ def compiler_fixup(compiler_so, cc_args): except ValueError: break + elif not _supports_arm64_builds(): + # Look for "-arch arm64" and drop that + for idx in reversed(range(len(compiler_so))): + if compiler_so[idx] == '-arch' and compiler_so[idx+1] == "arm64": + del compiler_so[idx:idx+2] + if 'ARCHFLAGS' in os.environ and not stripArch: # User specified different -arch flags in the environ, # see also distutils.sysconfig @@ -338,29 +398,39 @@ def compiler_fixup(compiler_so, cc_args): if stripSysroot: while True: - try: - index = compiler_so.index('-isysroot') + indices = [i for i,x in enumerate(compiler_so) if x.startswith('-isysroot')] + if not indices: + break + index = indices[0] + if compiler_so[index] == '-isysroot': # Strip this argument and the next one: del compiler_so[index:index+2] - except ValueError: - break + else: + # It's '-isysroot/some/path' in one arg + del compiler_so[index:index+1] # 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] + argvar = cc_args + indices = [i for i,x in enumerate(cc_args) if x.startswith('-isysroot')] + if not indices: + argvar = compiler_so + indices = [i for i,x in enumerate(compiler_so) if x.startswith('-isysroot')] + + for idx in indices: + if argvar[idx] == '-isysroot': + sysroot = argvar[idx+1] + break + else: + sysroot = argvar[idx][len('-isysroot'):] + break 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") + sys.stderr.write(f"Compiling with an SDK that doesn't seem to exist: {sysroot}\n") + sys.stderr.write("Please check your Xcode installation\n") + sys.stderr.flush() return compiler_so @@ -411,7 +481,7 @@ def customize_compiler(_config_vars): This customization is performed when the first extension module build is requested - in distutils.sysconfig.customize_compiler). + in distutils.sysconfig.customize_compiler. """ # Find a compiler to use for extension module builds @@ -454,10 +524,10 @@ def get_platform_osx(_config_vars, osname, release, machine): try: macrelease = tuple(int(i) for i in macrelease.split('.')[0:2]) except ValueError: - macrelease = (10, 0) + macrelease = (10, 3) else: # assume no universal support - macrelease = (10, 0) + macrelease = (10, 3) if (macrelease >= (10, 4)) and '-arch' in cflags.strip(): # The universal build will build fat binaries, but not on @@ -470,6 +540,8 @@ def get_platform_osx(_config_vars, osname, release, machine): if len(archs) == 1: machine = archs[0] + elif archs == ('arm64', 'x86_64'): + machine = 'universal2' elif archs == ('i386', 'ppc'): machine = 'fat' elif archs == ('i386', 'x86_64'): diff --git a/Lib/test/test__osx_support.py b/Lib/test/test__osx_support.py index 4e85a3e07..907ae27d5 100644 --- a/Lib/test/test__osx_support.py +++ b/Lib/test/test__osx_support.py @@ -1,331 +1,327 @@ -""" -Test suite for _osx_support: shared OS X support functions. -""" - -import os -import platform -import stat -import sys -import unittest - -from test.support import os_helper - -import _osx_support - -@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()) - self.env = os_helper.EnvironmentVarGuard() - self.addCleanup(self.env.__exit__) - for cv in ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', - 'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC', - 'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', - 'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS'): - if cv in self.env: - self.env.unset(cv) - - 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): - if self.env['PATH']: - self.env['PATH'] = self.env['PATH'] + ':' - self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) - os_helper.unlink(self.prog_name) - self.assertIsNone(_osx_support._find_executable(self.prog_name)) - self.addCleanup(os_helper.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): - if self.env['PATH']: - self.env['PATH'] = self.env['PATH'] + ':' - self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) - os_helper.unlink(self.prog_name) - self.addCleanup(os_helper.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 - mac_ver_tuple = tuple(int(i) for i in - platform.mac_ver()[0].split('.')[0:2]) - self.assertEqual(mac_ver_tuple >= (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) - - suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' - self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix - for c_name, c_output in compilers: - os_helper.unlink(c_name) - self.addCleanup(os_helper.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)) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test__remove_universal_flags_alternate(self): - # bpo-38360: also test the alternate single-argument form of -isysroot - 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)) - - 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) - - suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' - self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix - c_name = 'clang' - os_helper.unlink(c_name) - self.addCleanup(os_helper.unlink, c_name) - # exit status 255 means no PPC support in this compiler chain - with open(c_name, 'w') as f: - f.write("#!/bin/sh\nexit 255") - os.chmod(c_name, stat.S_IRWXU) - self.assertEqual(expected_vars, - _osx_support._remove_unsupported_archs( - config_vars)) - - def test__override_all_archs(self): - self.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)) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test__check_for_unavailable_sdk_alternate(self): - # bpo-38360: also test the alternate single-argument form of -isysroot - 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) - -if __name__ == "__main__": - unittest.main() +""" +Test suite for _osx_support: shared OS X support functions. +""" + +import os +import platform +import stat +import sys +import unittest + +from test.support import os_helper + +import _osx_support + +@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()) + self.env = os_helper.EnvironmentVarGuard() + self.addCleanup(self.env.__exit__) + for cv in ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', + 'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC', + 'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', + 'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS'): + if cv in self.env: + self.env.unset(cv) + + 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): + if self.env['PATH']: + self.env['PATH'] = self.env['PATH'] + ':' + self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) + os_helper.unlink(self.prog_name) + self.assertIsNone(_osx_support._find_executable(self.prog_name)) + self.addCleanup(os_helper.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): + if self.env['PATH']: + self.env['PATH'] = self.env['PATH'] + ':' + self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) + os_helper.unlink(self.prog_name) + self.addCleanup(os_helper.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 + mac_ver_tuple = tuple(int(i) for i in + platform.mac_ver()[0].split('.')[0:2]) + self.assertEqual(mac_ver_tuple >= (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) + + suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' + self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix + for c_name, c_output in compilers: + os_helper.unlink(c_name) + self.addCleanup(os_helper.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)) + + def test__remove_universal_flags_alternate(self): + # bpo-38360: also test the alternate single-argument form of -isysroot + 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)) + + 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) + + suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' + self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix + c_name = 'clang' + os_helper.unlink(c_name) + self.addCleanup(os_helper.unlink, c_name) + # exit status 255 means no PPC support in this compiler chain + with open(c_name, 'w') as f: + f.write("#!/bin/sh\nexit 255") + os.chmod(c_name, stat.S_IRWXU) + self.assertEqual(expected_vars, + _osx_support._remove_unsupported_archs( + config_vars)) + + def test__override_all_archs(self): + self.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__check_for_unavailable_sdk_alternate(self): + # bpo-38360: also test the alternate single-argument form of -isysroot + 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) + +if __name__ == "__main__": + unittest.main()