-From a8e38eadcb0e802ed353077a0ce619daffdf17c3 Mon Sep 17 00:00:00 2001
-From: Lawrence D'Anna <lawrence_danna@apple.com>
-Date: Mon, 29 Jun 2020 17:36:23 -0700
-Subject: [PATCH 3/9] use system libffi for Mac OS 10.15 and up
-Comment: Patches 1/9 and 3/9 have been slightly modified in order to
-Comment: backport this patchset to Python 3.8.3.
-
-This updates setup.py to default to the system libffi on Mac OS 10.15 and forward.   It also updates
-detect_ctypes to prefer finding libffi in the Xcode SDK, rather than /usr/include
----
- setup.py | 41 ++++++++++++++++++++++++++++++-----------
- 1 file changed, 30 insertions(+), 11 deletions(-)
-
-diff --git a/setup.py b/setup.py
-index 6f20605..546fb48 100644
---- a/setup.py
-+++ b/setup.py
-@@ -205,6 +205,13 @@ def macosx_sdk_specified():
-     macosx_sdk_root()
-     return MACOS_SDK_SPECIFIED
- 
-+def is_macosx_at_least(vers):
-+    if MACOS:
-+        dep_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
-+        if dep_target:
-+            return tuple(map(int, dep_target.split('.'))) >= vers
-+    return False
-+
- 
- def is_macosx_sdk_path(path):
-     """
-@@ -2059,7 +2066,12 @@ class PyBuildExt(build_ext):
- 
-     def detect_ctypes(self):
-         # Thomas Heller's _ctypes module
--        self.use_system_libffi = False
-+
-+        if not sysconfig.get_config_var("LIBFFI_INCLUDEDIR") and is_macosx_at_least((10,15)):
-+            self.use_system_libffi = True
-+        else:
-+            self.use_system_libffi = '--with-system-ffi' in sysconfig.get_config_var("CONFIG_ARGS")
-+
-         include_dirs = []
-         extra_compile_args = []
-         extra_link_args = []
-@@ -2106,15 +2118,23 @@ class PyBuildExt(build_ext):
-                                sources=['_ctypes/_ctypes_test.c'],
-                                libraries=['m']))
- 
--        ffi_inc_dirs = self.inc_dirs.copy()
--        if MACOS:
--            if '--with-system-ffi' not in sysconfig.get_config_var("CONFIG_ARGS"):
--                return
--            # OS X 10.5 comes with libffi.dylib; the include files are
--            # in /usr/include/ffi
--            ffi_inc_dirs.append('/usr/include/ffi')
+diff -pur a/Lib/_osx_support.py b/Lib/_osx_support.py
+--- a/Lib/_osx_support.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/_osx_support.py	2020-11-05 14:28:53.000000000 +0100
+@@ -110,6 +110,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.
+@@ -132,14 +152,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"""
+@@ -331,6 +355,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 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
+@@ -481,6 +511,8 @@ def get_platform_osx(_config_vars, osnam
+ 
+             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 -pur a/Lib/ctypes/macholib/dyld.py b/Lib/ctypes/macholib/dyld.py
+--- a/Lib/ctypes/macholib/dyld.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/ctypes/macholib/dyld.py	2020-11-05 14:28:53.000000000 +0100
+@@ -6,6 +6,11 @@ import os
+ from ctypes.macholib.framework import framework_info
+ from ctypes.macholib.dylib import dylib_info
+ from itertools import *
++try:
++    from _ctypes import _dyld_shared_cache_contains_path
++except ImportError:
++    def _dyld_shared_cache_contains_path(*args):
++        raise NotImplementedError
+ 
+ __all__ = [
+     'dyld_find', 'framework_find',
+@@ -122,8 +127,15 @@ def dyld_find(name, executable_path=None
+                 dyld_executable_path_search(name, executable_path),
+                 dyld_default_search(name, env),
+             ), env):
++
+         if os.path.isfile(path):
+             return path
++        try:
++            if _dyld_shared_cache_contains_path(path):
++                return path
++        except NotImplementedError:
++            pass
++
+     raise ValueError("dylib %s could not be found" % (name,))
+ 
+ def framework_find(fn, executable_path=None, env=None):
+diff -pur a/Lib/ctypes/test/test_macholib.py b/Lib/ctypes/test/test_macholib.py
+--- a/Lib/ctypes/test/test_macholib.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/ctypes/test/test_macholib.py	2020-11-05 14:28:53.000000000 +0100
+@@ -45,19 +45,22 @@ def find_lib(name):
+ class MachOTest(unittest.TestCase):
+     @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test')
+     def test_find(self):
 -
-         ffi_inc = [sysconfig.get_config_var("LIBFFI_INCLUDEDIR")]
-+        ffi_lib = None
-+
-+        ffi_inc_dirs = self.inc_dirs.copy()
-+        if MACOS:
-+            if not self.use_system_libffi:
-+                return
-+            ffi_in_sdk = os.path.join(macosx_sdk_root(), "usr/include/ffi")
-+            if os.path.exists(ffi_in_sdk):
-+                ffi_inc = [ffi_in_sdk]
-+                ffi_lib = 'ffi'
-+                sources.remove('_ctypes/malloc_closure.c')
-+            else:
-+                # OS X 10.5 comes with libffi.dylib; the include files are
-+                # in /usr/include/ffi
-+                ffi_inc_dirs.append('/usr/include/ffi')
-+
-         if not ffi_inc or ffi_inc[0] == '':
-             ffi_inc = find_file('ffi.h', [], ffi_inc_dirs)
-         if ffi_inc is not None:
-@@ -2122,8 +2142,7 @@ class PyBuildExt(build_ext):
-             if not os.path.exists(ffi_h):
-                 ffi_inc = None
-                 print('Header file {} does not exist'.format(ffi_h))
--        ffi_lib = None
--        if ffi_inc is not None:
-+        if ffi_lib is None and ffi_inc is not None:
-             for lib_name in ('ffi', 'ffi_pic'):
-                 if (self.compiler.find_library_file(self.lib_dirs, lib_name)):
-                     ffi_lib = lib_name
--- 
-2.21.1 (Apple Git-122.3)
-
-
-From 3e5c97ce129b2da509e3c7cebd14735a733c4d9e Mon Sep 17 00:00:00 2001
-From: Lawrence D'Anna <lawrence_danna@apple.com>
-Date: Tue, 30 Jun 2020 11:11:08 -0700
-Subject: [PATCH 4/9] ctypes: use the correct ABI for variadic functions
-Comment: Patches 1/9 and 3/9 have been slightly modified in order to
-Comment: backport this patchset to Python 3.8.3.
-
-On arm64 the calling convention for variardic functions is different
-than the convention for fixed-arg functions of the same arg types.
-
-ctypes needs to use ffi_prep_cif_var to tell libffi which calling
-convention to use.
----
- Doc/library/ctypes.rst     |  7 +++++
- Lib/test/test_bytes.py     |  2 ++
- Lib/test/test_unicode.py   |  3 +++
- Modules/_ctypes/_ctypes.c  | 32 +++++++++++++++++++++++
- Modules/_ctypes/callproc.c | 52 +++++++++++++++++++++++++++++---------
- Modules/_ctypes/ctypes.h   |  1 +
- 6 files changed, 85 insertions(+), 12 deletions(-)
-
-diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
-index 2d6c6d0..caed17a 100644
---- a/Doc/library/ctypes.rst
-+++ b/Doc/library/ctypes.rst
-@@ -1579,6 +1579,13 @@ They are instances of a private class:
-       value usable as argument (integer, string, ctypes instance).  This allows
-       defining adapters that can adapt custom objects as function parameters.
- 
-+   .. attribute:: variadic
-+
-+      Assign a boolean to specify that the function takes a variable nubmer of
-+      arguments.   This does not matter on most platforms, but for Apple arm64
-+      platforms variadic functions have a different calling convention than
-+      normal functions.
-+
-    .. attribute:: errcheck
- 
-       Assign a Python function or another callable to this attribute. The
-diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
-index bbd45c7..66c703f 100644
---- a/Lib/test/test_bytes.py
-+++ b/Lib/test/test_bytes.py
-@@ -963,6 +963,8 @@ class BytesTest(BaseBytesTest, unittest.TestCase):
+-        self.assertEqual(find_lib('pthread'),
+-                             '/usr/lib/libSystem.B.dylib')
++        # On Mac OS 11, system dylibs are only present in the shared cache,
++        # so symlinks like libpthread.dylib -> libSystem.B.dylib will not
++        # be resolved by dyld_find
++        self.assertIn(find_lib('pthread'),
++                              ('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib'))
+ 
+         result = find_lib('z')
+         # Issue #21093: dyld default search path includes $HOME/lib and
+         # /usr/local/lib before /usr/lib, which caused test failures if
+         # a local copy of libz exists in one of them. Now ignore the head
+         # of the path.
+-        self.assertRegex(result, r".*/lib/libz\..*.*\.dylib")
++        self.assertRegex(result, r".*/lib/libz.*\.dylib")
+ 
+-        self.assertEqual(find_lib('IOKit'),
+-                             '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit')
++        self.assertIn(find_lib('IOKit'),
++                              ('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit',
++                              '/System/Library/Frameworks/IOKit.framework/IOKit'))
+ 
+ if __name__ == "__main__":
+     unittest.main()
+diff -pur a/Lib/distutils/tests/test_build_ext.py b/Lib/distutils/tests/test_build_ext.py
+--- a/Lib/distutils/tests/test_build_ext.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/distutils/tests/test_build_ext.py	2020-11-05 14:28:53.000000000 +0100
+@@ -492,7 +492,7 @@ class BuildExtTestCase(TempdirManager,
+         # format the target value as defined in the Apple
+         # Availability Macros.  We can't use the macro names since
+         # at least one value we test with will not exist yet.
+-        if target[1] < 10:
++        if target[:2] < (10, 10):
+             # for 10.1 through 10.9.x -> "10n0"
+             target = '%02d%01d0' % target
+         else:
+diff -pur a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
+--- a/Lib/test/test_bytes.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/test/test_bytes.py	2020-11-05 14:28:53.000000000 +0100
+@@ -1034,6 +1034,7 @@ class BytesTest(BaseBytesTest, unittest.
              c_char_p)
  
          PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
-+        PyBytes_FromFormat.variadic = True
 +        PyBytes_FromFormat.argtypes = (c_char_p,)
          PyBytes_FromFormat.restype = py_object
  
          # basic tests
-diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py
-index 1d6aabd..a6f5d50 100644
---- a/Lib/test/test_unicode.py
-+++ b/Lib/test/test_unicode.py
-@@ -2454,11 +2454,14 @@ class CAPITest(unittest.TestCase):
+diff -pur a/Lib/test/test_platform.py b/Lib/test/test_platform.py
+--- a/Lib/test/test_platform.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/test/test_platform.py	2020-11-05 14:28:53.000000000 +0100
+@@ -245,7 +245,7 @@ class PlatformTest(unittest.TestCase):
+             self.assertEqual(res[1], ('', '', ''))
+ 
+             if sys.byteorder == 'little':
+-                self.assertIn(res[2], ('i386', 'x86_64'))
++                self.assertIn(res[2], ('i386', 'x86_64', 'arm64'))
+             else:
+                 self.assertEqual(res[2], 'PowerPC')
+ 
+diff -pur a/Lib/test/test_posix.py b/Lib/test/test_posix.py
+--- a/Lib/test/test_posix.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/test/test_posix.py	2020-11-05 14:28:53.000000000 +0100
+@@ -1905,6 +1905,233 @@ class TestPosixSpawnP(unittest.TestCase,
+         assert_python_ok(*args, PATH=path)
+ 
+ 
++@unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS")
++class TestPosixWeaklinking(unittest.TestCase):
++    # These test cases verify that weak linking support on macOS works
++    # as expected. These cases only test new behaviour introduced by weak linking,
++    # regular behaviour is tested by the normal test cases. 
++    #
++    # See the section on Weak Linking in Mac/README.txt for more information.
++    def setUp(self):
++        import sysconfig
++        import platform
++
++        config_vars = sysconfig.get_config_vars()
++        self.available = { nm for nm in config_vars if nm.startswith("HAVE_") and config_vars[nm] }
++        self.mac_ver = tuple(int(part) for part in platform.mac_ver()[0].split("."))
++
++    def _verify_available(self, name):
++        if name not in self.available:
++            raise unittest.SkipTest(f"{name} not weak-linked")
++
++    def test_pwritev(self):
++        self._verify_available("HAVE_PWRITEV")
++        if self.mac_ver >= (10, 16):
++            self.assertTrue(hasattr(os, "pwritev"), "os.pwritev is not available")
++            self.assertTrue(hasattr(os, "preadv"), "os.readv is not available")
++
++        else:
++            self.assertFalse(hasattr(os, "pwritev"), "os.pwritev is available")
++            self.assertFalse(hasattr(os, "preadv"), "os.readv is available")
++
++    def test_stat(self):
++        self._verify_available("HAVE_FSTATAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_FSTATAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_FSTATAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.stat("file", dir_fd=0)
++
++    def test_access(self):
++        self._verify_available("HAVE_FACCESSAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_FACCESSAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_FACCESSAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.access("file", os.R_OK, dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "follow_symlinks unavailable"):
++                os.access("file", os.R_OK, follow_symlinks=False)
++
++            with self.assertRaisesRegex(NotImplementedError, "effective_ids unavailable"):
++                os.access("file", os.R_OK, effective_ids=True)
++
++    def test_chmod(self):
++        self._verify_available("HAVE_FCHMODAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_FCHMODAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_FCHMODAT", posix._have_functions)
++            self.assertIn("HAVE_LCHMOD", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.chmod("file", 0o644, dir_fd=0)
++
++    def test_chown(self):
++        self._verify_available("HAVE_FCHOWNAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_FCHOWNAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_FCHOWNAT", posix._have_functions)
++            self.assertIn("HAVE_LCHOWN", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.chown("file", 0, 0, dir_fd=0)
++
++    def test_link(self):
++        self._verify_available("HAVE_LINKAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_LINKAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_LINKAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"):
++                os.link("source", "target",  src_dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "dst_dir_fd unavailable"):
++                os.link("source", "target",  dst_dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"):
++                os.link("source", "target",  src_dir_fd=0, dst_dir_fd=0)
++
++            # issue 41355: !HAVE_LINKAT code path ignores the follow_symlinks flag
++            with os_helper.temp_dir() as base_path:
++                link_path = os.path.join(base_path, "link")
++                target_path = os.path.join(base_path, "target")
++                source_path = os.path.join(base_path, "source")
++
++                with open(source_path, "w") as fp:
++                    fp.write("data")
++
++                os.symlink("target", link_path)
++
++                # Calling os.link should fail in the link(2) call, and
++                # should not reject *follow_symlinks* (to match the
++                # behaviour you'd get when building on a platform without
++                # linkat)
++                with self.assertRaises(FileExistsError):
++                    os.link(source_path, link_path, follow_symlinks=True)
++
++                with self.assertRaises(FileExistsError):
++                    os.link(source_path, link_path, follow_symlinks=False)
++
++
++    def test_listdir_scandir(self):
++        self._verify_available("HAVE_FDOPENDIR")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_FDOPENDIR", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_FDOPENDIR", posix._have_functions)
++
++            with self.assertRaisesRegex(TypeError, "listdir: path should be string, bytes, os.PathLike or None, not int"):
++                os.listdir(0)
++
++            with self.assertRaisesRegex(TypeError, "scandir: path should be string, bytes, os.PathLike or None, not int"):
++                os.scandir(0)
++
++    def test_mkdir(self):
++        self._verify_available("HAVE_MKDIRAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_MKDIRAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_MKDIRAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.mkdir("dir", dir_fd=0)
++
++    def test_rename_replace(self):
++        self._verify_available("HAVE_RENAMEAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_RENAMEAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_RENAMEAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
++                os.rename("a", "b", src_dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
++                os.rename("a", "b", dst_dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
++                os.replace("a", "b", src_dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"):
++                os.replace("a", "b", dst_dir_fd=0)
++
++    def test_unlink_rmdir(self):
++        self._verify_available("HAVE_UNLINKAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_UNLINKAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_UNLINKAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.unlink("path", dir_fd=0)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.rmdir("path", dir_fd=0)
++
++    def test_open(self):
++        self._verify_available("HAVE_OPENAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_OPENAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_OPENAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.open("path", os.O_RDONLY, dir_fd=0)
++
++    def test_readlink(self):
++        self._verify_available("HAVE_READLINKAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_READLINKAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_READLINKAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.readlink("path",  dir_fd=0)
++
++    def test_symlink(self):
++        self._verify_available("HAVE_SYMLINKAT")
++        if self.mac_ver >= (10, 10):
++            self.assertIn("HAVE_SYMLINKAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_SYMLINKAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.symlink("a", "b",  dir_fd=0)
++
++    def test_utime(self):
++        self._verify_available("HAVE_FUTIMENS")
++        self._verify_available("HAVE_UTIMENSAT")
++        if self.mac_ver >= (10, 13):
++            self.assertIn("HAVE_FUTIMENS", posix._have_functions)
++            self.assertIn("HAVE_UTIMENSAT", posix._have_functions)
++
++        else:
++            self.assertNotIn("HAVE_FUTIMENS", posix._have_functions)
++            self.assertNotIn("HAVE_UTIMENSAT", posix._have_functions)
++
++            with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
++                os.utime("path", dir_fd=0)
++
++
+ def test_main():
+     try:
+         support.run_unittest(
+@@ -1912,6 +2139,7 @@ def test_main():
+             PosixGroupsTester,
+             TestPosixSpawn,
+             TestPosixSpawnP,
++            TestPosixWeaklinking
+         )
+     finally:
+         support.reap_children()
+diff -pur a/Lib/test/test_time.py b/Lib/test/test_time.py
+--- a/Lib/test/test_time.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/test/test_time.py	2020-11-05 14:28:53.000000000 +0100
+@@ -1040,6 +1040,36 @@ class TestOldPyTime(CPyTimeTestCase, uni
+             with self.assertRaises(ValueError):
+                 pytime_object_to_timespec(float('nan'), time_rnd)
+ 
++@unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS")
++class TestTimeWeaklinking(unittest.TestCase):
++    # These test cases verify that weak linking support on macOS works
++    # as expected. These cases only test new behaviour introduced by weak linking,
++    # regular behaviour is tested by the normal test cases.
++    #
++    # See the section on Weak Linking in Mac/README.txt for more information.
++    def test_clock_functions(self):
++        import sysconfig
++        import platform
++
++        config_vars = sysconfig.get_config_vars()
++        var_name = "HAVE_CLOCK_GETTIME"
++        if var_name not in config_vars or not config_vars[var_name]:
++            raise unittest.SkipTest(f"{var_name} is not available")
++
++        mac_ver = tuple(int(x) for x in platform.mac_ver()[0].split("."))
++
++        clock_names = [
++            "CLOCK_MONOTONIC", "clock_gettime", "clock_gettime_ns", "clock_settime",
++            "clock_settime_ns", "clock_getres"]
++        
++        if mac_ver >= (10, 12):
++            for name in clock_names:
++                self.assertTrue(hasattr(time, name), f"time.{name} is not available")
++
++        else:
++            for name in clock_names:
++                self.assertFalse(hasattr(time, name), f"time.{name} is available")
++
+ 
+ if __name__ == "__main__":
+     unittest.main()
+diff -pur a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py
+--- a/Lib/test/test_unicode.py	2020-10-05 17:07:58.000000000 +0200
++++ b/Lib/test/test_unicode.py	2020-11-05 14:28:53.000000000 +0100
+@@ -2523,11 +2523,13 @@ class CAPITest(unittest.TestCase):
      def test_from_format(self):
          support.import_module('ctypes')
          from ctypes import (

         name = "PyUnicode_FromFormat"
         _PyUnicode_FromFormat = getattr(pythonapi, name)
+        _PyUnicode_FromFormat.argtypes = (c_char_p,)
+        _PyUnicode_FromFormat.variadic = True
         _PyUnicode_FromFormat.restype = py_object
 
         def PyUnicode_FromFormat(format, *args):
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index b10b867..65d0374 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -3320,6 +3320,35 @@ PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
     }
 }
 
+static int
+PyCFuncPtr_set_variadic(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
+{
+    StgDictObject *dict = PyObject_stgdict((PyObject *)self);
+    assert(dict);
+    int r = PyObject_IsTrue(ob);
+    if (r == 1) {
+        dict->flags |= FUNCFLAG_VARIADIC;
+        return 0;
+    } else if (r == 0) {
+        dict->flags &= ~FUNCFLAG_VARIADIC;
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+static PyObject *
+PyCFuncPtr_get_variadic(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
+{
+    StgDictObject *dict = PyObject_stgdict((PyObject *)self);
+    assert(dict); /* Cannot be NULL for PyCFuncPtrObject instances */
+    if (dict->flags & FUNCFLAG_VARIADIC)
+        Py_RETURN_TRUE;
+    else
+        Py_RETURN_FALSE;
+}
+
+
 static int
 PyCFuncPtr_set_argtypes(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
 {
@@ -3365,6 +3394,8 @@ static PyGetSetDef PyCFuncPtr_getsets[] = {
     { "argtypes", (getter)PyCFuncPtr_get_argtypes,
       (setter)PyCFuncPtr_set_argtypes,
       "specify the argument types", NULL },
+    { "variadic", (getter)PyCFuncPtr_get_variadic, (setter)PyCFuncPtr_set_variadic,
+      "specify if function takes variable number of arguments", NULL },
     { NULL, NULL }
 };
 
@@ -5833,6 +5864,7 @@ PyInit__ctypes(void)
     PyModule_AddObject(m, "FUNCFLAG_USE_ERRNO", PyLong_FromLong(FUNCFLAG_USE_ERRNO));
     PyModule_AddObject(m, "FUNCFLAG_USE_LASTERROR", PyLong_FromLong(FUNCFLAG_USE_LASTERROR));
     PyModule_AddObject(m, "FUNCFLAG_PYTHONAPI", PyLong_FromLong(FUNCFLAG_PYTHONAPI));
+    PyModule_AddObject(m, "FUNCFLAG_VARIADIC", PyLong_FromLong(FUNCFLAG_VARIADIC));
     PyModule_AddStringConstant(m, "__version__", "1.1.0");
 
     PyModule_AddObject(m, "_memmove_addr", PyLong_FromVoidPtr(memmove));
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index 4027bdb..9b0ab9d 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -86,6 +86,10 @@
 #define DONT_USE_SEH
 #endif
 
+#if defined(__APPLE__) && __arm64__
+#define HAVE_FFI_PREP_CIF_VAR 1
+#endif
+
 #define CTYPES_CAPSULE_NAME_PYMEM "_ctypes pymem"
 
 static void pymem_destructor(PyObject *ptr)
@@ -813,7 +817,8 @@ static int _call_function_pointer(int flags,
                                   ffi_type **atypes,
                                   ffi_type *restype,
                                   void *resmem,

             if not os.path.exists(ffi_h):
                 ffi_inc = None
                 print('Header file {} does not exist'.format(ffi_h))
-        ffi_lib = None
-        if ffi_inc is not None:
+        if ffi_lib is None and ffi_inc:
             for lib_name in ('ffi', 'ffi_pic'):
                 if (self.compiler.find_library_file(self.lib_dirs, lib_name)):
