Skip to content

Commit b29d0a5

Browse files
ned-deilyronaldoussorenfxcoudertmaxbelanger
authored
[3.8] bpo-41100: Support macOS 11 Big Sur and Apple Silicon Macs (#25806)
* bpo-41100: Support macOS 11 and Apple Silicon on Python 3.8 This is a partial backport of bpo-41100 changes `e8b1c038b14b5fc8120aab62c9bf5fb840274cb6` and `96d906b144e6e6aa96c5ffebecbcc5d38034bbda` for Python 3.8. We introduce the ability to build Python from source for `arm64` on macOS, but we do not make a promise of support. This allows us to omit support for Universal2 binaries as well as weak-linking of symbols from the macOS SDK based on the deployment target, which are larger changes much more difficult to merge. This also includes a backport of subsequent bpo-42688 change `7e729978fa08a360cbf936dc215ba7dd25a06a08` to fix build errors with external `libffi`. * bpo-41116: Ensure system supplied libraries are found on macOS 11 (GH-23301) (GH-23455) On macOS system provided libraries are in a shared library cache and not at their usual location. This PR teaches distutils to search in the SDK, even if there was no "-sysroot" argument in the compiler flags. (cherry picked from commit 404a719) * bpo-42504: fix for MACOSX_DEPLOYMENT_TARGET=11 (GH-23556) macOS releases numbering has changed as of macOS 11 Big Sur. Previously, major releases were of the form 10.x, 10.x+1, 10.x+2, etc; as of Big Sur, they are now x, x+1, etc, so, for example, 10.15, 10.15.1, ..., 10.15.7, 11, 11.0.1, 11.1, ..., 12, 12.1, etc. Allow Python to build with single-digit deployment target values. Patch provided by FX Coudert. (cherry picked from commit 5291639) * bpo-42504: Ensure that get_config_var('MACOSX_DEPLOYMENT_TARGET') is a string (GH-24341) (GH-24410) * bpo-42504: Ensure that get_config_var('MACOSX_DEPLOYMENT_TARGET') is a string (cherry picked from commit 49926cf) Co-authored-by: Ronald Oussoren <[email protected]> Co-authored-by: FX Coudert <[email protected]> Co-authored-by: Max Bélanger <[email protected]>
1 parent d8ec61f commit b29d0a5

File tree

26 files changed

+547
-157
lines changed

26 files changed

+547
-157
lines changed

Doc/whatsnew/3.8.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,3 +2273,25 @@ IPv4 address sent from the remote server when setting up a passive data
22732273
channel. We reuse the ftp server IP address instead. For unusual code
22742274
requiring the old behavior, set a ``trust_server_pasv_ipv4_address``
22752275
attribute on your FTP instance to ``True``. (See :issue:`43285`)
2276+
2277+
Notable changes in Python 3.8.10
2278+
================================
2279+
2280+
macOS 11.0 (Big Sur) and Apple Silicon Mac support
2281+
--------------------------------------------------
2282+
2283+
As of 3.8.10, Python now supports building and running on macOS 11
2284+
(Big Sur) and on Apple Silicon Macs (based on the ``ARM64`` architecture).
2285+
A new universal build variant, ``universal2``, is now available to natively
2286+
support both ``ARM64`` and ``Intel 64`` in one set of executables.
2287+
Note that support for "weaklinking", building binaries targeted for newer
2288+
versions of macOS that will also run correctly on older versions by
2289+
testing at runtime for missing features, is not included in this backport
2290+
from Python 3.9; to support a range of macOS versions, continue to target
2291+
for and build on the oldest version in the range.
2292+
2293+
(Originally contributed by Ronald Oussoren and Lawrence D'Anna in :issue:`41100`,
2294+
with fixes by FX Coudert and Eli Rykoff, and backported to 3.8 by Maxime Bélanger
2295+
and Ned Deily)
2296+
2297+

Lib/_osx_support.py

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def _find_executable(executable, path=None):
5252
return executable
5353

5454

55-
def _read_output(commandstring):
55+
def _read_output(commandstring, capture_stderr=False):
5656
"""Output from successful command execution or None"""
5757
# Similar to os.popen(commandstring, "r").read(),
5858
# but without actually using os.popen because that
@@ -67,7 +67,10 @@ def _read_output(commandstring):
6767
os.getpid(),), "w+b")
6868

6969
with contextlib.closing(fp) as fp:
70-
cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
70+
if capture_stderr:
71+
cmd = "%s >'%s' 2>&1" % (commandstring, fp.name)
72+
else:
73+
cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
7174
return fp.read().decode('utf-8').strip() if not os.system(cmd) else None
7275

7376

@@ -110,6 +113,26 @@ def _get_system_version():
110113

111114
return _SYSTEM_VERSION
112115

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+
113136
def _remove_original_values(_config_vars):
114137
"""Remove original unmodified values for testing"""
115138
# This is needed for higher-level cross-platform tests of get_platform.
@@ -125,21 +148,52 @@ def _save_modified_value(_config_vars, cv, newvalue):
125148
_config_vars[_INITPRE + cv] = oldvalue
126149
_config_vars[cv] = newvalue
127150

151+
152+
_cache_default_sysroot = None
153+
def _default_sysroot(cc):
154+
""" Returns the root of the default SDK for this system, or '/' """
155+
global _cache_default_sysroot
156+
157+
if _cache_default_sysroot is not None:
158+
return _cache_default_sysroot
159+
160+
contents = _read_output('%s -c -E -v - </dev/null' % (cc,), True)
161+
in_incdirs = False
162+
for line in contents.splitlines():
163+
if line.startswith("#include <...>"):
164+
in_incdirs = True
165+
elif line.startswith("End of search list"):
166+
in_incdirs = False
167+
elif in_incdirs:
168+
line = line.strip()
169+
if line == '/usr/include':
170+
_cache_default_sysroot = '/'
171+
elif line.endswith(".sdk/usr/include"):
172+
_cache_default_sysroot = line[:-12]
173+
if _cache_default_sysroot is None:
174+
_cache_default_sysroot = '/'
175+
176+
return _cache_default_sysroot
177+
128178
def _supports_universal_builds():
129179
"""Returns True if universal builds are supported on this system"""
130180
# As an approximation, we assume that if we are running on 10.4 or above,
131181
# then we are running with an Xcode environment that supports universal
132182
# builds, in particular -isysroot and -arch arguments to the compiler. This
133183
# is in support of allowing 10.4 universal builds to run on 10.3.x systems.
134184

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 = ''
185+
osx_version = _get_system_version_tuple()
141186
return bool(osx_version >= (10, 4)) if osx_version else False
142187

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+
143197

144198
def _find_appropriate_compiler(_config_vars):
145199
"""Find appropriate C compiler for extension module builds"""
@@ -331,6 +385,12 @@ def compiler_fixup(compiler_so, cc_args):
331385
except ValueError:
332386
break
333387

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+
334394
if 'ARCHFLAGS' in os.environ and not stripArch:
335395
# User specified different -arch flags in the environ,
336396
# see also distutils.sysconfig
@@ -481,6 +541,8 @@ def get_platform_osx(_config_vars, osname, release, machine):
481541

482542
if len(archs) == 1:
483543
machine = archs[0]
544+
elif archs == ('arm64', 'x86_64'):
545+
machine = 'universal2'
484546
elif archs == ('i386', 'ppc'):
485547
machine = 'fat'
486548
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: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,12 +492,16 @@ 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:
499499
# for 10.10 and beyond -> "10nn00"
500-
target = '%02d%02d00' % target
500+
if len(target) >= 2:
501+
target = '%02d%02d00' % target
502+
else:
503+
# 11 and later can have no minor version (11 instead of 11.0)
504+
target = '%02d0000' % target
501505
deptarget_ext = Extension(
502506
'deptarget',
503507
[deptarget_c],

Lib/distutils/unixccompiler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def find_library_file(self, dirs, lib, debug=0):
290290
cflags = sysconfig.get_config_var('CFLAGS')
291291
m = re.search(r'-isysroot\s*(\S+)', cflags)
292292
if m is None:
293-
sysroot = '/'
293+
sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC'))
294294
else:
295295
sysroot = m.group(1)
296296

Lib/sysconfig.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
'parse_config_h',
1919
]
2020

21+
# Keys for get_config_var() that are never converted to Python integers.
22+
_ALWAYS_STR = {
23+
'MACOSX_DEPLOYMENT_TARGET',
24+
}
25+
2126
_INSTALL_SCHEMES = {
2227
'posix_prefix': {
2328
'stdlib': '{installed_base}/lib/python{py_version_short}',
@@ -242,6 +247,9 @@ def _parse_makefile(filename, vars=None):
242247
notdone[n] = v
243248
else:
244249
try:
250+
if n in _ALWAYS_STR:
251+
raise ValueError
252+
245253
v = int(v)
246254
except ValueError:
247255
# insert literal `$'
@@ -300,6 +308,8 @@ def _parse_makefile(filename, vars=None):
300308
notdone[name] = value
301309
else:
302310
try:
311+
if name in _ALWAYS_STR:
312+
raise ValueError
303313
value = int(value)
304314
except ValueError:
305315
done[name] = value.strip()
@@ -461,6 +471,8 @@ def parse_config_h(fp, vars=None):
461471
if m:
462472
n, v = m.group(1, 2)
463473
try:
474+
if n in _ALWAYS_STR:
475+
raise ValueError
464476
v = int(v)
465477
except ValueError:
466478
pass

Lib/test/test_bytes.py

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

965965
PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
966+
PyBytes_FromFormat.argtypes = (c_char_p,)
966967
PyBytes_FromFormat.restype = py_object
967968

968969
# basic tests

Lib/test/test_platform.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,18 @@ def test_mac_ver(self):
209209
# On Snow Leopard, sw_vers reports 10.6.0 as 10.6
210210
if len_diff > 0:
211211
expect_list.extend(['0'] * len_diff)
212-
self.assertEqual(result_list, expect_list)
212+
# For compatibility with older binaries, macOS 11.x may report
213+
# itself as '10.16' rather than '11.x.y'.
214+
if result_list != ['10', '16']:
215+
self.assertEqual(result_list, expect_list)
213216

214217
# res[1] claims to contain
215218
# (version, dev_stage, non_release_version)
216219
# That information is no longer available
217220
self.assertEqual(res[1], ('', '', ''))
218221

219222
if sys.byteorder == 'little':
220-
self.assertIn(res[2], ('i386', 'x86_64'))
223+
self.assertIn(res[2], ('i386', 'x86_64', 'arm64'))
221224
else:
222225
self.assertEqual(res[2], 'PowerPC')
223226

Lib/test/test_unicode.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2454,11 +2454,13 @@ class CAPITest(unittest.TestCase):
24542454
def test_from_format(self):
24552455
support.import_module('ctypes')
24562456
from ctypes import (
2457+
c_char_p,
24572458
pythonapi, py_object, sizeof,
24582459
c_int, c_long, c_longlong, c_ssize_t,
24592460
c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p)
24602461
name = "PyUnicode_FromFormat"
24612462
_PyUnicode_FromFormat = getattr(pythonapi, name)
2463+
_PyUnicode_FromFormat.argtypes = (c_char_p,)
24622464
_PyUnicode_FromFormat.restype = py_object
24632465

24642466
def PyUnicode_FromFormat(format, *args):

0 commit comments

Comments
 (0)