Skip to content

Commit e8b1c03

Browse files
[3.9] bpo-41100: Support macOS 11 and Apple Silicon (pythonGH-22855) (pythonGH-23295)
* [3.9] bpo-41100: Support macOS 11 and Apple Silicon (pythonGH-22855) Co-authored-by: Lawrence D’Anna <lawrence_danna@apple.com> * Add support for macOS 11 and Apple Silicon (aka arm64) As a side effect of this work use the system copy of libffi on macOS, and remove the vendored copy * Support building on recent versions of macOS while deploying to older versions This allows building installers on macOS 11 while still supporting macOS 10.9.. (cherry picked from commit 4176193) Co-authored-by: Ronald Oussoren <ronaldoussoren@mac.com> * Back port of changes to _decimal to support arm64 * temp_dir is in test.support in 3.9
1 parent 0aab352 commit e8b1c03

28 files changed

+1592
-346
lines changed

Lib/_osx_support.py

+38-6
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ def _get_system_version():
113113

114114
return _SYSTEM_VERSION
115115

116+
_SYSTEM_VERSION_TUPLE = None
117+
def _get_system_version_tuple():
118+
"""
119+
Return the macOS system version as a tuple
120+
121+
The return value is safe to use to compare
122+
two version numbers.
123+
"""
124+
global _SYSTEM_VERSION_TUPLE
125+
if _SYSTEM_VERSION_TUPLE is None:
126+
osx_version = _get_system_version()
127+
if osx_version:
128+
try:
129+
_SYSTEM_VERSION_TUPLE = tuple(int(i) for i in osx_version.split('.'))
130+
except ValueError:
131+
_SYSTEM_VERSION_TUPLE = ()
132+
133+
return _SYSTEM_VERSION_TUPLE
134+
135+
116136
def _remove_original_values(_config_vars):
117137
"""Remove original unmodified values for testing"""
118138
# This is needed for higher-level cross-platform tests of get_platform.
@@ -162,14 +182,18 @@ def _supports_universal_builds():
162182
# builds, in particular -isysroot and -arch arguments to the compiler. This
163183
# is in support of allowing 10.4 universal builds to run on 10.3.x systems.
164184

165-
osx_version = _get_system_version()
166-
if osx_version:
167-
try:
168-
osx_version = tuple(int(i) for i in osx_version.split('.'))
169-
except ValueError:
170-
osx_version = ''
185+
osx_version = _get_system_version_tuple()
171186
return bool(osx_version >= (10, 4)) if osx_version else False
172187

188+
def _supports_arm64_builds():
189+
"""Returns True if arm64 builds are supported on this system"""
190+
# There are two sets of systems supporting macOS/arm64 builds:
191+
# 1. macOS 11 and later, unconditionally
192+
# 2. macOS 10.15 with Xcode 12.2 or later
193+
# For now the second category is ignored.
194+
osx_version = _get_system_version_tuple()
195+
return osx_version >= (11, 0) if osx_version else False
196+
173197

174198
def _find_appropriate_compiler(_config_vars):
175199
"""Find appropriate C compiler for extension module builds"""
@@ -361,6 +385,12 @@ def compiler_fixup(compiler_so, cc_args):
361385
except ValueError:
362386
break
363387

388+
elif not _supports_arm64_builds():
389+
# Look for "-arch arm64" and drop that
390+
for idx in reversed(range(len(compiler_so))):
391+
if compiler_so[idx] == '-arch' and compiler_so[idx+1] == "arm64":
392+
del compiler_so[idx:idx+2]
393+
364394
if 'ARCHFLAGS' in os.environ and not stripArch:
365395
# User specified different -arch flags in the environ,
366396
# see also distutils.sysconfig
@@ -511,6 +541,8 @@ def get_platform_osx(_config_vars, osname, release, machine):
511541

512542
if len(archs) == 1:
513543
machine = archs[0]
544+
elif archs == ('arm64', 'x86_64'):
545+
machine = 'universal2'
514546
elif archs == ('i386', 'ppc'):
515547
machine = 'fat'
516548
elif archs == ('i386', 'x86_64'):

Lib/ctypes/macholib/dyld.py

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
from ctypes.macholib.framework import framework_info
77
from ctypes.macholib.dylib import dylib_info
88
from itertools import *
9+
try:
10+
from _ctypes import _dyld_shared_cache_contains_path
11+
except ImportError:
12+
def _dyld_shared_cache_contains_path(*args):
13+
raise NotImplementedError
914

1015
__all__ = [
1116
'dyld_find', 'framework_find',
@@ -122,8 +127,15 @@ def dyld_find(name, executable_path=None, env=None):
122127
dyld_executable_path_search(name, executable_path),
123128
dyld_default_search(name, env),
124129
), env):
130+
125131
if os.path.isfile(path):
126132
return path
133+
try:
134+
if _dyld_shared_cache_contains_path(path):
135+
return path
136+
except NotImplementedError:
137+
pass
138+
127139
raise ValueError("dylib %s could not be found" % (name,))
128140

129141
def framework_find(fn, executable_path=None, env=None):

Lib/ctypes/test/test_macholib.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,22 @@ def find_lib(name):
4545
class MachOTest(unittest.TestCase):
4646
@unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test')
4747
def test_find(self):
48-
49-
self.assertEqual(find_lib('pthread'),
50-
'/usr/lib/libSystem.B.dylib')
48+
# On Mac OS 11, system dylibs are only present in the shared cache,
49+
# so symlinks like libpthread.dylib -> libSystem.B.dylib will not
50+
# be resolved by dyld_find
51+
self.assertIn(find_lib('pthread'),
52+
('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib'))
5153

5254
result = find_lib('z')
5355
# Issue #21093: dyld default search path includes $HOME/lib and
5456
# /usr/local/lib before /usr/lib, which caused test failures if
5557
# a local copy of libz exists in one of them. Now ignore the head
5658
# of the path.
57-
self.assertRegex(result, r".*/lib/libz\..*.*\.dylib")
59+
self.assertRegex(result, r".*/lib/libz.*\.dylib")
5860

59-
self.assertEqual(find_lib('IOKit'),
60-
'/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit')
61+
self.assertIn(find_lib('IOKit'),
62+
('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit',
63+
'/System/Library/Frameworks/IOKit.framework/IOKit'))
6164

6265
if __name__ == "__main__":
6366
unittest.main()

Lib/distutils/tests/test_build_ext.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ def _try_compile_deployment_target(self, operator, target):
492492
# format the target value as defined in the Apple
493493
# Availability Macros. We can't use the macro names since
494494
# at least one value we test with will not exist yet.
495-
if target[1] < 10:
495+
if target[:2] < (10, 10):
496496
# for 10.1 through 10.9.x -> "10n0"
497497
target = '%02d%01d0' % target
498498
else:

Lib/test/test_bytes.py

+1
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,7 @@ def test_from_format(self):
10341034
c_char_p)
10351035

10361036
PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
1037+
PyBytes_FromFormat.argtypes = (c_char_p,)
10371038
PyBytes_FromFormat.restype = py_object
10381039

10391040
# basic tests

Lib/test/test_platform.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def test_mac_ver(self):
245245
self.assertEqual(res[1], ('', '', ''))
246246

247247
if sys.byteorder == 'little':
248-
self.assertIn(res[2], ('i386', 'x86_64'))
248+
self.assertIn(res[2], ('i386', 'x86_64', 'arm64'))
249249
else:
250250
self.assertEqual(res[2], 'PowerPC')
251251

Lib/test/test_posix.py

+228
Original file line numberDiff line numberDiff line change
@@ -1905,13 +1905,241 @@ def test_posix_spawnp(self):
19051905
assert_python_ok(*args, PATH=path)
19061906

19071907

1908+
@unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS")
1909+
class TestPosixWeaklinking(unittest.TestCase):
1910+
# These test cases verify that weak linking support on macOS works
1911+
# as expected. These cases only test new behaviour introduced by weak linking,
1912+
# regular behaviour is tested by the normal test cases.
1913+
#
1914+
# See the section on Weak Linking in Mac/README.txt for more information.
1915+
def setUp(self):
1916+
import sysconfig
1917+
import platform
1918+
1919+
config_vars = sysconfig.get_config_vars()
1920+
self.available = { nm for nm in config_vars if nm.startswith("HAVE_") and config_vars[nm] }
1921+
self.mac_ver = tuple(int(part) for part in platform.mac_ver()[0].split("."))
1922+
1923+
def _verify_available(self, name):
1924+
if name not in self.available:
1925+
raise unittest.SkipTest(f"{name} not weak-linked")
1926+
1927+
def test_pwritev(self):
1928+
self._verify_available("HAVE_PWRITEV")
1929+
if self.mac_ver >= (10, 16):
1930+
self.assertTrue(hasattr(os, "pwritev"), "os.pwritev is not available")
1931+
self.assertTrue(hasattr(os, "preadv"), "os.readv is not available")
1932+
1933+
else:
1934+
self.assertFalse(hasattr(os, "pwritev"), "os.pwritev is available")
1935+
self.assertFalse(hasattr(os, "preadv"), "os.readv is available")
1936+
1937+
def test_stat(self):
1938+
self._verify_available("HAVE_FSTATAT")
1939+
if self.mac_ver >= (10, 10):
1940+
self.assertIn("HAVE_FSTATAT", posix._have_functions)
1941+
1942+
else:
1943+
self.assertNotIn("HAVE_FSTATAT", posix._have_functions)
1944+
1945+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
1946+
os.stat("file", dir_fd=0)
1947+
1948+
def test_access(self):
1949+
self._verify_available("HAVE_FACCESSAT")
1950+
if self.mac_ver >= (10, 10):
1951+
self.assertIn("HAVE_FACCESSAT", posix._have_functions)
1952+
1953+
else:
1954+
self.assertNotIn("HAVE_FACCESSAT", posix._have_functions)
1955+
1956+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
1957+
os.access("file", os.R_OK, dir_fd=0)
1958+
1959+
with self.assertRaisesRegex(NotImplementedError, "follow_symlinks unavailable"):
1960+
os.access("file", os.R_OK, follow_symlinks=False)
1961+
1962+
with self.assertRaisesRegex(NotImplementedError, "effective_ids unavailable"):
1963+
os.access("file", os.R_OK, effective_ids=True)
1964+
1965+
def test_chmod(self):
1966+
self._verify_available("HAVE_FCHMODAT")
1967+
if self.mac_ver >= (10, 10):
1968+
self.assertIn("HAVE_FCHMODAT", posix._have_functions)
1969+
1970+
else:
1971+
self.assertNotIn("HAVE_FCHMODAT", posix._have_functions)
1972+
self.assertIn("HAVE_LCHMOD", posix._have_functions)
1973+
1974+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
1975+
os.chmod("file", 0o644, dir_fd=0)
1976+
1977+
def test_chown(self):
1978+
self._verify_available("HAVE_FCHOWNAT")
1979+
if self.mac_ver >= (10, 10):
1980+
self.assertIn("HAVE_FCHOWNAT", posix._have_functions)
1981+
1982+
else:
1983+
self.assertNotIn("HAVE_FCHOWNAT", posix._have_functions)
1984+
self.assertIn("HAVE_LCHOWN", posix._have_functions)
1985+
1986+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
1987+
os.chown("file", 0, 0, dir_fd=0)
1988+
1989+
def test_link(self):
1990+
self._verify_available("HAVE_LINKAT")
1991+
if self.mac_ver >= (10, 10):
1992+
self.assertIn("HAVE_LINKAT", posix._have_functions)
1993+
1994+
else:
1995+
self.assertNotIn("HAVE_LINKAT", posix._have_functions)
1996+
1997+
with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"):
1998+
os.link("source", "target", src_dir_fd=0)
1999+
2000+
with self.assertRaisesRegex(NotImplementedError, "dst_dir_fd unavailable"):
2001+
os.link("source", "target", dst_dir_fd=0)
2002+
2003+
with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"):
2004+
os.link("source", "target", src_dir_fd=0, dst_dir_fd=0)
2005+
2006+
# issue 41355: !HAVE_LINKAT code path ignores the follow_symlinks flag
2007+
with support.temp_dir() as base_path:
2008+
link_path = os.path.join(base_path, "link")
2009+
target_path = os.path.join(base_path, "target")
2010+
source_path = os.path.join(base_path, "source")
2011+
2012+
with open(source_path, "w") as fp:
2013+
fp.write("data")
2014+
2015+
os.symlink("target", link_path)
2016+
2017+
# Calling os.link should fail in the link(2) call, and
2018+
# should not reject *follow_symlinks* (to match the
2019+
# behaviour you'd get when building on a platform without
2020+
# linkat)
2021+
with self.assertRaises(FileExistsError):
2022+
os.link(source_path, link_path, follow_symlinks=True)
2023+
2024+
with self.assertRaises(FileExistsError):
2025+
os.link(source_path, link_path, follow_symlinks=False)
2026+
2027+
2028+
def test_listdir_scandir(self):
2029+
self._verify_available("HAVE_FDOPENDIR")
2030+
if self.mac_ver >= (10, 10):
2031+
self.assertIn("HAVE_FDOPENDIR", posix._have_functions)
2032+
2033+
else:
2034+
self.assertNotIn("HAVE_FDOPENDIR", posix._have_functions)
2035+
2036+
with self.assertRaisesRegex(TypeError, "listdir: path should be string, bytes, os.PathLike or None, not int"):
2037+
os.listdir(0)
2038+
2039+
with self.assertRaisesRegex(TypeError, "scandir: path should be string, bytes, os.PathLike or None, not int"):
2040+
os.scandir(0)
2041+
2042+
def test_mkdir(self):
2043+
self._verify_available("HAVE_MKDIRAT")
2044+
if self.mac_ver >= (10, 10):
2045+
self.assertIn("HAVE_MKDIRAT", posix._have_functions)
2046+
2047+
else:
2048+
self.assertNotIn("HAVE_MKDIRAT", posix._have_functions)
2049+
2050+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2051+
os.mkdir("dir", dir_fd=0)
2052+
2053+
def test_rename_replace(self):
2054+
self._verify_available("HAVE_RENAMEAT")
2055+
if self.mac_ver >= (10, 10):
2056+
self.assertIn("HAVE_RENAMEAT", posix._have_functions)
2057+
2058+
else:
2059+
self.assertNotIn("HAVE_RENAMEAT", posix._have_functions)
2060+
2061+
with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
2062+
os.rename("a", "b", src_dir_fd=0)
2063+
2064+
with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
2065+
os.rename("a", "b", dst_dir_fd=0)
2066+
2067+
with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
2068+
os.replace("a", "b", src_dir_fd=0)
2069+
2070+
with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
2071+
os.replace("a", "b", dst_dir_fd=0)
2072+
2073+
def test_unlink_rmdir(self):
2074+
self._verify_available("HAVE_UNLINKAT")
2075+
if self.mac_ver >= (10, 10):
2076+
self.assertIn("HAVE_UNLINKAT", posix._have_functions)
2077+
2078+
else:
2079+
self.assertNotIn("HAVE_UNLINKAT", posix._have_functions)
2080+
2081+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2082+
os.unlink("path", dir_fd=0)
2083+
2084+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2085+
os.rmdir("path", dir_fd=0)
2086+
2087+
def test_open(self):
2088+
self._verify_available("HAVE_OPENAT")
2089+
if self.mac_ver >= (10, 10):
2090+
self.assertIn("HAVE_OPENAT", posix._have_functions)
2091+
2092+
else:
2093+
self.assertNotIn("HAVE_OPENAT", posix._have_functions)
2094+
2095+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2096+
os.open("path", os.O_RDONLY, dir_fd=0)
2097+
2098+
def test_readlink(self):
2099+
self._verify_available("HAVE_READLINKAT")
2100+
if self.mac_ver >= (10, 10):
2101+
self.assertIn("HAVE_READLINKAT", posix._have_functions)
2102+
2103+
else:
2104+
self.assertNotIn("HAVE_READLINKAT", posix._have_functions)
2105+
2106+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2107+
os.readlink("path", dir_fd=0)
2108+
2109+
def test_symlink(self):
2110+
self._verify_available("HAVE_SYMLINKAT")
2111+
if self.mac_ver >= (10, 10):
2112+
self.assertIn("HAVE_SYMLINKAT", posix._have_functions)
2113+
2114+
else:
2115+
self.assertNotIn("HAVE_SYMLINKAT", posix._have_functions)
2116+
2117+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2118+
os.symlink("a", "b", dir_fd=0)
2119+
2120+
def test_utime(self):
2121+
self._verify_available("HAVE_FUTIMENS")
2122+
self._verify_available("HAVE_UTIMENSAT")
2123+
if self.mac_ver >= (10, 13):
2124+
self.assertIn("HAVE_FUTIMENS", posix._have_functions)
2125+
self.assertIn("HAVE_UTIMENSAT", posix._have_functions)
2126+
2127+
else:
2128+
self.assertNotIn("HAVE_FUTIMENS", posix._have_functions)
2129+
self.assertNotIn("HAVE_UTIMENSAT", posix._have_functions)
2130+
2131+
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
2132+
os.utime("path", dir_fd=0)
2133+
2134+
19082135
def test_main():
19092136
try:
19102137
support.run_unittest(
19112138
PosixTester,
19122139
PosixGroupsTester,
19132140
TestPosixSpawn,
19142141
TestPosixSpawnP,
2142+
TestPosixWeaklinking
19152143
)
19162144
finally:
19172145
support.reap_children()

0 commit comments

Comments
 (0)