Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 6ee91cc

Browse files
ronaldoussorencolesbury
authored andcommitted
[3.9] bpo-41100: Support macOS 11 and Apple Silicon (GH-22855) (GH-23295)
* [3.9] bpo-41100: Support macOS 11 and Apple Silicon (GH-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 140fdc1 commit 6ee91cc

28 files changed

Lines changed: 1519 additions & 314 deletions

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 reversed(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
@@ -470,6 +500,8 @@ def get_platform_osx(_config_vars, osname, release, machine):
470500

471501
if len(archs) == 1:
472502
machine = archs[0]
503+
elif archs == ('arm64', 'x86_64'):
504+
machine = 'universal2'
473505
elif archs == ('i386', 'ppc'):
474506
machine = 'fat'
475507
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
@@ -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

Lines changed: 1 addition & 0 deletions
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

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

219219
if sys.byteorder == 'little':
220-
self.assertIn(res[2], ('i386', 'x86_64'))
220+
self.assertIn(res[2], ('i386', 'x86_64', 'arm64'))
221221
else:
222222
self.assertEqual(res[2], 'PowerPC')
223223

Lib/test/test_posix.py

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

19081908

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

0 commit comments

Comments
 (0)