Skip to content

Commit 4176193

Browse files
bpo-41100: Support macOS 11 and Apple Silicon (pythonGH-22855)
Co-authored-by: Lawrence D’Anna <[email protected]> * 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.
1 parent fd6f6fa commit 4176193

27 files changed

+1587
-345
lines changed

Lib/_osx_support.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ def _get_system_version():
110110

111111
return _SYSTEM_VERSION
112112

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

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 = ''
155+
osx_version = _get_system_version_tuple()
141156
return bool(osx_version >= (10, 4)) if osx_version else False
142157

158+
def _supports_arm64_builds():
159+
"""Returns True if arm64 builds are supported on this system"""
160+
# There are two sets of systems supporting macOS/arm64 builds:
161+
# 1. macOS 11 and later, unconditionally
162+
# 2. macOS 10.15 with Xcode 12.2 or later
163+
# For now the second category is ignored.
164+
osx_version = _get_system_version_tuple()
165+
return osx_version >= (11, 0) if osx_version else False
166+
143167

144168
def _find_appropriate_compiler(_config_vars):
145169
"""Find appropriate C compiler for extension module builds"""
@@ -331,6 +355,12 @@ def compiler_fixup(compiler_so, cc_args):
331355
except ValueError:
332356
break
333357

358+
elif not _supports_arm64_builds():
359+
# Look for "-arch arm64" and drop that
360+
for idx in range(len(compiler_so)):
361+
if compiler_so[idx] == '-arch' and compiler_so[idx+1] == "arm64":
362+
del compiler_so[idx:idx+2]
363+
334364
if 'ARCHFLAGS' in os.environ and not stripArch:
335365
# User specified different -arch flags in the environ,
336366
# see also distutils.sysconfig
@@ -481,6 +511,8 @@ def get_platform_osx(_config_vars, osname, release, machine):
481511

482512
if len(archs) == 1:
483513
machine = archs[0]
514+
elif archs == ('arm64', 'x86_64'):
515+
machine = 'universal2'
484516
elif archs == ('i386', 'ppc'):
485517
machine = 'fat'
486518
elif archs == ('i386', 'x86_64'):

Lib/ctypes/macholib/dyld.py

Lines changed: 12 additions & 0 deletions
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

Lines changed: 9 additions & 6 deletions
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

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

Lib/test/test_bytes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,7 @@ def test_from_format(self):
10361036
c_char_p)
10371037

10381038
PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
1039+
PyBytes_FromFormat.argtypes = (c_char_p,)
10391040
PyBytes_FromFormat.restype = py_object
10401041

10411042
# basic tests

Lib/test/test_platform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def test_mac_ver(self):
246246
self.assertEqual(res[1], ('', '', ''))
247247

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

Lib/test/test_posix.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,13 +1925,241 @@ def test_posix_spawnp(self):
19251925
assert_python_ok(*args, PATH=path)
19261926

19271927

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

0 commit comments

Comments
 (0)