From 69f73f4f6338aec8d83ae74fedb75451932add6c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 19 Oct 2026 09:50:13 -0700 Subject: [PATCH 1/3] Add initial 3.16 support. --- .github/workflows/ci.yaml | 9 +- Makefile | 2 +- patch/Python/Python.patch | 11359 ++++++++++++++++++++++++++++++------ 3 files changed, 9718 insertions(+), 1652 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fe4bd220..2eb05781 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -148,9 +148,7 @@ jobs: platform: ["macOS", "iOS"] include: - briefcase-run-args: - - platform: iOS - briefcase-run-args: ' -d "iPhone 17e::iOS 26.5"' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -201,12 +199,7 @@ jobs: strategy: fail-fast: false matrix: - platform: ["iOS", "tvOS", "visionOS"] - include: - - platform: "iOS" - testbed-args: '--simulator "iPhone 17e,arch=arm64,OS=26.5"' - - platform: "visionOS" - testbed-args: '--simulator "Apple Vision Pro,arch=arm64,OS=26.5"' + platform: ["iOS", "tvOS", "watchOS", "visionOS"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/Makefile b/Makefile index b9ffe7db..030c8640 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ BUILD_NUMBER=custom # of a release cycle, as official binaries won't be published. # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.15.0 +PYTHON_VERSION=3.16.0a1 PYTHON_PKG_VERSION=$(PYTHON_VERSION) PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 2abd6403..367db3d9 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,8 +1,1108 @@ +diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst +index 609b5e885b6..75a4adc810f 100644 +--- a/Doc/c-api/function.rst ++++ b/Doc/c-api/function.rst +@@ -14,6 +14,10 @@ + + The C structure used for functions. + ++ In the free-threaded build, direct access to mutable fields requires ++ synchronization if another thread may modify the function. See ++ :ref:`freethreading-extensions-howto`. ++ + + .. c:var:: PyTypeObject PyFunction_Type + +@@ -71,6 +75,11 @@ + This is normally a :class:`string ` containing the module name, + but can be set to any other object by Python code. + ++ In the free-threaded build, if the attribute may be modified concurrently, ++ convert the borrowed reference to a strong reference with :c:func:`Py_XNewRef` ++ immediately, before calling any other Python API. The same applies to ++ :c:func:`PyFunction_GET_MODULE`. ++ + + .. c:function:: PyObject* PyFunction_GetDefaults(PyObject *op) + +diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst +index 01de4e6bd85..b0155b444b7 100644 +--- a/Doc/c-api/init_config.rst ++++ b/Doc/c-api/init_config.rst +@@ -1439,6 +1439,10 @@ + + See also the :c:member:`~PyConfig.filesystem_errors` member. + ++ See also :c:func:`PyUnicode_EncodeFSDefault` and ++ :ref:`related functions ` ++ for encoding paths at runtime. ++ + .. c:member:: wchar_t* filesystem_errors + + :term:`Filesystem error handler `: +diff --git a/Doc/c-api/interp-lifecycle.rst b/Doc/c-api/interp-lifecycle.rst +index a43298d44bd..7cfde5cdae6 100644 +--- a/Doc/c-api/interp-lifecycle.rst ++++ b/Doc/c-api/interp-lifecycle.rst +@@ -514,7 +514,7 @@ + Return the version of this Python interpreter. This is a string that looks + something like :: + +- "3.15.0rc2 (3.15.0~rc2-1.fc44.x86_64, Sep 3 2026, 00:00:00) [GCC 16.2.1 20260819 (Red Hat 16.2.1-2)]" ++ "3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]" + + .. index:: single: version (in module sys) + +diff --git a/Doc/c-api/reflection.rst b/Doc/c-api/reflection.rst +index 54fd5a064aa..37213dff216 100644 +--- a/Doc/c-api/reflection.rst ++++ b/Doc/c-api/reflection.rst +@@ -96,6 +96,12 @@ + Return the name of *func* if it is a function, class or instance object, else the + name of *func*\s type. + ++ For a Python function or method, the returned pointer refers to the ++ function's name and must not be retained across operations that may change ++ that name. In the free-threaded build, copy the string before detaching ++ the thread state or executing Python code if the name may be modified ++ concurrently. ++ + + .. c:function:: const char* PyEval_GetFuncDesc(PyObject *func) + +diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst +index 80a23d5c491..8db6ef22b00 100644 +--- a/Doc/c-api/unicode.rst ++++ b/Doc/c-api/unicode.rst +@@ -812,6 +812,8 @@ + used for ``strict``. + + ++.. _capi-filesystem-encoding: ++ + File System Encoding + """""""""""""""""""" + +diff --git a/Doc/howto/remote_debugging.rst b/Doc/howto/remote_debugging.rst +index 1a8e8593e8a..f5f45c399ae 100644 +--- a/Doc/howto/remote_debugging.rst ++++ b/Doc/howto/remote_debugging.rst +@@ -16,7 +16,7 @@ + + * Set the :envvar:`PYTHON_DISABLE_REMOTE_DEBUG` environment variable to ``1`` before + starting the interpreter. +-* Use the :option:`-X disable_remote_debug` command-line option. ++* Use the :option:`-X disable-remote-debug` command-line option. + * Compile Python with the :option:`--without-remote-debug` build flag. + + .. _permission-requirements: +@@ -717,7 +717,7 @@ + ------------------------------------------- + + The environment variable :envvar:`PYTHON_DISABLE_REMOTE_DEBUG` (and the +-equivalent :option:`-X disable_remote_debug` flag) allows operators to disable ++equivalent :option:`-X disable-remote-debug` flag) allows operators to disable + the in-process side of the protocol as a **defence-in-depth** measure. This + may be useful in hardened or sandboxed deployment environments where no + debugging or profiling of the process is expected and reducing attack surface +diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst +index fb0ffc73b48..0dc1481aeff 100644 +--- a/Doc/library/calendar.rst ++++ b/Doc/library/calendar.rst +@@ -169,10 +169,10 @@ + the specified width, representing an empty day. The *weekday* parameter + is unused. + +- .. method:: formatweek(theweek, w=0) ++ .. method:: formatweek(theweek, width) + +- Return a single week in a string with no newline. If *w* is provided, it +- specifies the width of the date columns, which are centered. Depends ++ Return a single week in a string with no newline. *width* specifies ++ the width of the date columns, which are centered. Depends + on the first weekday as specified in the constructor or set by the + :meth:`setfirstweekday` method. + +@@ -200,7 +200,7 @@ + :meth:`setfirstweekday` method. + + +- .. method:: formatmonthname(theyear, themonth, width=0, withyear=True) ++ .. method:: formatmonthname(theyear, themonth, width, withyear=True) + + Return a string representing the month's name centered within the + specified *width*. If *withyear* is ``True``, include the year in the +diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst +index 926b45917de..1b5345dc146 100644 +--- a/Doc/library/dbm.rst ++++ b/Doc/library/dbm.rst +@@ -100,7 +100,8 @@ + + Key and values are always stored as :class:`bytes`. This means that when + strings are used they are implicitly converted to the default encoding before +-being stored. ++being stored. For detailed information about accepted types, see ++:ref:`dbm-key-value-types`. + + These objects also support being used in a :keyword:`with` statement, which + will automatically close them when done. +@@ -121,6 +122,47 @@ + :meth:`!clear` methods are now available for all :mod:`!dbm` backends. + + ++.. _dbm-key-value-types: ++ ++Key and value types ++------------------- ++ ++The accepted types for keys and values vary by backend. Keys and values are ++handled the same way, except where noted below: ++ ++* **Traditional backends:** ++ ++ * :mod:`dbm.gnu` and :mod:`dbm.ndbm`: Accept :class:`str` and :class:`bytes` ++ objects. ++ * :mod:`dbm.dumb`: Accepts :class:`str` and :class:`bytes` objects; ++ :class:`bytearray` is acceptable as a value, but not as a key. ++ ++* **SQLite backend** (:mod:`dbm.sqlite3`): Accepts :class:`str`, :class:`bytes`, ++ :class:`int`, :class:`float`, :class:`bool`, :class:`bytearray`, ++ :class:`memoryview`, and :class:`array.array` objects. ++ ++**Storage format:** ++ ++All keys and values are stored as :class:`bytes` objects in the database. ++When retrieving, you'll always get bytes back, regardless of the original ++type stored. ++ ++**Type conversion examples:** ++ ++* ``db['key'] = 'string'`` stored as ``b'string'`` ++* ``db['key'] = bytearray(b'data')`` stored as ``b'data'`` ++ (:mod:`dbm.dumb` and :mod:`dbm.sqlite3` only) ++* ``db['key'] = 42`` stored as ``b'42'`` (:mod:`dbm.sqlite3` only) ++* ``db['key'] = 3.14`` stored as ``b'3.14'`` (:mod:`dbm.sqlite3` only) ++* ``db['key'] = True`` stored as ``b'1'`` (:mod:`dbm.sqlite3` only) ++* ``db['key'] = False`` stored as ``b'0'`` (:mod:`dbm.sqlite3` only) ++* ``db['key'] = memoryview(b'data')`` stored as ``b'data'`` ++ (:mod:`dbm.sqlite3` only) ++* ``db['key'] = array.array('i', [1, 2, 3])`` stored as bytes (for example, on ++ little-endian: ``b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'``) ++ (:mod:`dbm.sqlite3` only) ++ ++ + The following example records some hostnames and a corresponding title, and + then prints out the contents of the database:: + +@@ -142,8 +184,9 @@ + # Often-used methods of the dict interface work too. + print(db.get('python.org', b'not present')) + +- # Storing a non-string key or value will raise an exception (most +- # likely a TypeError). ++ # Storing a non-string key or value behavior depends on the backend: ++ # - Traditional backends (ndbm, gnu, dumb) will raise a TypeError ++ # - The sqlite3 backend accepts it and converts to bytes + db['www.yahoo.com'] = 4 + + # db is automatically closed when leaving the with statement. +diff --git a/Doc/library/http.client.rst b/Doc/library/http.client.rst +index 4002fc697ce..ab557f1805e 100644 +--- a/Doc/library/http.client.rst ++++ b/Doc/library/http.client.rst +@@ -370,7 +370,8 @@ + proxy server). + + The *headers* argument should be a mapping of extra HTTP headers to send with +- the CONNECT request. ++ the CONNECT request. Non-string header values are converted to strings via ++ :func:`str`. + + As HTTP/1.1 is used for HTTP CONNECT tunnelling request, `as per the RFC + `_, a HTTP ``Host:`` +diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst +index 5b9741bdbca..0399b55721c 100644 +--- a/Doc/library/mailbox.rst ++++ b/Doc/library/mailbox.rst +@@ -15,6 +15,12 @@ + :class:`~email.message.Message` class with format-specific state and behavior. + Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. + ++.. warning:: ++ ++ The :mod:`!mailbox` module is designed for use in trusted environments, ++ where data from untrusted sources is never passed to the module's API ++ calls and only trusted users have access to the filesystems accessible ++ to the module. + + .. seealso:: + +diff --git a/Doc/library/os.rst b/Doc/library/os.rst +index 0ac18e9dd02..3f0b91af4d0 100644 +--- a/Doc/library/os.rst ++++ b/Doc/library/os.rst +@@ -1479,7 +1479,7 @@ + Return a pair of file descriptors ``(r, w)`` usable for reading and writing, + respectively. + +- .. availability:: Unix, macOS >= 27.0, not WASI, not iOS. ++ .. availability:: Unix, macOS >= 27.0, iOS >= 27.0, not WASI. + + .. versionadded:: 3.3 + +@@ -2799,14 +2799,23 @@ + may be converted to an absolute pathname using + ``os.path.join(os.path.dirname(path), result)``. + +- If the *path* is a string object (directly or indirectly through a +- :class:`PathLike` interface), the result will also be a string object, +- and the call may raise a UnicodeDecodeError. If the *path* is a bytes +- object (direct or indirectly), the result will be a bytes object. +- + This function can also support :ref:`paths relative to directory descriptors + `. + ++ On Linux, Android, macOS and iOS, *path* can also be a file descriptor referring ++ to a symbolic link. In that case, *dir_fd* must be ``None``. ++ (On Linux and Android, such a file descriptor must be obtained through ++ :func:`os.open` with ``os.O_RDONLY | os.O_PATH | os.O_NOFOLLOW``. ++ On macOS and iOS, :func:`os.open` must be called with ++ ``os.O_RDONLY | os.O_SYMLINK``.) ++ On other operating systems, a ``NotImplementedError`` is raised if *path* ++ is an integer. ++ ++ If the *path* is a string object (directly or indirectly through a ++ :class:`PathLike` interface) or a file descriptor, the result will be a ++ string object, and the call may raise a UnicodeDecodeError. If the *path* ++ is a bytes object (direct or indirectly), the result will be a bytes object. ++ + When trying to resolve a path that may contain links, use + :func:`~os.path.realpath` to properly handle recursion and platform + differences. +@@ -2829,6 +2838,10 @@ + substitution path (which typically includes ``\\?\`` prefix) rather + than the optional "print name" field that was previously returned. + ++ .. versionchanged:: next ++ Accepts file descriptors pointing to symbolic links as *path* on ++ Linux, Android, macOS and iOS. ++ + .. function:: remove(path, *, dir_fd=None) + + Remove (delete) the file *path*. If *path* is a directory, an +diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst +index b447e4253a8..d1b23dfc52b 100644 +--- a/Doc/library/sys.rst ++++ b/Doc/library/sys.rst +@@ -418,6 +418,29 @@ + .. versionadded:: 3.11 + + ++.. data:: _wasi_info ++ ++ An object containing information about the build on the WebAssembly WASI ++ platform. The object is provisional and may change in the future. ++ ++ .. attribute:: _wasi_info.wasi_version ++ ++ The WASI target version as a string: ``'p1'``, ``'p2'``, or ``'p3'``. ++ ++ .. attribute:: _wasi_info.wasi_sdk_version ++ ++ The full WASI SDK version as a string, such as ``'34.0'``. ++ ++ .. attribute:: _wasi_info.cooperative_threads ++ ++ ``True`` if wasi-libc was compiled with cooperative multithreading ++ support, ``False`` otherwise. ++ ++ .. availability:: WASI. ++ ++ .. versionadded:: next ++ ++ + .. data:: pycache_prefix + + If this is set (not ``None``), Python will write bytecode-cache ``.pyc`` +diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst +index f1c7dbbab40..1b2c328f9ca 100644 +--- a/Doc/library/tarfile.rst ++++ b/Doc/library/tarfile.rst +@@ -1137,17 +1137,13 @@ + Return the modified ``TarInfo`` member. + + .. warning:: +- This filter does not check :attr:`TarInfo.linkname`. This means it does +- not limit where a symbolic link member points. An absolute symbolic-link +- target will remain absolute, and a relative target may resolve outside the +- extraction destination. The destination path protection described above +- concerns only where the archive member itself is written, not where a +- symbolic link resolves. The stricter :func:`data_filter` rejects these +- unsafe link targets, raising :class:`~tarfile.AbsoluteLinkError` for +- absolute link targets or :class:`~tarfile.LinkOutsideDestinationError` +- for link targets outside the destination. +- +- .. versionchanged:: 3.16 ++ ++ This filter does not check link targets (:attr:`TarInfo.linkname`). ++ It allows benign cases (like links to ``/dev/null``), but it also allows ++ links (symbolic and hard) to arbitrary files on the filesystem, ++ potentially granting unintended access to these files. ++ ++ .. versionchanged:: next + + Filenames containing ``..`` components are now normalized. + +diff --git a/Doc/library/test.rst b/Doc/library/test.rst +index 765811eba32..b4809fdd66c 100644 +--- a/Doc/library/test.rst ++++ b/Doc/library/test.rst +@@ -165,11 +165,13 @@ + the hood, it uses :mod:`!test.regrtest`; the call :program:`python -m + test.regrtest` used in previous Python versions still works. Running the + script by itself automatically starts running all regression tests in the +-:mod:`!test` package. It does this by finding all modules in the package whose +-name starts with ``test_``, importing them, and executing the function +-:func:`test_main` if present or loading the tests via +-unittest.TestLoader.loadTestsFromModule if ``test_main`` does not exist. The +-names of tests to execute may also be passed to the script. Specifying a single ++:mod:`!test` package. It does this by finding modules and packages whose names ++start with ``test_``, importing each one, and loading its tests with ++:meth:`unittest.TestLoader.loadTestsFromModule`. See the ++:ref:`load_tests protocol ` for how a module or package ++can customize what is loaded. Defining a module-level ``test_main()`` function ++is not supported and causes the test to fail. The names of tests to ++execute may also be passed to the script. Specifying a single + regression test (:program:`python -m test test_spam`) will minimize output and + only print whether the test passed or failed. + +@@ -882,9 +884,11 @@ + + .. function:: reap_children() + +- Use this at the end of ``test_main`` whenever sub-processes are started. +- This will help ensure that no extra children (zombies) stick around to +- hog resources and create problems when looking for refleaks. ++ Call this from :meth:`~unittest.TestCase.setUp`, ++ :meth:`~unittest.TestCase.tearDown`, or ``tearDownModule()`` in tests ++ that start subprocesses. This will help ensure that no extra children ++ (zombies) stick around to hog resources and create problems when looking ++ for refleaks. + + + .. function:: get_attribute(obj, name) +diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst +index 47cd7343400..653a91c5cbe 100644 +--- a/Doc/library/tkinter.rst ++++ b/Doc/library/tkinter.rst +@@ -773,34 +773,76 @@ + + Many widget options documented in the reference + accept values of a small number of common types, described here. ++The standard options and their types are described in the ++:manpage:`options(3tk)` manual page. + + anchor + Legal values are points of the compass: ``"n"``, ``"ne"``, ``"e"``, ``"se"``, + ``"s"``, ``"sw"``, ``"w"``, ``"nw"``, and also ``"center"``. ++ The *labelanchor* option of :class:`LabelFrame` and ++ :class:`ttk.LabelFrame ` ++ accepts also ``"en"``, ``"es"``, ``"wn"`` and ``"ws"``. ++ The *sticky* option of the grid geometry manager accepts a combination ++ of the compass directions, such as ``"nsew"``, or an empty string. + + bitmap + There are ten built-in, named bitmaps: ``'error'``, ``'gray12'``, + ``'gray25'``, ``'gray50'``, ``'gray75'``, ``'hourglass'``, ``'info'``, +- ``'questhead'``, ``'question'``, ``'warning'``. To specify an X bitmap +- filename, give the full path to the file, preceded with an ``@``, as in +- ``"@/usr/contrib/bitmap/gumby.bit"``. ++ ``'questhead'``, ``'question'``, ``'warning'``. ++ On macOS the following bitmaps are available as well: ++ ``'application'``, ``'accessory'``, ``'folder'``, ``'pfolder'``, ++ ``'trash'``, ``'floppy'``, ``'ramdisk'``, ``'cdrom'``, ``'preferences'``, ++ ``'querydoc'``, ``'stop'``, ``'note'`` and ``'caution'``. ++ To specify an X bitmap file, give the full path to the file, ++ preceded with an ``@``, as in ``"@/usr/contrib/bitmap/gumby.bit"``. + + boolean +- You can pass integers 0 or 1 or the strings ``"yes"`` or ``"no"``. ++ You can pass the integers 0 or 1, ``True`` or ``False``, or the strings ++ ``"0"``, ``"1"``, ``"yes"``, ``"no"``, ``"true"``, ``"false"``, ``"on"`` ++ or ``"off"``. ++ The strings are matched ignoring case, ++ and an unambiguous prefix, such as ``"t"``, is enough. ++ Any non-zero integer means true. + + callback +- This is any Python function that takes no arguments. For example:: ++ This is any callable object. ++ Most callbacks take no arguments. For example:: + + def print_it(): + print("hi there") + fred["command"] = print_it + ++ Some options call it with arguments, ++ such as the *command* option of :class:`Scale`, ++ which is called with the new value. ++ + color +- Colors can be given as the names of X colors in the rgb.txt file, or as strings +- representing RGB values in 4 bit: ``"#RGB"``, 8 bit: ``"#RRGGBB"``, 12 bit: +- ``"#RRRGGGBBB"``, or 16 bit: ``"#RRRRGGGGBBBB"`` ranges, where R,G,B here +- represent any legal hex digit. See the :manpage:`colors(3tk)` man page for +- the list of named colors. ++ Colors can be given as symbolic names, such as ``"red"`` or ``"light blue"``, ++ or as strings representing RGB values in 4 bit: ``"#RGB"``, 8 bit: ++ ``"#RRGGBB"``, 12 bit: ``"#RRRGGGBBB"``, or 16 bit: ``"#RRRRGGGGBBBB"`` ++ ranges, where R,G,B here represent any legal hex digit. ++ Tk provides a portable set of symbolic names available on all platforms; ++ they are matched ignoring case and spaces, ++ so ``"light blue"``, ``"Light Blue"`` and ``"lightblue"`` ++ are the same color. ++ The full list of names, including the platform-specific names ++ of the current system colors ++ (for example ``"systemButtonFace"`` on Windows ++ or ``"systemWindowBody"`` on macOS), ++ is given in the :manpage:`colors(3tk)` manual page. ++ :meth:`~tkinter.Misc.winfo_rgb` returns ++ the red, green and blue components of a color. ++ ++compound ++ Determines how to combine an image and a text displayed in a widget. ++ Legal values are ``"none"`` (display the image if there is one, ++ otherwise the text), ``"text"``, ``"image"``, ++ ``"center"`` (display the text in the center of the image) and ++ ``"top"``, ``"bottom"``, ``"left"``, ``"right"`` ++ (display the image above, below, to the left of or to the right ++ of the text). ++ The themed widgets accept all these values, ++ the classic widgets accept all except ``"text"`` and ``"image"``. + + cursor + The name of the mouse cursor to display while the pointer is over the widget. +@@ -836,19 +878,32 @@ + ``(family, size, *styles)`` (or as the equivalent string + ``"Courier 10 bold"``). Font sizes with positive numbers are measured in + points; sizes with negative numbers are measured in pixels. +- +-geometry +- This is a string of the form ``widthxheight``, where width and height are +- measured in pixels for most widgets (in characters for widgets displaying text). +- For example: ``fred["geometry"] = "200x100"``. ++ The name of a named font, such as ``"TkDefaultFont"``, ++ a :class:`tkinter.font.Font` instance, ++ or, on X11, an XLFD font name can be used as well. + + justify + Legal values are the strings: ``"left"``, ``"center"``, and ``"right"``. + ++orient ++ Legal values are ``"horizontal"`` and ``"vertical"``. ++ ++padding ++ This is a legal distance (see above), applied on all sides, ++ or a sequence of up to four distances ++ specifying the left, top, right and bottom padding. ++ If fewer than four are specified, ++ the bottom padding defaults to the top one, ++ the right one to the left one, and the top one to the left one. ++ It is only supported by the themed widgets. ++ + region +- This is a string with four space-delimited elements, each of which is a legal +- distance (see above). For example: ``"2 3 4 5"`` and ``"3i 2i 4.5i 2i"`` and +- ``"3c 2c 4c 10.43c"`` are all legal regions. ++ Used for the *scrollregion* option of :class:`Canvas`. ++ This is a string with four space-delimited elements, ++ each of which is a legal distance (see above), ++ or a sequence of four such elements. ++ For example: ``"2 3 4 5"``, ``(2, 3, 4, 5)``, ``"3i 2i 4.5i 2i"`` and ++ ``"3c 2c 4c 10.43c"`` are all legal regions. + + relief + Determines what the border style of a widget will be. Legal values are: +@@ -859,8 +914,19 @@ + This is almost always the :meth:`!set` method of some scrollbar widget, but can + be any widget method that takes a single argument. + ++state ++ Legal values are ``"normal"``, ``"disabled"`` and, depending on the widget, ++ also ``"active"``, ``"hidden"`` (for :class:`Canvas` items) ++ or ``"readonly"`` (for :class:`Entry`, :class:`Spinbox` ++ and the themed widgets). ++ The themed widgets have in addition a more general set of states, ++ which is manipulated with ++ :meth:`ttk.Widget.state `. ++ + wrap +- Must be one of: ``"none"``, ``"char"``, or ``"word"``. ++ Must be one of ``"none"``, ``"char"`` or ``"word"`` ++ for the :class:`Text` widget and its tags; ++ for a tag an empty string means to use the widget's value. + + .. _Bindings-and-Events: + +@@ -1110,7 +1176,7 @@ + Interpret the string *s* as a Tcl boolean and return the corresponding + :class:`bool`. + Tcl accepts values such as ``'1'``, ``'0'``, ``'yes'``, ``'no'``, +- ``'true'`` and ``'false'``. ++ ``'true'``, ``'false'``, ``'on'`` and ``'off'``. + Raise :exc:`ValueError` if *s* is not a valid boolean. + + .. method:: getdouble(s) +@@ -2143,6 +2209,9 @@ + + Return the geometry of the widget, in the form ``widthxheight+x+y``. + All dimensions are in pixels. ++ The position is relative to the parent widget; ++ use :meth:`winfo_rootx` and :meth:`winfo_rooty` ++ for the position on the screen. + An offset can be negative; see :meth:`~Wm.geometry`. + + .. method:: winfo_height() +@@ -3369,6 +3438,8 @@ + Shift the view left or right by *number* units. + *what* is either ``'units'`` or ``'pages'``; a negative *number* scrolls + left and a positive one scrolls right. ++ The :class:`Text` widget accepts also ``'pixels'``, ++ and then *number* can be a screen distance, such as ``'1i'``. + + + .. class:: YView() +@@ -3399,6 +3470,8 @@ + Shift the view up or down by *number* units. + *what* is either ``'units'`` or ``'pages'``; a negative *number* scrolls + up and a positive one scrolls down. ++ The :class:`Text` widget accepts also ``'pixels'``, ++ and then *number* can be a screen distance, such as ``'1i'``. + + + .. class:: BaseWidget(master, widgetName, cnf={}, kw={}, extra=()) +@@ -3828,8 +3901,7 @@ + + .. note:: + +- Tk 8.6 added the *angle* option and Tk 9.0 added the *underline* +- option for ``text`` items. ++ Tk 8.6 added the *angle* option for ``text`` items. + + .. method:: coords(tagOrId) + coords(tagOrId, coordList, /) +@@ -5226,10 +5298,10 @@ + *increment* options, or an explicit list of strings given by the *values* + option (which takes precedence over the range). + Each time an arrow is invoked the *command* callback, if any, is called; the +- *wrap* option controls whether stepping past either end of the range wraps +- around to the other end; the *format* option specifies how numeric values +- are formatted; and the *validate* option enables validation of the entered +- text. ++ boolean *wrap* option controls whether stepping past either end of the range ++ wraps around to the other end; ++ the *format* option specifies how numeric values are formatted; ++ and the *validate* option enables validation of the entered text. + Inherits from :class:`Widget` and :class:`XView`. + + With a non-integer *increment*, see :ref:`numeric values and the locale +@@ -5631,7 +5703,7 @@ + baseline, for superscripts and subscripts. + + *underline* +- Whether to underline the text. ++ Whether to underline the text (a boolean). + + *underlinefg* + The color of the underline; it defaults to the text color. +@@ -5789,7 +5861,8 @@ + Embed a window (any widget) at *index*. + The options, given in *cnf* and *kw*, include *window* (the widget to + embed), *create* (a callback that creates the widget on demand), *align*, +- *stretch*, *padx* and *pady*. ++ the boolean *stretch* (whether the widget is stretched vertically to the ++ height of the line), *padx* and *pady*. + The embedded widget must be a descendant of the text widget's parent. + + .. method:: window_cget(index, option) +@@ -6665,7 +6738,7 @@ + Convert the Tcl boolean string *s* (one of ``'1'``, ``'true'``, ``'yes'``, + ``'on'`` and similar, or their false counterparts) to a Python + :class:`bool`. +- Raise :exc:`TclError` for an invalid value. ++ Raise :exc:`ValueError` for an invalid value. + + .. function:: getdouble(s) + +diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst +index aa48cea357c..8f167eb3524 100644 +--- a/Doc/library/traceback.rst ++++ b/Doc/library/traceback.rst +@@ -44,10 +44,6 @@ + necessary for later formatting without holding references to actual exception + and traceback objects. + +-.. versionadded:: 3.13 +- Output is colorized by default and can be +- :ref:`controlled using environment variables `. +- + + Module-Level Functions + ---------------------- +diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst +index bd75afc18d0..e0ad7945a8e 100644 +--- a/Doc/using/cmdline.rst ++++ b/Doc/using/cmdline.rst +@@ -652,7 +652,7 @@ + + .. versionadded:: 3.13 + +- * ``-X disable_remote_debug`` disables the remote debugging support as described ++ * ``-X disable-remote-debug`` disables the remote debugging support as described + in :pep:`768`. This includes both the functionality to schedule code for + execution in another process and the functionality to receive code for + execution in the current process. +@@ -1313,7 +1313,7 @@ + to schedule code for execution in another process and the functionality to + receive code for execution in the current process. + +- See also the :option:`-X disable_remote_debug` command-line option. ++ See also the :option:`-X disable-remote-debug` command-line option. + + .. versionadded:: 3.14 + +diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst +index 637a80f42d4..a8350980767 100644 +--- a/Doc/using/windows.rst ++++ b/Doc/using/windows.rst +@@ -283,8 +283,8 @@ + Otherwise, they will be left. If no tags are provided with ``--update``, all + installs managed by the Python install manager will be updated if newer versions + are available. Updates will remove any modifications made to the install, +-including globally installed packages, but virtual environments will continue to +-work. ++including globally installed packages, but will leave ``site-packages`` and ++``Scripts`` directories as is. Virtual environments are also unaffected. + + Passing ``--dry-run`` will generate output and logs, but will not modify any + installs. +@@ -399,8 +399,9 @@ + This section will describe the defaults, but be aware that modified or + overridden installs may resolve settings differently. + +-A global configuration file may be configured by an administrator, and would be +-read first. The user configuration file is stored at ++A global configuration file may be configured by an ++:ref:`administrator ` ++and would be read first. The user configuration file is stored at + :file:`%AppData%\\Python\\pymanager.json` + (note that this location is under ``Roaming``, not ``Local``) and is read next, + overwriting any settings from earlier files. An additional configuration file +diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst +index d9e12df91ad..a1d7d1eaae7 100644 +--- a/Doc/whatsnew/3.16.rst ++++ b/Doc/whatsnew/3.16.rst +@@ -531,6 +531,10 @@ + now raises :exc:`PermissionError` instead of the functions being unavailable. + (Contributed by Md Arif in :gh:`152936`.) + ++* :func:`os.readlink` now accepts a file descriptor referring to a symlink on ++ Linux, Android, macOS and iOS. ++ (Contributed by OOTS in :gh:`157899`.) ++ + + pydoc + ----- +diff --git a/Include/cpython/funcobject.h b/Include/cpython/funcobject.h +index 9e1599a7648..bcdd9df561d 100644 +--- a/Include/cpython/funcobject.h ++++ b/Include/cpython/funcobject.h +@@ -98,7 +98,12 @@ + #define PyFunction_GET_GLOBALS(func) PyFunction_GET_GLOBALS(_PyObject_CAST(func)) + + static inline PyObject* PyFunction_GET_MODULE(PyObject *func) { ++#ifdef Py_GIL_DISABLED ++ return (PyObject *)_Py_atomic_load_ptr_acquire( ++ &_PyFunction_CAST(func)->func_module); ++#else + return _PyFunction_CAST(func)->func_module; ++#endif + } + #define PyFunction_GET_MODULE(func) PyFunction_GET_MODULE(_PyObject_CAST(func)) + +@@ -118,7 +123,12 @@ + #define PyFunction_GET_CLOSURE(func) PyFunction_GET_CLOSURE(_PyObject_CAST(func)) + + static inline PyObject* PyFunction_GET_ANNOTATIONS(PyObject *func) { ++#ifdef Py_GIL_DISABLED ++ return (PyObject *)_Py_atomic_load_ptr_acquire( ++ &_PyFunction_CAST(func)->func_annotations); ++#else + return _PyFunction_CAST(func)->func_annotations; ++#endif + } + #define PyFunction_GET_ANNOTATIONS(func) PyFunction_GET_ANNOTATIONS(_PyObject_CAST(func)) + +diff --git a/Include/cpython/object.h b/Include/cpython/object.h +index 12d2e614623..da94a640fc8 100644 +--- a/Include/cpython/object.h ++++ b/Include/cpython/object.h +@@ -469,8 +469,12 @@ + #elif defined(__arm__) && __ARM_ARCH >= 7 + __asm__ ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tid)); + #elif defined(__aarch64__) && defined(__APPLE__) +- __asm__ ("mrs %0, tpidrro_el0" : "=r" (tid)); +-#elif defined(__aarch64__) ++ // Use a 64-bit temporary: on arm64_32 (ILP32), uintptr_t is 32 bits ++ // but system registers are 64 bits. ++ uint64_t tp; ++ __asm__ ("mrs %0, tpidrro_el0" : "=r" (tp)); ++ tid = (uintptr_t)tp; ++#elif defined (__aarch64__) + __asm__ ("mrs %0, tpidr_el0" : "=r" (tid)); + #elif defined(__powerpc64__) + #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) +diff --git a/Include/errcode.h b/Include/errcode.h +index dac5cf068c9..10cd9b74371 100644 +--- a/Include/errcode.h ++++ b/Include/errcode.h +@@ -38,6 +38,7 @@ + #define E_BADSINGLE 27 /* Ill-formed single statement input */ + #define E_INTERACT_STOP 28 /* Interactive mode stopped tokenization */ + #define E_COLUMNOVERFLOW 29 /* Column offset overflow */ ++#define E_INCOMPLETE 30 /* An unfinished token in partial input */ + + #ifdef __cplusplus + } +diff --git a/Include/internal/mimalloc/mimalloc/internal.h b/Include/internal/mimalloc/mimalloc/internal.h +index a7daa3a40a4..423693f33e1 100644 +--- a/Include/internal/mimalloc/mimalloc/internal.h ++++ b/Include/internal/mimalloc/mimalloc/internal.h +@@ -23,10 +23,6 @@ + #define mi_trace_message(...) + #endif + +-#if defined(__EMSCRIPTEN__) && !defined(__wasi__) +-#define __wasi__ +-#endif +- + #if defined(__cplusplus) + #define mi_decl_externc extern "C" + #else +@@ -34,7 +30,7 @@ + #endif + + // pthreads +-#if !defined(_WIN32) && !defined(__wasi__) ++#if !defined(_WIN32) && !defined(__wasm__) + #define MI_USE_PTHREADS + #include + #endif +diff --git a/Include/internal/mimalloc/mimalloc/prim.h b/Include/internal/mimalloc/mimalloc/prim.h +index 322ab29e6b4..bb54814238d 100644 +--- a/Include/internal/mimalloc/mimalloc/prim.h ++++ b/Include/internal/mimalloc/mimalloc/prim.h +@@ -174,12 +174,15 @@ + __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + res = tcb[slot]; + #elif defined(__aarch64__) +- void** tcb; MI_UNUSED(ofs); ++ // Use a 64-bit temporary: on arm64_32 (ILP32), pointers are 32 bits ++ // but system registers are 64 bits. ++ uint64_t tp; MI_UNUSED(ofs); + #if defined(__APPLE__) // M1, issue #343 +- __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tp)); + #else +- __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tp)); + #endif ++ void** tcb = (void**)(uintptr_t)tp; + res = tcb[slot]; + #endif + return res; +@@ -201,12 +204,15 @@ + __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + tcb[slot] = value; + #elif defined(__aarch64__) +- void** tcb; MI_UNUSED(ofs); ++ // Use a 64-bit temporary: on arm64_32 (ILP32), pointers are 32 bits ++ // but system registers are 64 bits. ++ uint64_t tp; MI_UNUSED(ofs); + #if defined(__APPLE__) // M1, issue #343 +- __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tp)); + #else +- __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tp)); + #endif ++ void** tcb = (void**)(uintptr_t)tp; + tcb[slot] = value; + #endif + } +diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h +index 9cc25eda44f..dca86dec8be 100644 +--- a/Include/internal/pycore_interp_structs.h ++++ b/Include/internal/pycore_interp_structs.h +@@ -650,6 +650,10 @@ + PyObject *once_registry; /* Dict */ + PyObject *default_action; /* String */ + _PyRecursiveMutex lock; ++#ifdef HAVE_FORK ++ // Whether the thread calling fork() owns the warnings lock. ++ bool lock_held_at_fork; ++#endif + long filters_version; + PyObject *context; + }; +diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h +index 7dd2419ecec..db7141d5dc7 100644 +--- a/Include/internal/pycore_lock.h ++++ b/Include/internal/pycore_lock.h +@@ -173,6 +173,9 @@ + extern PyLockStatus _PyRecursiveMutex_LockTimed(_PyRecursiveMutex *m, PyTime_t timeout, _PyLockFlags flags); + PyAPI_FUNC(void) _PyRecursiveMutex_Unlock(_PyRecursiveMutex *m); + extern int _PyRecursiveMutex_TryUnlock(_PyRecursiveMutex *m); ++#ifdef HAVE_FORK ++extern void _PyRecursiveMutex_at_fork_reinit(_PyRecursiveMutex *m, int owned); ++#endif + + // A readers-writer (RW) lock. The lock supports multiple concurrent readers or + // a single writer. The lock is write-preferring: if a writer is waiting while +diff --git a/Include/internal/pycore_parser.h b/Include/internal/pycore_parser.h +index b89d02035db..2355016d8a8 100644 +--- a/Include/internal/pycore_parser.h ++++ b/Include/internal/pycore_parser.h +@@ -27,13 +27,17 @@ + }, \ + } + ++/* The caller must initialize *incomplete to NULL if supplied. A returned AST ++ can also return an owned exception there for a missing line terminator. ++ Validate the AST before reporting that exception. */ + extern struct _mod* _PyParser_ASTFromString( + const char *str, + PyObject* filename, + int mode, + PyCompilerFlags *flags, + PyArena *arena, +- PyObject *module); ++ PyObject *module, ++ PyObject **incomplete); + + extern struct _mod* _PyParser_ASTFromFile( + FILE *fp, +diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h +index 99d09c5e0d4..bfc94e3e852 100644 +--- a/Include/internal/pycore_pylifecycle.h ++++ b/Include/internal/pycore_pylifecycle.h +@@ -23,7 +23,7 @@ + + /* Various one-time initializers */ + +-extern PyStatus _Py_GetVersion_Init(void); ++extern void _Py_InitVersion(void); + extern PyStatus _PyFaulthandler_Init(int enable); + extern PyObject * _PyBuiltin_Init(PyInterpreterState *interp); + extern int _PyBuiltin_InitPythonFunctions(PyObject *dict); +@@ -43,7 +43,6 @@ + + /* Various internal finalizers */ + +-extern void _Py_GetVersion_Fini(void); + extern int _PySignal_Init(int install_signal_handlers); + extern void _PySignal_Fini(void); + +diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h +index 16f87614002..0a764d886c6 100644 +--- a/Include/internal/pycore_pystate.h ++++ b/Include/internal/pycore_pystate.h +@@ -342,7 +342,11 @@ + #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + result = (uintptr_t)_AddressOfReturnAddress(); + #elif defined(__aarch64__) +- __asm__ ("mov %0, sp" : "=r" (result)); ++ // Use a 64-bit temporary: on arm64_32 (ILP32), uintptr_t is 32 bits ++ // but sp is a 64-bit register. ++ uint64_t sp; ++ __asm__ ("mov %0, sp" : "=r" (sp)); ++ result = (uintptr_t)sp; + #elif defined(__x86_64__) + __asm__ ("{movq %%rsp, %0|mov %0, rsp}" : "=r" (result)); + #else +diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h +index b8e24da693b..1a945f386ec 100644 +--- a/Include/internal/pycore_unicodeobject.h ++++ b/Include/internal/pycore_unicodeobject.h +@@ -17,9 +17,7 @@ + #define _Py_MAX_UNICODE 0x10ffff + + +-// Export for '_multibytecodec' shared extension. _PyUnicodeWriter_CanWrite() +-// calls this function when assertions are enabled. +-PyAPI_FUNC(int) _PyUnicode_IsModifiable(PyObject *unicode); ++extern int _PyUnicode_IsModifiable(PyObject *unicode); + extern void _PyUnicodeWriter_InitWithBuffer( + _PyUnicodeWriter *writer, + PyObject *buffer); +diff --git a/Include/internal/pycore_warnings.h b/Include/internal/pycore_warnings.h +index e1348d529a2..d3c0c549373 100644 +--- a/Include/internal/pycore_warnings.h ++++ b/Include/internal/pycore_warnings.h +@@ -12,6 +12,11 @@ + + extern PyObject* _PyWarnings_Init(void); + ++#ifdef HAVE_FORK ++extern void _PyWarnings_BeforeFork(PyInterpreterState *interp); ++extern void _PyWarnings_AfterFork(PyInterpreterState *interp); ++#endif ++ + extern void _PyErr_WarnUnawaitedCoroutine(PyObject *coro); + extern void _PyErr_WarnUnawaitedAgenMethod(PyAsyncGenObject *agen, PyObject *method); + +diff --git a/Include/patchlevel.h b/Include/patchlevel.h +index 20428239db7..c6c7dde1797 100644 +--- a/Include/patchlevel.h ++++ b/Include/patchlevel.h +@@ -27,7 +27,7 @@ + #define PY_RELEASE_SERIAL 1 + + /* Version as a string */ +-#define PY_VERSION "3.16.0a1" ++#define PY_VERSION "3.16.0a1+dev" + /*--end constants--*/ + + +diff --git a/Lib/_pyrepl/historical_reader.py b/Lib/_pyrepl/historical_reader.py +index e0d31591598..b5cdc82d826 100644 +--- a/Lib/_pyrepl/historical_reader.py ++++ b/Lib/_pyrepl/historical_reader.py +@@ -291,9 +291,14 @@ + + @contextmanager + def suspend_history(self) -> SimpleContextManager: ++ with self.suspend_history_writes(): ++ del self.history[:] ++ yield ++ ++ @contextmanager ++ def suspend_history_writes(self) -> SimpleContextManager: + try: + old_history = self.history[:] +- del self.history[:] + yield + finally: + self.history[:] = old_history +diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py +index 1d00de3683d..a8af6cb425d 100644 +--- a/Lib/_pyrepl/readline.py ++++ b/Lib/_pyrepl/readline.py +@@ -380,7 +380,8 @@ + prompt_str = str(prompt) + reader.ps1 = prompt_str + sys.audit("builtins.input", prompt_str) +- result = reader.readline(startup_hook=self.startup_hook) ++ with reader.suspend_history_writes(): ++ result = reader.readline(startup_hook=self.startup_hook) + sys.audit("builtins.input/result", result) + return result + +diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py +index 90269c93655..9eb34c5f29f 100644 +--- a/Lib/asyncio/base_events.py ++++ b/Lib/asyncio/base_events.py +@@ -1244,6 +1244,7 @@ + await waiter + except: + transport.close() ++ waiter = None + raise + + return transport, protocol +@@ -1381,6 +1382,7 @@ + transport.close() + conmade_cb.cancel() + resume_cb.cancel() ++ waiter = None + raise + + return ssl_protocol._app_transport +diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py +index bf20f053ded..f41fa47b891 100644 +--- a/Lib/asyncio/selector_events.py ++++ b/Lib/asyncio/selector_events.py +@@ -1225,6 +1225,7 @@ + super()._call_connection_lost(exc) + finally: + self._write_ready = None ++ self._read_ready_cb = None + if self._empty_waiter is not None: + self._empty_waiter.set_exception( + ConnectionError("Connection is closed by peer")) +diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py +index 3a66cee93da..2e32773495a 100644 +--- a/Lib/asyncio/unix_events.py ++++ b/Lib/asyncio/unix_events.py +@@ -681,10 +681,10 @@ + # end polls as readable whenever unread data sits in the FIFO, and no + # event is delivered when the read end is closed, so it can only + # ever report a false disconnection (gh-145030). The same XNU +- # behaviour applies on iOS/tvOS/watchOS (sys.platform is not ++ # behaviour applies on iOS/tvOS/visionOS/watchOS (sys.platform is not + # "darwin" there). + is_named_fifo_without_close_event = ( +- sys.platform in {"darwin", "ios", "tvos", "watchos", "sunos5"} ++ sys.platform in {"darwin", "ios", "tvos", "visionos", "watchos", "sunos5"} + and is_fifo and pipe_stat.st_nlink > 0) + if is_socket or (is_fifo + and not sys.platform.startswith("aix") +diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py +index 2a7c18cda8a..b17ac7ab255 100644 +--- a/Lib/asyncio/windows_events.py ++++ b/Lib/asyncio/windows_events.py +@@ -515,11 +515,11 @@ + return self._register(ov, conn, partial(self._finish_recvfrom, + empty_result=b'')) + +- def recvfrom_into(self, conn, buf, flags=0): ++ def recvfrom_into(self, conn, buf, nbytes=0): + self._register_with_iocp(conn) + ov = _overlapped.Overlapped(NULL) + try: +- ov.WSARecvFromInto(conn.fileno(), buf, flags) ++ ov.WSARecvFromInto(conn.fileno(), buf, nbytes) + except BrokenPipeError: + return self._result((0, None)) + +diff --git a/Lib/codeop.py b/Lib/codeop.py +index 40e88423119..140e960b35f 100644 +--- a/Lib/codeop.py ++++ b/Lib/codeop.py +@@ -62,15 +62,10 @@ + warnings.simplefilter("ignore", (SyntaxWarning, DeprecationWarning)) + try: + compiler(source, filename, symbol, flags=flags) ++ except _IncompleteInputError: ++ return None + except SyntaxError: # Let other compile() errors propagate. +- try: +- compiler(source + "\n", filename, symbol, flags=flags) +- return None +- except _IncompleteInputError: +- return None +- except SyntaxError: +- pass +- # fallthrough ++ pass + + return compiler(source, filename, symbol, incomplete_input=False) + diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py -index 0b84a27f8c6..937a0b6ad1c 100644 +index 8b7089b6e57..695851b7ef1 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py -@@ -441,9 +441,9 @@ +@@ -447,9 +447,9 @@ else: def _load_library(self, name, mode, handle, winmode): @@ -16,10 +1116,10 @@ index 0b84a27f8c6..937a0b6ad1c 100644 with open(name) as f: name = _os.path.join( diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py -index 015c62b31a1..c893fc8375a 100644 +index f4fb05c29b4..3ca2458a2a6 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py -@@ -133,7 +133,7 @@ +@@ -104,7 +104,7 @@ if (name := _get_module_filename(h)) is not None] return libraries @@ -28,11 +1128,100 @@ index 015c62b31a1..c893fc8375a 100644 from ctypes.macholib.dyld import dyld_find as _dyld_find def find_library(name): possible = ['lib%s.dylib' % name, +diff --git a/Lib/email/message.py b/Lib/email/message.py +index 64e6c1e23cf..093b9012f31 100644 +--- a/Lib/email/message.py ++++ b/Lib/email/message.py +@@ -1026,7 +1026,7 @@ + if subtype in preferencelist: + yield (preferencelist.index(subtype), part) + return +- if maintype != 'multipart' or not self.is_multipart(): ++ if maintype != 'multipart' or not part.is_multipart(): + return + if subtype != 'related': + for subpart in part.iter_parts(): +diff --git a/Lib/http/client.py b/Lib/http/client.py +index 4b6fc549a9b..21484b835a0 100644 +--- a/Lib/http/client.py ++++ b/Lib/http/client.py +@@ -397,6 +397,10 @@ + # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked" + self.length = None + length = self.headers.get("content-length") ++ if length is not None: ++ # RFC 9112, section 5.1: optional whitespace (SP / HTAB) around ++ # the field value is not part of the value. ++ length = length.strip(' \t') + if length and not self.chunked and _is_legal_content_length(length): + self.length = int(length) + else: +@@ -1015,7 +1019,7 @@ + headers = [connect] + for header, value in self._tunnel_headers.items(): + header_bytes = header.encode("latin-1") +- value_bytes = value.encode("latin-1") ++ value_bytes = str(value).encode("latin-1") + if not _is_legal_header_name(header_bytes): + raise ValueError('Invalid header name %r' % (header_bytes,)) + if _is_illegal_header_value(value_bytes): +diff --git a/Lib/idlelib/autocomplete_w.py b/Lib/idlelib/autocomplete_w.py +index 24320b5a3bf..b28edffa2b0 100644 +--- a/Lib/idlelib/autocomplete_w.py ++++ b/Lib/idlelib/autocomplete_w.py +@@ -185,12 +185,14 @@ + self.autocompletewindow = acw = Toplevel(self.widget) + acw.withdraw() + acw.wm_overrideredirect(1) +- try: ++ if acw._windowingsystem == 'aqua': + # Prevent grabbing focus on macOS. +- acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w, +- "help", "noActivates") +- except TclError: +- pass ++ if acw.info_patchlevel() >= (9, 1): ++ acw.update_idletasks() ++ acw.wm_attributes(stylemask='docmodal') ++ else: ++ acw.tk.call('::tk::unsupported::MacWindowStyle', 'style', ++ acw._w, 'help', 'noActivates') + self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL) + self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set, + exportselection=False) +diff --git a/Lib/idlelib/tooltip.py b/Lib/idlelib/tooltip.py +index df5b1fe1dcf..f88ac604b1b 100644 +--- a/Lib/idlelib/tooltip.py ++++ b/Lib/idlelib/tooltip.py +@@ -30,14 +30,15 @@ + self.tipwindow = tw = Toplevel(self.anchor_widget) + # show no border on the top level window + tw.wm_overrideredirect(1) +- try: +- # This command is only needed and available on Tk >= 8.4.0 for OSX. +- # Without it, call tips intrude on the typing process by grabbing +- # the focus. +- tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, +- "help", "noActivates") +- except TclError: +- pass ++ if tw._windowingsystem == 'aqua': ++ # Without this, call tips intrude on the typing process by ++ # grabbing the focus. ++ if tw.info_patchlevel() >= (9, 1): ++ tw.update_idletasks() ++ tw.wm_attributes(stylemask='docmodal') ++ else: ++ tw.tk.call('::tk::unsupported::MacWindowStyle', 'style', ++ tw._w, 'help', 'noActivates') + + self.position_window() + self.showcontents() diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py -index a1cb729efb7..cb51488ce9a 100644 +index 484e0457eb0..2c8d482e4f6 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py -@@ -52,7 +52,7 @@ +@@ -55,7 +55,7 @@ # Bootstrap-related code ###################################################### _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', @@ -41,7 +1230,7 @@ index a1cb729efb7..cb51488ce9a 100644 _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) -@@ -1563,7 +1563,7 @@ +@@ -1570,7 +1570,7 @@ """ extension_loaders = [] if hasattr(_imp, 'create_dynamic'): @@ -50,11 +1239,86 @@ index a1cb729efb7..cb51488ce9a 100644 extension_loaders = [(AppleFrameworkLoader, [ suffix.replace(".so", ".fwork") for suffix in _imp.extension_suffixes() +diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py +index 549a6b3c457..15d5ac28179 100644 +--- a/Lib/logging/__init__.py ++++ b/Lib/logging/__init__.py +@@ -1680,6 +1680,8 @@ + exc_info = (type(exc_info), exc_info, exc_info.__traceback__) + elif not isinstance(exc_info, tuple): + exc_info = sys.exc_info() ++ else: # Normalize any falsy value to None, for type precision ++ exc_info = None + record = self.makeRecord(self.name, level, fn, lno, msg, args, + exc_info, func, extra, sinfo) + self.handle(record) +diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py +index 1d6b33fe854..561b99a92c8 100644 +--- a/Lib/mimetypes.py ++++ b/Lib/mimetypes.py +@@ -23,6 +23,8 @@ + read_mime_types(file) -- parse one file, return a dictionary or None + """ + ++import sys ++ + try: + from _winapi import _mimetypes_read_windows_registry + except ImportError: +@@ -40,17 +42,23 @@ + "suffix_map", "encodings_map", "types_map", "common_types" + ] + +-knownfiles = [ +- "/etc/mime.types", +- "/etc/httpd/mime.types", # Mac OS X +- "/etc/httpd/conf/mime.types", # Apache +- "/etc/apache/mime.types", # Apache 1 +- "/etc/apache2/mime.types", # Apache 2 +- "/usr/local/etc/httpd/conf/mime.types", +- "/usr/local/lib/netscape/mime.types", +- "/usr/local/etc/httpd/conf/mime.types", # Apache 1.2 +- "/usr/local/etc/mime.types", # Apache 1.3 +- ] ++if sys.platform == "win32": ++ # The default paths would resolve to the root of the current drive on Windows ++ # (e.g. C:\etc\mime.types), which unprivileged users can usually create. ++ # Only the registry is used by default. ++ knownfiles = [] ++else: ++ knownfiles = [ ++ "/etc/mime.types", ++ "/etc/httpd/mime.types", # Mac OS X ++ "/etc/httpd/conf/mime.types", # Apache ++ "/etc/apache/mime.types", # Apache 1 ++ "/etc/apache2/mime.types", # Apache 2 ++ "/usr/local/etc/httpd/conf/mime.types", ++ "/usr/local/lib/netscape/mime.types", ++ "/usr/local/etc/httpd/conf/mime.types", # Apache 1.2 ++ "/usr/local/etc/mime.types", # Apache 1.3 ++ ] + + inited = False + _db = None +diff --git a/Lib/os.py b/Lib/os.py +index 98d476f9841..f999150150c 100644 +--- a/Lib/os.py ++++ b/Lib/os.py +@@ -161,6 +161,9 @@ + _add("HAVE_FSTATVFS", "statvfs") + if _exists("statx"): + _set.add(statx) ++ _add("HAVE_FREADLINK", "readlink") ++ if sys.platform in ["linux", "android"] and "O_PATH" in _globals: ++ _add("HAVE_READLINKAT", "readlink") + supports_fd = _set + + _set = set() diff --git a/Lib/platform.py b/Lib/platform.py -index 36489d4fdd9..8e52bf202ff 100644 +index c4ef1c3eb20..1e28b15b2b9 100644 --- a/Lib/platform.py +++ b/Lib/platform.py -@@ -536,6 +536,78 @@ +@@ -541,6 +541,78 @@ return IOSVersionInfo(system, release, model, is_simulator) @@ -133,7 +1397,7 @@ index 36489d4fdd9..8e52bf202ff 100644 AndroidVer = collections.namedtuple( "AndroidVer", "release api_level manufacturer model device is_emulator") -@@ -676,7 +748,7 @@ +@@ -685,7 +757,7 @@ default in case the command should fail. """ @@ -142,7 +1406,7 @@ index 36489d4fdd9..8e52bf202ff 100644 # XXX Others too ? return default -@@ -840,14 +912,30 @@ +@@ -849,14 +921,30 @@ csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' @@ -176,7 +1440,7 @@ index 36489d4fdd9..8e52bf202ff 100644 def from_subprocess(): """ Fall back to `uname -p` -@@ -1000,9 +1088,15 @@ +@@ -1009,9 +1097,15 @@ system = 'Android' release = android_ver().release @@ -193,7 +1457,7 @@ index 36489d4fdd9..8e52bf202ff 100644 vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' -@@ -1292,6 +1386,12 @@ +@@ -1301,6 +1395,12 @@ # macOS and iOS both report as a "Darwin" kernel if sys.platform == "ios": system, release, _, _ = ios_ver() @@ -207,7 +1471,7 @@ index 36489d4fdd9..8e52bf202ff 100644 macos_release = mac_ver()[0] if macos_release: diff --git a/Lib/site.py b/Lib/site.py -index 89e136d7b10..8d2063607a6 100644 +index 294a4d6f641..d763168afbd 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -653,8 +653,8 @@ @@ -222,7 +1486,7 @@ index 89e136d7b10..8d2063607a6 100644 def joinuser(*args): diff --git a/Lib/subprocess.py b/Lib/subprocess.py -index 5948cd841bc..d92c8b57fd7 100644 +index 466f2c10a2c..e2dac4938d5 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -75,7 +75,7 @@ @@ -235,7 +1499,7 @@ index 5948cd841bc..d92c8b57fd7 100644 if _mswindows: import _winapi diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py -index 4f8ebf15d36..86598a8b4b2 100644 +index 0657e458806..dfed1cd1435 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -23,6 +23,9 @@ @@ -257,7 +1521,7 @@ index 4f8ebf15d36..86598a8b4b2 100644 return None def joinuser(*args): -@@ -741,6 +744,18 @@ +@@ -750,6 +753,18 @@ release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") osname = sys.platform machine = sys.implementation._multiarch @@ -266,7 +1530,7 @@ index 4f8ebf15d36..86598a8b4b2 100644 + osname = sys.platform + machine = sys.implementation._multiarch + elif sys.platform == "watchos": -+ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0") ++ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "9.0") + osname = sys.platform + machine = sys.implementation._multiarch + elif sys.platform == "visionos": @@ -277,10 +1541,10 @@ index 4f8ebf15d36..86598a8b4b2 100644 import _osx_support osname, release, machine = _osx_support.get_platform_osx( diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py -index c11e9c068be..13cc95a81cd 100644 +index 716c662ad45..d8fa3fa526e 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py -@@ -7406,9 +7406,9 @@ +@@ -7450,9 +7450,9 @@ self.assertEqual(dt_orig, dt_rt) def test_type_check_in_subinterp(self): @@ -292,11 +1556,165 @@ index c11e9c068be..13cc95a81cd 100644 extension_loader = "AppleFrameworkLoader" else: extension_loader = "ExtensionFileLoader" +diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py +index bc6735d0749..4b31d2f775e 100644 +--- a/Lib/test/pythoninfo.py ++++ b/Lib/test/pythoninfo.py +@@ -9,7 +9,7 @@ + + + MS_WINDOWS = (sys.platform == "win32") +-APPLE = (sys.platform in ("darwin", "ios", "tvos", "watchos")) ++APPLE = (sys.platform in ("darwin", "ios", "tvos", "watchos", "visionos")) + + COMMAND_TIMEOUT = 60.0 + diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 8e13ab4b9bc..f20f9528085 100644 +index 5331615b1b1..4f27a23b1a6 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -587,7 +587,7 @@ +@@ -19,6 +19,7 @@ + import types + import unittest + import warnings ++lazy import platform + + + __all__ = [ +@@ -33,6 +34,7 @@ + "is_resource_enabled", "get_resource_value", "requires", "requires_resource", + "requires_freebsd_version", + "requires_gil_enabled", "requires_linux_version", "requires_mac_ver", ++ "requires_ios_ver", + "check_syntax_error", + "requires_gzip", "requires_bz2", "requires_lzma", "requires_zstd", + "bigmemtest", "nomemtest", "bigaddrspacetest", "cpython_only", "get_attribute", +@@ -229,7 +231,6 @@ + def _is_gui_available(): + if hasattr(_is_gui_available, 'result'): + return _is_gui_available.result +- import platform + reason = None + if sys.platform.startswith('win') and platform.win32_is_iot(): + reason = "gui is not available on Windows IoT Core" +@@ -347,17 +348,17 @@ + if resource == 'gui' and not _is_gui_available(): + raise ResourceDenied(_is_gui_available.reason) + +-def _requires_unix_version(sysname, min_version): +- """Decorator raising SkipTest if the OS is `sysname` and the version is less ++ ++def _requires_platform_version(name, sys_platform, get_version_text, min_version): ++ """ ++ Decorator raising SkipTest if the OS is `sysname` and the version is less + than `min_version`. + +- For example, @_requires_unix_version('FreeBSD', (7, 2)) raises SkipTest if +- the FreeBSD version is less than 7.2. ++ For example, @_requires_platform_version('FreeBSD', 'freebsd', (7, 2)) ++ raises SkipTest if the FreeBSD version is less than 7.2. + """ +- import platform +- min_version_txt = '.'.join(map(str, min_version)) +- version_txt = platform.release().split('-', 1)[0] +- if platform.system() == sysname: ++ version_txt = get_version_text() ++ if sys.platform == sys_platform: + try: + version = tuple(map(int, version_txt.split('.'))) + except ValueError: +@@ -367,13 +368,19 @@ + else: + skip = False + ++ min_version_txt = '.'.join(map(str, min_version)) + return unittest.skipIf( + skip, +- f"{sysname} version {min_version_txt} or higher required, not " ++ f"{name} version {min_version_txt} or higher required, not " + f"{version_txt}" + ) + + ++def _get_platform_release(): ++ # Replace '7.2.8-200.fc44.x86_64' with '7.2.8' ++ return platform.release().partition('-')[0] ++ ++ + def requires_freebsd_version(*min_version): + """Decorator raising SkipTest if the OS is FreeBSD and the FreeBSD version is + less than `min_version`. +@@ -381,7 +388,11 @@ + For example, @requires_freebsd_version(7, 2) raises SkipTest if the FreeBSD + version is less than 7.2. + """ +- return _requires_unix_version('FreeBSD', min_version) ++ return _requires_platform_version( ++ 'FreeBSD', 'freebsd', ++ get_version_text=_get_platform_release, ++ min_version=min_version) ++ + + def requires_linux_version(*min_version): + """Decorator raising SkipTest if the OS is Linux and the Linux version is +@@ -390,7 +401,11 @@ + For example, @requires_linux_version(2, 6, 32) raises SkipTest if the Linux + version is less than 2.6.32. + """ +- return _requires_unix_version('Linux', min_version) ++ return _requires_platform_version( ++ "Linux", "linux", ++ get_version_text=_get_platform_release, ++ min_version=min_version) ++ + + def requires_mac_ver(*min_version): + """Decorator raising SkipTest if the OS is Mac OS X and the OS X +@@ -399,26 +414,23 @@ + For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version + is lesser than 10.5. + """ +- def decorator(func): +- @functools.wraps(func) +- def wrapper(*args, **kw): +- if sys.platform == 'darwin': +- import platform +- version_txt = platform.mac_ver()[0] +- try: +- version = tuple(map(int, version_txt.split('.'))) +- except ValueError: +- pass +- else: +- if version < min_version: +- min_version_txt = '.'.join(map(str, min_version)) +- raise unittest.SkipTest( +- "Mac OS X %s or higher required, not %s" +- % (min_version_txt, version_txt)) +- return func(*args, **kw) +- wrapper.min_version = min_version +- return wrapper +- return decorator ++ return _requires_platform_version( ++ "macOS", "darwin", ++ get_version_text=lambda: platform.mac_ver()[0], ++ min_version=min_version) ++ ++ ++def requires_ios_ver(*min_version): ++ """Decorator raising SkipTest if the OS is iOS and the OS version ++ version if less than min_version. ++ ++ For example, requires_ios_ver(16, 0) raises SkipTest if the iOS version ++ is lesser than 16.0. ++ """ ++ return _requires_platform_version( ++ "iOS", "ios", ++ get_version_text=lambda: platform.ios_ver().release, ++ min_version=min_version) + + + def thread_unsafe(reason): +@@ -616,7 +628,7 @@ sys.platform == "android", f"Android blocks {name} with SELinux" ) @@ -305,7 +1723,7 @@ index 8e13ab4b9bc..f20f9528085 100644 unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None -@@ -606,7 +606,7 @@ +@@ -635,7 +647,7 @@ def skip_wasi_stack_overflow(): return unittest.skipIf(is_wasi, "Exhausts stack on WASI") @@ -314,6 +1732,37 @@ index 8e13ab4b9bc..f20f9528085 100644 is_apple = is_apple_mobile or sys.platform == "darwin" has_fork_support = hasattr(os, "fork") and not ( +@@ -1602,10 +1614,10 @@ + + + def reap_children(): +- """Use this function at the end of test_main() whenever sub-processes +- are started. This will help ensure that no extra children (zombies) +- stick around to hog resources and create problems when looking +- for refleaks. ++ """Call this from setUp(), tearDown(), or tearDownModule() in tests ++ that start subprocesses. This will help ensure that no extra children ++ (zombies) stick around to hog resources and create problems when ++ looking for refleaks. + """ + # Need os.waitpid(-1, os.WNOHANG): Windows is not supported + if not (hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG')): +@@ -2223,7 +2235,6 @@ + if is_android and interval < minimum_interval: + global _old_android_emulator + if _old_android_emulator is None: +- import platform + av = platform.android_ver() + _old_android_emulator = av.is_emulator and av.api_level < 24 + if _old_android_emulator: +@@ -3480,7 +3491,6 @@ + + # On linux, we'll depend on the platform module to do the check, so new + # musl platforms should add support in that module if possible. +- import platform + lib, version = platform.libc_ver() + if lib != 'musl': + _linked_to_musl = False diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py index ec5a26bc1c0..1b66f9c7646 100644 --- a/Lib/test/test__interpreters.py @@ -326,6 +1775,546 @@ index ec5a26bc1c0..1b66f9c7646 100644 def test_os_exec(self): expected = 'spam spam spam spam spam' subinterp = _interpreters.create() +diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py +index bb282eba5d4..b1483ad6bf2 100644 +--- a/Lib/test/test_ast/test_ast.py ++++ b/Lib/test/test_ast/test_ast.py +@@ -1107,13 +1107,11 @@ + enum._test_simple_enum(_Precedence, _ast_unparse._Precedence) + + @support.cpython_only +- @support.run_with_limited_c_stack( +- 100_000 if sys.platform == "android" else 500_000) ++ @support.run_with_limited_c_stack(500_000) + @skip_wasi_stack_overflow() + @skip_emscripten_stack_overflow() + def test_ast_recursion_limit(self): +- # Android test devices have less memory. +- crash_depth = 100_000 if sys.platform == "android" else 500_000 ++ crash_depth = 500_000 + success_depth = 200 + if _testinternalcapi is not None: + remaining = _testinternalcapi.get_c_recursion_remaining() +diff --git a/Lib/test/test_asyncio/test_sslproto.py b/Lib/test/test_asyncio/test_sslproto.py +index 253a470680f..acb80c19c0b 100644 +--- a/Lib/test/test_asyncio/test_sslproto.py ++++ b/Lib/test/test_asyncio/test_sslproto.py +@@ -832,6 +832,68 @@ + self.assertIsInstance(server_err, ssl.SSLError) + self.assertIn('ALERT_UNKNOWN_CA', server_err.reason or '') + ++ def test_create_connection_ssl_failed_certificate_no_cycles(self): ++ # gh-159049 ++ sslctx = test_utils.simple_server_sslcontext() ++ client_sslctx = test_utils.simple_client_sslcontext( ++ disable_verify=False) ++ transports = [] ++ ++ def server(sock): ++ try: ++ sock.start_tls(sslctx, server_side=True) ++ except OSError: ++ pass ++ ++ connection_made = sslproto.SSLProtocol.connection_made ++ ++ def remember_transport(proto, tr): ++ transports.append(weakref.ref(tr)) ++ connection_made(proto, tr) ++ ++ async def conn(addr): ++ try: ++ await self.loop.create_connection( ++ asyncio.Protocol, *addr, ++ ssl=client_sslctx, server_hostname='') ++ except ssl.SSLCertVerificationError: ++ pass ++ ++ support.gc_collect() ++ with (self.tcp_server(server) as srv, support.disable_gc(), ++ mock.patch.object(sslproto.SSLProtocol, 'connection_made', ++ remember_transport)): ++ self.loop.run_until_complete(conn(srv.addr)) ++ self.assertEqual([tr() for tr in transports], [None]) ++ ++ def test_start_tls_failed_certificate_no_cycles(self): ++ # gh-159049 ++ sslctx = test_utils.simple_server_sslcontext() ++ client_sslctx = test_utils.simple_client_sslcontext( ++ disable_verify=False) ++ transports = [] ++ ++ def server(sock): ++ try: ++ sock.start_tls(sslctx, server_side=True) ++ except OSError: ++ pass ++ ++ async def client(addr): ++ tr, proto = await self.loop.create_connection( ++ asyncio.Protocol, *addr) ++ transports.append(weakref.ref(tr)) ++ try: ++ await self.loop.start_tls( ++ tr, proto, client_sslctx, server_hostname='') ++ except ssl.SSLCertVerificationError: ++ pass ++ ++ support.gc_collect() ++ with self.tcp_server(server) as srv, support.disable_gc(): ++ self.loop.run_until_complete(client(srv.addr)) ++ self.assertEqual([tr() for tr in transports], [None]) ++ + def test_create_server_ssl_failed_handshake_sends_alert(self): + # gh-98078: when the handshake fails, the server must send the + # fatal TLS alert generated by OpenSSL to the client before +diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py +index f460029aff8..95e1f760123 100644 +--- a/Lib/test/test_asyncio/test_tasks.py ++++ b/Lib/test/test_asyncio/test_tasks.py +@@ -1,5 +1,6 @@ + """Tests for tasks.py.""" + ++import _thread + import collections + import contextlib + import contextvars +@@ -20,6 +21,7 @@ + from asyncio import tasks + from test.test_asyncio import utils as test_utils + from test import support ++from test.support import threading_helper + from test.support.script_helper import assert_python_ok + + +@@ -3095,6 +3097,39 @@ + all_tasks = getattr(tasks, '_c_all_tasks', None) + current_task = staticmethod(getattr(tasks, '_c_current_task', None)) + ++ @threading_helper.requires_working_threading() ++ def test_task_created_during_thread_state_clear(self): ++ loop = self.loop ++ task = None ++ var = contextvars.ContextVar('var') ++ # Avoid recreating the thread state's context after it is cleared. ++ task_context = contextvars.Context() ++ ++ async def noop(): ++ pass ++ ++ class CreatesTaskOnClear: ++ def __del__(self): ++ nonlocal task ++ task = loop.create_task(noop(), context=task_context) ++ ++ def create_finalizer(): ++ # gh-159041: Run the finalizer when PyThreadState_Clear() ++ # clears the thread's context. ++ var.set(CreatesTaskOnClear()) ++ ++ # threading.Thread owns its Context, so use _thread to make the ++ # Context lifetime match the thread state's lifetime. ++ handle = _thread.start_joinable_thread(create_finalizer) ++ handle.join(support.SHORT_TIMEOUT) ++ self.assertTrue(handle.is_done(), 'thread failed to exit') ++ self.assertIsNotNone(task) ++ ++ try: ++ self.assertEqual(self.all_tasks(loop), {task}) ++ finally: ++ loop.run_until_complete(task) ++ + def test_del__log_destroy_pending_segfault(self): + async def coro(): + pass +diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py +index 96190a7f582..05faad4532b 100644 +--- a/Lib/test/test_bytes.py ++++ b/Lib/test/test_bytes.py +@@ -898,6 +898,19 @@ + b = self.type2test(b'%*b') + b % (_testcapi.PY_SSIZE_T_MAX, b'abc') + ++ def test_mod_bytes_lookup_error(self): ++ # An error raised while looking up __bytes__ propagates. ++ class BadBytes: ++ @property ++ def __bytes__(self): ++ raise ZeroDivisionError ++ def __buffer__(self, flags): ++ return memoryview(b'abc') ++ ++ b = self.type2test(b'%b') ++ with self.assertRaises(ZeroDivisionError): ++ b % BadBytes() ++ + def test_imod(self): + b = self.type2test(b'hello, %b!') + orig = b +diff --git a/Lib/test/test_capi/test_build.py b/Lib/test/test_capi/test_build.py +index d45c9dbc1c5..8319ad1ec77 100644 +--- a/Lib/test/test_capi/test_build.py ++++ b/Lib/test/test_capi/test_build.py +@@ -20,11 +20,15 @@ + + # Test Py_GetCompiler() + def test_getcompiler(self): +- self.assertIn(_testlimitedcapi.py_getcompiler(), sys.version) ++ compiler = _testlimitedcapi.py_getcompiler() ++ self.assertIsInstance(compiler, str) ++ self.assertNotEqual(compiler, '') + + # Test Py_GetBuildInfo() + def test_getbuildinfo(self): +- self.assertIn(_testlimitedcapi.py_getbuildinfo(), sys.version) ++ buildinfo = _testlimitedcapi.py_getbuildinfo() ++ self.assertIsInstance(buildinfo, str) ++ self.assertNotEqual(buildinfo, '') + + # Test internal _Py_GetBuiltWithAssert() + def test__getbuiltwithassert(self): +diff --git a/Lib/test/test_capi/test_function.py b/Lib/test/test_capi/test_function.py +index c1a278e5d4d..9a07e3106f3 100644 +--- a/Lib/test/test_capi/test_function.py ++++ b/Lib/test/test_capi/test_function.py +@@ -44,6 +44,12 @@ + with self.assertRaises(SystemError): + _testcapi.function_get_module(None) # not a function + ++ self.assertEqual(_testcapi.function_get_module_macro(some), module) ++ value = object() ++ some.__module__ = value ++ self.assertIs(_testcapi.function_get_module(some), value) ++ self.assertIs(_testcapi.function_get_module_macro(some), value) ++ + def test_function_get_defaults(self): + # Test PyFunction_GetDefaults() + def some( +diff --git a/Lib/test/test_capi/test_mem.py b/Lib/test/test_capi/test_mem.py +index 21b51d13344..7e35fabdd27 100644 +--- a/Lib/test/test_capi/test_mem.py ++++ b/Lib/test/test_capi/test_mem.py +@@ -1,3 +1,4 @@ ++import os + import re + import textwrap + import unittest +@@ -188,10 +189,12 @@ + 'need address sanitizer') + class AddressSanitizerTests(unittest.TestCase): + def test_buffer_overflow(self): ++ asan_options = os.environ.get('ASAN_OPTIONS', '') + with support.SuppressCrashReport(): + out = assert_python_failure( + '-c', 'import _testcapi; _testcapi.pymem_buffer_overflow()', + PYTHONMALLOC='malloc', ++ ASAN_OPTIONS=f'{asan_options}:log_path=stderr', + ) + self.assertIn(b'AddressSanitizer', out.err) + +diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py +index b4585adb07e..338fc61ea1e 100644 +--- a/Lib/test/test_capi/test_object.py ++++ b/Lib/test/test_capi/test_object.py +@@ -275,6 +275,19 @@ + obj = MyObj() + _testinternalcapi.incref_decref_delayed(obj) + ++ def test_decref_delayed_immortal(self): ++ # _PyObject_XDecRefDelayed() must handle immortal objects, including ++ # objects that become immortal after they were queued. The GC ++ # processes the queue with the world stopped. ++ import gc ++ _testinternalcapi.incref_decref_delayed(None) ++ gc.collect() ++ ++ s = ''.join(['decref', '_delayed', '_immortal']) ++ _testinternalcapi.incref_decref_delayed(s) ++ s = sys.intern(s) ++ gc.collect() ++ + def test_is_unique_temporary(self): + self.assertTrue(_testcapi.pyobject_is_unique_temporary(object())) + obj = object() +diff --git a/Lib/test/test_cext/runtests.py b/Lib/test/test_cext/runtests.py +index e8288ee5e11..6700b410b7d 100644 +--- a/Lib/test/test_cext/runtests.py ++++ b/Lib/test/test_cext/runtests.py +@@ -19,6 +19,36 @@ + print(flush=True) + + ++def run_refleak_tests(testmod): ++ # Run tests and check for reference leaks ++ warmups = 3 ++ runs = 3 ++ repcount = warmups + runs ++ refcount = array.array('q', [0] * repcount) ++ rep_range = tuple(range(repcount)) ++ ++ for i in rep_range: ++ print(f"Run #{i + 1}", flush=True) ++ run_tests(testmod) ++ gc.collect() ++ refcount[i] = sys.gettotalrefcount() ++ ++ # Compute differences ++ previous = None ++ for i in rep_range: ++ value = refcount[i] ++ if previous is not None: ++ refcount[i] = value - previous ++ previous = value ++ ++ deltas = list(refcount[warmups:]) ++ if all(delta >= 1 for delta in deltas): ++ print(f'ERROR: Tests leaked {deltas} references') ++ sys.exit(1) ++ elif any(deltas): ++ print(f'WARNING: Tests leaked {deltas} references (this is fine)') ++ ++ + def main(): + if len(sys.argv) < 2: + print("usage: python runtests.py TEST_MODULE_NAME") +@@ -45,21 +75,7 @@ + print() + + if hasattr(sys, 'gettotalrefcount'): +- # First run to warm up Python. For example, test_datetime() imports +- # the datetime module. +- run_tests(testmod, verbose=False) +- +- refcount = array.array('q', [0, 0]) +- gc.collect() +- +- # Check for reference leak +- refcount[0] = sys.gettotalrefcount() +- run_tests(testmod) +- refcount[1] = sys.gettotalrefcount() +- +- diff = refcount[1] - refcount[0] +- if diff >= 1: +- raise AssertionError(f'Tests leaked {diff} references') ++ run_refleak_tests(testmod) + else: + run_tests(testmod) + +diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py +index 0af0debd815..82b9d0f86ba 100644 +--- a/Lib/test/test_cmd_line.py ++++ b/Lib/test/test_cmd_line.py +@@ -176,35 +176,24 @@ + self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) + + def test_showrefcount(self): +- def run_python(*args): +- # this is similar to assert_python_ok but doesn't strip +- # the refcount from stderr. It can be replaced once +- # assert_python_ok stops doing that. +- cmd = [sys.executable] +- cmd.extend(args) +- PIPE = subprocess.PIPE +- p = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE) +- out, err = p.communicate() +- p.stdout.close() +- p.stderr.close() +- rc = p.returncode +- self.assertEqual(rc, 0) +- return rc, out, err +- code = 'import sys; print(sys._xoptions)' + # normally the refcount is hidden +- rc, out, err = run_python('-c', code) +- self.assertEqual(out.rstrip(), b'{}') +- self.assertEqual(err, b'') ++ # (assert_python_ok() enables faulthandler) ++ code = 'import sys; print(sys._xoptions)' ++ proc = assert_python_ok('-c', code) ++ self.assertEqual(proc.out.rstrip(), b"{'faulthandler': True}") ++ self.assertEqual(proc.err, b'') ++ + # "-X showrefcount" shows the refcount, but only in debug builds +- rc, out, err = run_python('-I', '-X', 'showrefcount', '-c', code) +- self.assertEqual(out.rstrip(), b"{'showrefcount': True}") ++ proc = assert_python_ok('-I', '-X', 'showrefcount', '-c', code) ++ self.assertEqual(proc.out.rstrip(), ++ b"{'faulthandler': True, 'showrefcount': True}") + if support.Py_DEBUG: + # bpo-46417: Tolerate negative reference count which can occur + # because of bugs in C extensions. This test is only about checking + # the showrefcount feature. +- self.assertRegex(err, br'^\[-?\d+ refs, \d+ blocks\]') ++ self.assertRegex(proc.err, br'^\[-?\d+ refs, \d+ blocks\]') + else: +- self.assertEqual(err, b'') ++ self.assertEqual(proc.err, b'') + + @support.cpython_only + def test_xoption_frozen_modules(self): +diff --git a/Lib/test/test_codeop.py b/Lib/test/test_codeop.py +index c75c0f8627b..69dbc2ddfb6 100644 +--- a/Lib/test/test_codeop.py ++++ b/Lib/test/test_codeop.py +@@ -124,6 +124,99 @@ + self.assertEqual(cm.exception.args, ( + 'incomplete input', ('', 2, 9, text, 2, -1))) + ++ @subTests('compiler', COMPILERS) ++ @subTests('newline', ('\n', '\r', '\r\n')) ++ def test_unterminated_trailing_line(self, compiler, newline): ++ for head, symbol in (('1', 'eval'), ('if 1: pass', 'single'), ++ ('if 1:\n pass', 'single')): ++ for tail in (' ', '\t', '\f ', '\n '): ++ source = head + newline + tail ++ with self.subTest(source=source, symbol=symbol): ++ if compiler in RAW_COMPILERS: ++ with self.assertRaises(_IncompleteInputError): ++ compiler(source, '', symbol) ++ else: ++ self.assertIsNone(compiler(source, '', symbol)) ++ self.assertValid(source + '\n', symbol, compiler=compiler) ++ for tail in ('\f', ' \f', '\n\f'): ++ with self.subTest(head=head, tail=tail, symbol=symbol): ++ self.assertValid(head + newline + tail, symbol, ++ compiler=compiler) ++ ++ for tail in ('# comment', ' # comment'): ++ self.assertValid('if 1: pass' + newline + tail, compiler=compiler) ++ self.assertValid('1' + newline + tail, 'eval', compiler=compiler) ++ ++ for head in ('1', 'if 1: pass', 'if 1:\n pass'): ++ for tail in (' ', '\t', '\f', '# comment'): ++ self.assertValid(head + newline + tail, 'exec', ++ compiler=compiler) ++ ++ @subTests('symbol', ('single', 'exec', 'eval')) ++ def test_unfinished_token_in_invalid_position(self, symbol): ++ for source in ('a b', '(a b', '1 """', '(1 """', ++ 'f"{lambda:\\', 't"{lambda:\\'): ++ for compiler in COMPILERS: ++ with self.subTest(source=source, compiler=compiler): ++ with self.assertRaises(SyntaxError) as cm: ++ compiler(source, '', symbol) ++ self.assertNotIsInstance(cm.exception, _IncompleteInputError) ++ ++ @subTests('symbol', ('single', 'exec', 'eval')) ++ def test_invalid_partial_string_diagnostic(self, symbol): ++ with self.assertRaisesRegex(SyntaxError, 'invalid syntax'): ++ Compile()('a"""', '', symbol) ++ ++ @subTests('symbol', ('single', 'exec', 'eval')) ++ @subTests('prefix', ('', 'r', 'b', 'f', 't', 'rf', 'rt')) ++ def test_string_continuation(self, symbol, prefix): ++ source = prefix + '"first\\' ++ compile(source + '\nsecond"\n', '', symbol) ++ self.assertIsNone(compile_command(source, symbol=symbol)) ++ with self.assertRaises(_IncompleteInputError): ++ Compile()(source, '', symbol) ++ ++ @subTests('symbol', ('single', 'exec', 'eval')) ++ @subTests('newline', ('\n', '\r', '\r\n')) ++ def test_continued_final_line(self, symbol, newline): ++ source = '1\\' + newline ++ compile(source + '+ 2\n', '', symbol) ++ self.assertIsNone(compile_command(source, symbol=symbol)) ++ with self.assertRaises(_IncompleteInputError): ++ Compile()(source, '', symbol) ++ with self.assertRaises(SyntaxError) as cm: ++ Compile()('1 + \\' + newline, '', symbol) ++ # Appending LF to CR completes the CRLF line terminator. ++ if newline != '\r': ++ self.assertNotIsInstance(cm.exception, _IncompleteInputError) ++ ++ @subTests('symbol', ('single', 'exec')) ++ def test_eof_before_nested_body(self, symbol): ++ for source in ('if True:\n if True:', ++ 'if True:\n def f():', ++ 'match 1:\n case 1:'): ++ for tail in ('', '\n', '\n\n'): ++ with self.subTest(source=source, tail=tail): ++ compile(source + tail + '\n pass\n', '', symbol) ++ self.assertIsNone(compile_command(source + tail, symbol=symbol)) ++ with self.assertRaises(_IncompleteInputError): ++ Compile()(source + tail, '', symbol) ++ ++ def test_incomplete_suite_semantic_errors(self): ++ cases = ( ++ ('if True: break', "'break' outside loop"), ++ ('if True: return 1', "'return' outside function"), ++ ('def f(x, x):\n pass', 'duplicate parameter'), ++ ) ++ for source, message in cases: ++ for compiler in COMPILERS: ++ with self.subTest(source=source, compiler=compiler): ++ with self.assertRaises(SyntaxError) as cm: ++ compiler(source, '', 'single') ++ self.assertNotIsInstance(cm.exception, _IncompleteInputError) ++ if compiler in RAW_COMPILERS: ++ self.assertIn(message, str(cm.exception)) ++ + @subTests('compiler', COMPILERS) + def test_incomplete(self, compiler): + ai = functools.partial(self.assertIncomplete, compiler=compiler) +diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py +index 53b4be1e75b..ff791d1b1d6 100644 +--- a/Lib/test/test_compile.py ++++ b/Lib/test/test_compile.py +@@ -773,14 +773,12 @@ + + @support.cpython_only + @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") +- @support.run_with_limited_c_stack( +- 100_000 if sys.platform == "android" else 500_000) ++ @support.run_with_limited_c_stack(500_000) + @support.skip_emscripten_stack_overflow() + def test_compiler_recursion_limit(self): + # Compiler frames are small + limit = 100 +- # Android test devices have less memory. +- crash_depth = limit * (1000 if sys.platform == "android" else 5000) ++ crash_depth = limit * 5000 + success_depth = limit + + def check_limit(prefix, repeated, mode="single"): +@@ -1092,8 +1090,7 @@ + # complex statements. + @support.requires_resource('cpu') + def test_stack_overflow(self): +- # Android test devices have less memory. +- size = 100_000 if sys.platform == "android" else 200_000 ++ size = 200_000 + compile("if a: b\n" * size, "", "exec") + + # Multiple users rely on the fact that CPython does not generate +@@ -1586,6 +1583,28 @@ + + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') + ++ def test_backward_jump_conditional_in_loop_in_try(self): ++ # gh-130279: The jumps in a loop body, including the backward jump ++ # for the untaken branch of a conditional at the end of the body, ++ # must be covered by the exception handler of the enclosing try. ++ def f(items): ++ try: ++ for x in items: ++ if x < 0: ++ raise ValueError(x) ++ except KeyboardInterrupt: ++ return 'handled' ++ ++ bytecode = dis.Bytecode(f) ++ jumps = [instr for instr in bytecode ++ if instr.opcode in dis.hasjump] ++ self.assertTrue(jumps) ++ for instr in jumps: ++ with self.subTest(instr=instr): ++ self.assertTrue( ++ any(entry.start <= instr.offset < entry.end ++ for entry in bytecode.exception_entries)) ++ + def test_big_dict_literal(self): + # The compiler has a flushing point in "compiler_dict" that calls compiles + # a portion of the dictionary literal when the loop that iterates over the items diff --git a/Lib/test/test_ctypes/test_dllist.py b/Lib/test/test_ctypes/test_dllist.py index 15603dc3d77..bff6c0fb95f 100644 --- a/Lib/test/test_ctypes/test_dllist.py @@ -339,15 +2328,1185 @@ index 15603dc3d77..bff6c0fb95f 100644 if WINDOWS: KNOWN_LIBRARIES = ["KERNEL32.DLL"] -diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py -index 63c130813ec..5baa6145f4a 100644 ---- a/Lib/test/test_platform.py -+++ b/Lib/test/test_platform.py -@@ -287,13 +287,21 @@ - if sys.platform == "android": - self.assertEqual(res.system, "Android") - self.assertEqual(res.release, platform.android_ver().release) -- elif sys.platform == "ios": +diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py +index 5db44ebedd2..ec64ce52dd9 100644 +--- a/Lib/test/test_curses.py ++++ b/Lib/test/test_curses.py +@@ -14,6 +14,7 @@ + check_disallow_instantiation, MISSING_C_DOCSTRINGS, + gc_collect, SHORT_TIMEOUT) + from test.support.import_helper import import_module ++from test.support import os_helper + + # Optionally test curses module. This currently requires that the + # 'curses' resource be given on the regrtest command line using the -u +@@ -129,6 +130,9 @@ + + BROKEN_VARIATION_SELECTOR_WIDTH = _broken_variation_selector_width() + ++class CustomError(Exception): ++ pass ++ + # newterm() is used when available (it reports errors instead of exiting), but + # initscr() is still the fallback, and an unusable $TERM has no terminal to + # drive either way. +@@ -3271,6 +3275,11 @@ + self.save_signals = SaveSignals() + self.save_signals.save() + self.addCleanup(self.save_signals.restore) ++ # A new pseudo-terminal has no size, so newterm() takes it from ++ # $LINES and $COLUMNS, or from the terminfo entry if they are unset. ++ env = self.enterContext(os_helper.EnvironmentVarGuard()) ++ env['LINES'] = '24' ++ env['COLUMNS'] = '80' + + def tearDown(self): + # Leave visual mode and reclaim the test's screens while their +@@ -3337,6 +3346,26 @@ + win.addstr(0, 0, 'hello') + win.refresh() + ++ def test_lines_columns_env(self): ++ # $LINES and $COLUMNS override the size from the terminfo entry ++ # (24x80 for xterm). ++ for lines, columns, size in [('30', '100', (30, 100)), ++ ('30', None, (30, 80)), ++ (None, '100', (24, 100)), ++ (None, None, (24, 80))]: ++ with self.subTest(lines=lines, columns=columns): ++ with os_helper.EnvironmentVarGuard() as env: ++ for name, value in [('LINES', lines), ++ ('COLUMNS', columns)]: ++ if value is None: ++ env.unset(name) ++ else: ++ env[name] = value ++ s = self.make_pty() ++ screen = curses.newterm('xterm', s, s) ++ self.assertEqual(screen.stdscr.getmaxyx(), size) ++ self.assertEqual((curses.LINES, curses.COLS), size) ++ + def test_newterm_file_object(self): + # type=None uses $TERM; the file arguments accept file objects too. + s = self.make_pty() +@@ -3353,6 +3382,50 @@ + self.assertIs(curses.set_term(a), b) # returns the previous one + self.assertIs(curses.set_term(b), a) + ++ def make_screens_of_different_terms(self): ++ # Two screens of different sizes and numbers of colors. Return them ++ # with the values of LINES, COLS, COLORS and COLOR_PAIRS for each. ++ screens = [] ++ with os_helper.EnvironmentVarGuard() as env: ++ for term, lines, cols in [('xterm', 25, 80), ++ ('xterm-256color', 30, 100)]: ++ env['LINES'] = str(lines) ++ env['COLUMNS'] = str(cols) ++ s = self.make_pty() ++ try: ++ screen = curses.newterm(term, s, s) ++ except curses.error: ++ self.skipTest(f'no {term} terminfo entry') ++ curses.start_color() ++ self.assertEqual(screen.stdscr.getmaxyx(), (lines, cols)) ++ screens.append((screen, self.get_screen_values())) ++ self.assertNotEqual(screens[0][1], screens[1][1]) ++ return screens ++ ++ def get_screen_values(self): ++ return curses.LINES, curses.COLS, curses.COLORS, curses.COLOR_PAIRS ++ ++ def test_set_term_refreshes_lines_cols_colors(self): ++ (a, avalues), (b, bvalues) = self.make_screens_of_different_terms() ++ self.assertIs(curses.set_term(a), b) ++ self.assertEqual(self.get_screen_values(), avalues) ++ self.assertIs(curses.set_term(b), a) ++ self.assertEqual(self.get_screen_values(), bvalues) ++ ++ @unittest.skipUnless(hasattr(curses.screen, 'use'), ++ 'requires curses.screen.use()') ++ def test_use_screen_refreshes_lines_cols_colors(self): ++ (a, avalues), (b, bvalues) = self.make_screens_of_different_terms() ++ self.assertEqual(a.use(lambda scr: self.get_screen_values()), avalues) ++ self.assertEqual(self.get_screen_values(), bvalues) ++ ++ def raise_in_use(scr): ++ self.assertEqual(self.get_screen_values(), avalues) ++ raise CustomError ++ with self.assertRaises(CustomError): ++ a.use(raise_in_use) ++ self.assertEqual(self.get_screen_values(), bvalues) ++ + def test_window_keeps_screen_alive(self): + # The standard window keeps its screen alive; dropping every other + # reference and collecting must not invalidate the window. +diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py +index d977a81876d..a1791a5ada7 100644 +--- a/Lib/test/test_dbm_dumb.py ++++ b/Lib/test/test_dbm_dumb.py +@@ -31,6 +31,7 @@ + b'd': b'way', + b'f': b'Guido', + b'g': b'intended', ++ b'h': bytearray(b'bytearray_value'), + '\u00fc'.encode('utf-8') : b'!', + } + +@@ -378,6 +379,14 @@ + self.assertTrue(b'key' in db) + self.assertEqual(db[b'key'], b'value') + ++ def test_bytearray(self): ++ self.init_db() ++ with contextlib.closing(dumbdbm.open(_fname, 'r')) as f: ++ self.assertEqual(f[b'h'], b'bytearray_value') ++ with contextlib.closing(dumbdbm.open(_fname)) as f: ++ with self.assertRaises(TypeError): ++ f[bytearray(b'bytearray_key')] = b'value' ++ + def test_open_with_pathlib_path(self): + dumbdbm.open(os_helper.FakePath(_fname), "c").close() + +diff --git a/Lib/test/test_dbm_sqlite3.py b/Lib/test/test_dbm_sqlite3.py +index f367a98865d..0e87bb1c6e8 100644 +--- a/Lib/test/test_dbm_sqlite3.py ++++ b/Lib/test/test_dbm_sqlite3.py +@@ -267,6 +267,10 @@ + (3.14, b"3.14"), + ("string", b"string"), + (b"bytes", b"bytes"), ++ (True, b"1"), ++ (False, b"0"), ++ (bytearray(b"bytearray"), b"bytearray"), ++ (memoryview(b"memoryview"), b"memoryview"), + ) + + def setUp(self): +diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py +index 9d013e3d2ca..9b1f8d5b8db 100644 +--- a/Lib/test/test_dict.py ++++ b/Lib/test/test_dict.py +@@ -121,18 +121,16 @@ + import_helper.import_module('_testcapi') + from test.support.script_helper import assert_python_ok + code = """if 1: +- import _testcapi ++ from test import support + items = {1: 2, 3: 4}.items() + ballast = [(i, i) for i in range(3000)] + held = [] + for start in range(1, 5): +- _testcapi.set_nomemory(start) + try: +- held.append(iter(items)) ++ with support.inject_memory_error_cm(start): ++ held.append(iter(items)) + except MemoryError: + pass +- finally: +- _testcapi.remove_mem_hooks() + """ + assert_python_ok('-c', code) + +diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py +index 3e562a26ad5..9879a665bc0 100644 +--- a/Lib/test/test_dis.py ++++ b/Lib/test/test_dis.py +@@ -454,53 +454,52 @@ + """ + + dis_traceback = """\ +-%4d RESUME 0 ++%4d RESUME 0 + +-%4d NOP ++%4d NOP + +-%4d L1: LOAD_SMALL_INT 1 +- LOAD_SMALL_INT 0 +- --> BINARY_OP 11 (/) +- POP_TOP ++%4d L1: LOAD_SMALL_INT 1 ++ LOAD_SMALL_INT 0 ++ --> BINARY_OP 11 (/) ++ POP_TOP + +-%4d L2: LOAD_FAST_CHECK 1 (tb) +- RETURN_VALUE ++%4d L2: LOAD_FAST_CHECK 1 (tb) ++ RETURN_VALUE + +- -- L3: PUSH_EXC_INFO ++ -- L3: PUSH_EXC_INFO + +-%4d LOAD_GLOBAL 0 (Exception) +- CHECK_EXC_MATCH +- POP_JUMP_IF_FALSE 24 (to L9) +- L4: NOT_TAKEN +- L5: STORE_FAST 0 (e) ++%4d LOAD_GLOBAL 0 (Exception) ++ CHECK_EXC_MATCH ++ POP_JUMP_IF_FALSE 24 (to L7) ++ NOT_TAKEN ++ STORE_FAST 0 (e) + +-%4d L6: LOAD_FAST 0 (e) +- LOAD_ATTR 2 (__traceback__) +- STORE_FAST 1 (tb) +- L7: POP_EXCEPT +- LOAD_COMMON_CONSTANT 7 (None) +- STORE_FAST 0 (e) +- DELETE_FAST 0 (e) ++%4d L4: LOAD_FAST 0 (e) ++ LOAD_ATTR 2 (__traceback__) ++ STORE_FAST 1 (tb) ++ L5: POP_EXCEPT ++ LOAD_COMMON_CONSTANT 7 (None) ++ STORE_FAST 0 (e) ++ DELETE_FAST 0 (e) + +-%4d LOAD_FAST 1 (tb) +- RETURN_VALUE ++%4d LOAD_FAST 1 (tb) ++ RETURN_VALUE + +- -- L8: LOAD_COMMON_CONSTANT 7 (None) +- STORE_FAST 0 (e) +- DELETE_FAST 0 (e) +- RERAISE 1 ++ -- L6: LOAD_COMMON_CONSTANT 7 (None) ++ STORE_FAST 0 (e) ++ DELETE_FAST 0 (e) ++ RERAISE 1 + +-%4d L9: RERAISE 0 ++%4d L7: RERAISE 0 + +- -- L10: COPY 3 +- POP_EXCEPT +- RERAISE 1 ++ -- L8: COPY 3 ++ POP_EXCEPT ++ RERAISE 1 + ExceptionTable: + L1 to L2 -> L3 [0] +- L3 to L4 -> L10 [1] lasti +- L5 to L6 -> L10 [1] lasti +- L6 to L7 -> L8 [1] lasti +- L8 to L10 -> L10 [1] lasti ++ L3 to L4 -> L8 [1] lasti ++ L4 to L5 -> L6 [1] lasti ++ L6 to L8 -> L8 [1] lasti + """ % (TRACEBACK_CODE.co_firstlineno, + TRACEBACK_CODE.co_firstlineno + 1, + TRACEBACK_CODE.co_firstlineno + 2, +@@ -569,11 +568,11 @@ + %4d L3: PUSH_EXC_INFO + WITH_EXCEPT_START + TO_BOOL +- POP_JUMP_IF_TRUE 2 (to L6) +- L4: NOT_TAKEN +- L5: RERAISE 2 +- L6: POP_TOP +- L7: POP_EXCEPT ++ POP_JUMP_IF_TRUE 2 (to L4) ++ NOT_TAKEN ++ RERAISE 2 ++ L4: POP_TOP ++ L5: POP_EXCEPT + POP_TOP + POP_TOP + POP_TOP +@@ -583,13 +582,12 @@ + LOAD_COMMON_CONSTANT 7 (None) + RETURN_VALUE + +- -- L8: COPY 3 ++ -- L6: COPY 3 + POP_EXCEPT + RERAISE 1 + ExceptionTable: + L1 to L2 -> L3 [2] lasti +- L3 to L4 -> L8 [4] lasti +- L5 to L7 -> L8 [4] lasti ++ L3 to L5 -> L6 [4] lasti + """ % (_with.__code__.co_firstlineno, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 2, +@@ -664,11 +662,11 @@ + L20: CLEANUP_THROW + L21: END_SEND + TO_BOOL +- POP_JUMP_IF_TRUE 2 (to L24) +- L22: NOT_TAKEN +- L23: RERAISE 2 +- L24: POP_TOP +- L25: POP_EXCEPT ++ POP_JUMP_IF_TRUE 2 (to L22) ++ NOT_TAKEN ++ RERAISE 2 ++ L22: POP_TOP ++ L23: POP_EXCEPT + POP_TOP + POP_TOP + POP_TOP +@@ -678,25 +676,24 @@ + LOAD_COMMON_CONSTANT 7 (None) + RETURN_VALUE + +- -- L26: COPY 3 ++ -- L24: COPY 3 + POP_EXCEPT + RERAISE 1 +- L27: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) ++ L25: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + RERAISE 1 + ExceptionTable: +- L1 to L3 -> L27 [0] lasti ++ L1 to L3 -> L25 [0] lasti + L3 to L4 -> L12 [5] +- L4 to L6 -> L27 [0] lasti ++ L4 to L6 -> L25 [0] lasti + L6 to L7 -> L16 [2] lasti +- L7 to L9 -> L27 [0] lasti ++ L7 to L9 -> L25 [0] lasti + L9 to L10 -> L14 [3] +- L10 to L13 -> L27 [0] lasti +- L14 to L15 -> L27 [0] lasti +- L16 to L18 -> L26 [4] lasti ++ L10 to L13 -> L25 [0] lasti ++ L14 to L15 -> L25 [0] lasti ++ L16 to L18 -> L24 [4] lasti + L18 to L19 -> L20 [8] +- L19 to L22 -> L26 [4] lasti +- L23 to L25 -> L26 [4] lasti +- L25 to L27 -> L27 [0] lasti ++ L19 to L23 -> L24 [4] lasti ++ L23 to L25 -> L25 [0] lasti + """ % (_asyncwith.__code__.co_firstlineno, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 2, +diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py +index d398c0789d2..ea5cc7c8871 100644 +--- a/Lib/test/test_dtrace.py ++++ b/Lib/test/test_dtrace.py +@@ -113,7 +113,7 @@ + proc.communicate(timeout=TERMINATE_TIMEOUT) # Clean up + + +-def run_readelf(cmd): ++def run_command(cmd): + # Force the C locale to disable localization. + env = dict(os.environ, LC_ALL="C") + try: +@@ -125,7 +125,7 @@ + env=env, + ) + except OSError: +- raise unittest.SkipTest("Couldn't find readelf on the path") ++ raise unittest.SkipTest(f"Couldn't find {cmd[0]!r} on the path") + + with proc: + stdout, stderr = proc.communicate() +@@ -528,6 +528,12 @@ + class CheckDtraceProbes(unittest.TestCase): + @classmethod + def setUpClass(cls): ++ if sys.platform == "sunos5": ++ # Solaris does not expose available probes through readable ELF ++ # section; it uses DTrace itself instead. ++ DTraceBackend().assert_usable() ++ return ++ + readelf_major_version, readelf_minor_version = cls.get_readelf_version() + if support.verbose: + print(f"readelf version: {readelf_major_version}.{readelf_minor_version}") +@@ -535,7 +541,7 @@ + + @staticmethod + def get_readelf_version(): +- version = run_readelf(["readelf", "--version"]) ++ version = run_command(["readelf", "--version"]) + + # Regex to parse: + # 'GNU readelf (GNU Binutils) 2.40.0\n' -> 2.40 +@@ -545,38 +551,50 @@ + + return int(match.group(1)), int(match.group(2)) + +- def get_readelf_output(self): +- return run_readelf(["readelf", "-n", get_probe_binary()]) ++ def get_probe_output(self): ++ if sys.platform == "sunos5": ++ # Get probes available in this process. ++ return run_command( ++ ["dtrace", "-l", "-P", f"python{os.getpid()}"] ++ ) ++ ++ return run_command(["readelf", "-n", get_probe_binary()]) ++ ++ def assert_probe_present(self, probe_name, output): ++ if sys.platform == "sunos5": ++ self.assertIn(probe_name.replace("__", "-"), output) ++ else: ++ self.assertIn(f"Name: {probe_name}", output) + + def test_check_probes(self): +- readelf_output = self.get_readelf_output() ++ probe_output = self.get_probe_output() + + available_probe_names = [ +- "Name: import__find__load__done", +- "Name: import__find__load__start", +- "Name: audit", +- "Name: gc__start", +- "Name: gc__done", +- "Name: function__entry", +- "Name: function__return", ++ "import__find__load__done", ++ "import__find__load__start", ++ "audit", ++ "gc__start", ++ "gc__done", ++ "function__entry", ++ "function__return", + ] + + for probe_name in available_probe_names: + with self.subTest(probe_name=probe_name): +- self.assertIn(probe_name, readelf_output) ++ self.assert_probe_present(probe_name, probe_output) + + @unittest.expectedFailure + def test_missing_probes(self): +- readelf_output = self.get_readelf_output() ++ probe_output = self.get_probe_output() + + # Missing probes will be added in the future. + missing_probe_names = [ +- "Name: line", ++ "line", + ] + + for probe_name in missing_probe_names: + with self.subTest(probe_name=probe_name): +- self.assertIn(probe_name, readelf_output) ++ self.assert_probe_present(probe_name, probe_output) + + + if __name__ == '__main__': +diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py +index 56ad446694d..974b0ceaafb 100644 +--- a/Lib/test/test_email/test_message.py ++++ b/Lib/test/test_email/test_message.py +@@ -1081,6 +1081,25 @@ + # AttributeError: 'str' object has no attribute 'is_attachment' + m.get_body() + ++ def test_get_body_malformed_nested_multipart(self): ++ # gh-159304: a nested multipart part without a boundary has a ++ # string payload, which get_body() must not iterate over. ++ msg = textwrap.dedent("""\ ++ From: sender@example.com ++ To: receiver@example.com ++ Subject: Malformed ++ Content-Type: multipart/related; boundary=OUTER ++ ++ --OUTER ++ Content-Type: multipart/related ++ ++ raw body no boundary ++ --OUTER-- ++ """) ++ m = self._str_msg(msg) ++ self.assertIsNone(m.get_body(('plain', 'html'))) ++ self.assertIs(m.get_body(('related',)), m) ++ + def test_get_bytes_payload_with_quoted_printable_encoding(self): + # We use a memoryview to avoid directly changing the private payload + # and to prevent using the dedicated paths for string or bytes objects. +diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py +index 02fb352502b..05c62e7990f 100644 +--- a/Lib/test/test_embed.py ++++ b/Lib/test/test_embed.py +@@ -440,6 +440,43 @@ + out, err = self.run_embedded_interpreter("test_repeated_init_exec", code) + self.assertEqual(out, 'Tests passed\n' * INIT_LOOPS) + ++ def test_asyncio_task_during_interpreter_clear(self): ++ import_helper.import_module('_asyncio') ++ code = textwrap.dedent(""" ++ import asyncio ++ import contextvars ++ import gc ++ import os ++ ++ gc.disable() ++ ++ async def noop(): ++ pass ++ ++ class Loop: ++ def get_debug(self): ++ return False ++ ++ def call_soon(self, callback, *args, context=None): ++ self.callback = callback ++ ++ class CreatesTaskOnClear: ++ def __del__(self): ++ asyncio.Task(noop(), loop=Loop(), context=contextvars.Context()) ++ os.write(1, b'task created\\n') ++ ++ # Keep the finalizer alive until interpreter_clear()'s last GC. ++ finalizer = CreatesTaskOnClear() ++ finalizer.cycle = finalizer ++ del finalizer ++ """) ++ # gh-159041: Delete the cleared interpreter with no thread state attached. ++ out, err = self.run_embedded_interpreter( ++ "test_interpreter_clear_and_delete", code, ++ timeout=support.SHORT_TIMEOUT) ++ self.assertEqual(out, "task created\n") ++ self.assertEqual(err, "") ++ + def test_simple_initialization_api(self): + # _testembed now uses Py_InitializeFromConfig by default + # This case specifically checks Py_Initialize(Ex) still works +diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py +index 9fff8e8bff9..d6243c3fc81 100644 +--- a/Lib/test/test_external_inspection.py ++++ b/Lib/test/test_external_inspection.py +@@ -475,6 +475,17 @@ + + @requires_remote_subprocess_debugging() + class TestSelfStackTrace(RemoteInspectionTestBase): ++ def _extract_coroutine_funcnames(self, trace): ++ return { ++ task.task_name: [ ++ frame.funcname.rpartition(".")[2] ++ for coro in task.coroutine_stack ++ for frame in coro.call_stack ++ ] ++ for info in trace ++ for task in info.awaited_by ++ } ++ + @skip_if_not_supported + def test_long_task_name_is_truncated(self): + # gh-157788 +@@ -508,6 +519,101 @@ + + self.assertEqual(asyncio.run(rec(3)), ["rec"] * 4) + ++ @skip_if_not_supported ++ def test_async_generator_stack_is_not_truncated(self): ++ # gh-159620 ++ async def agen(): ++ await asyncio.sleep(10_000) ++ yield ++ ++ async def agen_with_cleanup(): ++ try: ++ yield ++ finally: ++ await asyncio.sleep(10_000) ++ ++ async def outer_agen(): ++ async for _ in agen(): ++ yield ++ ++ async def consumer(gen): ++ async for _ in gen: ++ pass ++ ++ async def main(): ++ g = agen_with_cleanup() ++ await anext(g) ++ async_for_task = asyncio.create_task(consumer(agen()), name="async for") ++ nested_task = asyncio.create_task( ++ consumer(outer_agen()), name="nested async for") ++ anext_task = asyncio.create_task(anext(agen()), name="anext") ++ aclose_task = asyncio.create_task(g.aclose(), name="aclose") ++ await asyncio.sleep(0) ++ return RemoteUnwinder(os.getpid()).get_all_awaited_by() ++ ++ stacks = self._extract_coroutine_funcnames(asyncio.run(main())) ++ self.assertEqual(stacks["async for"], ["sleep", "agen", "consumer"]) ++ self.assertEqual(stacks["nested async for"], ++ ["sleep", "agen", "outer_agen", "consumer"]) ++ self.assertEqual(stacks["anext"], ["sleep", "agen"]) ++ self.assertEqual(stacks["aclose"], ["sleep", "agen_with_cleanup"]) ++ ++ @skip_if_not_supported ++ def test_async_generator_waiter_in_async_stack_trace(self): ++ # gh-159620 ++ async def agen(task): ++ await task ++ yield ++ ++ async def main(): ++ waiter_task = asyncio.create_task( ++ anext(agen(asyncio.current_task())), name="anext") ++ await asyncio.sleep(0) ++ return RemoteUnwinder(os.getpid()).get_async_stack_trace() ++ ++ stacks = self._extract_coroutine_funcnames(asyncio.run(main())) ++ self.assertEqual(stacks["anext"], ["agen"]) ++ ++ @skip_if_not_supported ++ def test_async_generator_running_task_stack(self): ++ # gh-159620 ++ async def agen(): ++ yield RemoteUnwinder(os.getpid()).get_async_stack_trace() ++ ++ async def main(): ++ return await asyncio.create_task(anext(agen()), name="anext") ++ ++ stacks = self._extract_coroutine_funcnames(asyncio.run(main())) ++ self.assertEqual(stacks["anext"], ["agen"]) ++ ++ @skip_if_not_supported ++ def test_class_named_like_async_generator_wrapper_is_not_walked(self): ++ # gh-159620 ++ async def decoy(): ++ await asyncio.sleep(10_000) ++ ++ class async_generator_asend: ++ __slots__ = ("gen",) ++ def __init__(self, gen): ++ self.gen = gen ++ def __await__(self): ++ return self ++ def __next__(self): ++ return None ++ ++ async def consumer(aw): ++ await aw ++ ++ async def main(): ++ with contextlib.closing(decoy()) as gen: ++ consumer_task = asyncio.create_task( ++ consumer(async_generator_asend(gen)), name="consumer") ++ await asyncio.sleep(0) ++ return RemoteUnwinder(os.getpid()).get_all_awaited_by() ++ ++ stacks = self._extract_coroutine_funcnames(asyncio.run(main())) ++ self.assertEqual(stacks["consumer"], ["consumer"]) ++ + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, +diff --git a/Lib/test/test_free_threading/test_datetime.py b/Lib/test/test_free_threading/test_datetime.py +new file mode 100644 +index 00000000000..6cea76b32d9 +--- /dev/null ++++ b/Lib/test/test_free_threading/test_datetime.py +@@ -0,0 +1,25 @@ ++import datetime ++import unittest ++ ++from test.support import threading_helper ++from test.support.threading_helper import run_concurrently ++ ++ ++NTHREADS = 10 ++ ++ ++@threading_helper.requires_working_threading() ++class TestDatetime(unittest.TestCase): ++ def test_concurrent_strftime_normalize_century(self): ++ d = datetime.date(2026, 4, 12) ++ results = [] ++ ++ def worker(): ++ results.append(d.strftime("%Y-%m-%d")) ++ ++ run_concurrently(worker_func=worker, nthreads=NTHREADS) ++ self.assertEqual(results, ["2026-04-12"] * NTHREADS) ++ ++ ++if __name__ == "__main__": ++ unittest.main() +diff --git a/Lib/test/test_free_threading/test_functions.py b/Lib/test/test_free_threading/test_functions.py +index 7a59638d27d..1874922d424 100644 +--- a/Lib/test/test_free_threading/test_functions.py ++++ b/Lib/test/test_free_threading/test_functions.py +@@ -81,6 +81,123 @@ + threading_helper.run_concurrently( + [set_annotations, set_annotate] * (NUM_THREADS // 2)) + ++ def test_read_while_replacing_name_attrs(self): ++ # Readers load __name__, __qualname__, __doc__ and __module__ ++ # without holding a lock. A writer thread replacing them must not ++ # free a value a reader thread is about to incref. ++ def target(): ++ yield ++ ++ def writer(): ++ for _ in range(ITERS): ++ target.__name__ = random_string() ++ target.__qualname__ = random_string() ++ target.__doc__ = random_string() ++ target.__module__ = random_string() ++ ++ def reader(): ++ for _ in range(ITERS): ++ target.__name__ ++ target.__qualname__ ++ target.__doc__ ++ target.__module__ ++ # make_gen() copies __name__ and __qualname__. ++ gen = target() ++ gen.__name__ ++ gen.__qualname__ ++ gen.close() ++ ++ threading_helper.run_concurrently( ++ [writer, reader] * (NUM_THREADS // 2)) ++ ++ def test_repr_while_replacing_qualname(self): ++ def target(): ++ pass ++ ++ def writer(): ++ for _ in range(ITERS): ++ target.__qualname__ = random_string() ++ ++ def reader(): ++ for _ in range(ITERS): ++ self.assertRegex(repr(target), r"^$") ++ ++ threading_helper.run_concurrently( ++ [writer, reader] * (NUM_THREADS // 2)) ++ ++ def test_c_api_read_while_replacing_module(self): ++ from test.support import import_helper ++ _testcapi = import_helper.import_module('_testcapi') ++ ++ def target(): ++ pass ++ ++ def writer(): ++ for _ in range(ITERS): ++ target.__module__ = random_string() ++ target.__module__ = object() ++ target.__module__ = None ++ ++ def reader(): ++ for _ in range(ITERS): ++ for get_module in (_testcapi.function_get_module, ++ _testcapi.function_get_module_macro): ++ value = get_module(target) ++ self.assertIn(type(value), (str, object, type(None))) ++ ++ threading_helper.run_concurrently( ++ [writer, reader] * (NUM_THREADS // 2)) ++ ++ def test_c_api_read_while_replacing_name(self): ++ from test.support import import_helper ++ _testlimitedcapi = import_helper.import_module('_testlimitedcapi') ++ ++ def target(): ++ pass ++ ++ def writer(): ++ for _ in range(ITERS): ++ # Non-ASCII names exercise lazy UTF-8 conversion. ++ target.__name__ = 'name_\u03bb' + random_string() ++ ++ def reader(): ++ for _ in range(ITERS): ++ name = _testlimitedcapi.eval_get_func_name(target) ++ self.assertIsInstance(name, str) ++ ++ threading_helper.run_concurrently( ++ [writer, reader] * (NUM_THREADS // 2)) ++ ++ def test_argument_errors_while_replacing_qualname(self): ++ def target(x, *, y): ++ pass ++ ++ def posonly(x, /): ++ pass ++ ++ calls = ( ++ lambda: target(), ++ lambda: target(1), ++ lambda: target(1, 2), ++ lambda: target(1, x=2), ++ lambda: target(1, extra=2), ++ lambda: posonly(x=1), ++ ) ++ ++ def writer(): ++ for _ in range(ITERS): ++ target.__qualname__ = random_string() ++ posonly.__qualname__ = random_string() ++ ++ def reader(): ++ for _ in range(ITERS): ++ for call in calls: ++ with self.assertRaises(TypeError): ++ call() ++ ++ threading_helper.run_concurrently( ++ [writer, reader] * (NUM_THREADS // 2)) ++ + def test_call_while_replacing_defaults(self): + # The eval loop reads __defaults__ and __kwdefaults__ without holding + # a lock while pushing a frame. +diff --git a/Lib/test/test_free_threading/test_races.py b/Lib/test/test_free_threading/test_races.py +index 23b48c76195..f08de7496eb 100644 +--- a/Lib/test/test_free_threading/test_races.py ++++ b/Lib/test/test_free_threading/test_races.py +@@ -272,6 +272,8 @@ + do_race(set_value, mutate) + + def test_racing_recursion_limit(self): ++ self.addCleanup(sys.setrecursionlimit, sys.getrecursionlimit()) ++ + def something_recursive(): + def count(n): + if n > 0: +diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py +index 9ebb76febd5..f091a6cb69c 100644 +--- a/Lib/test/test_funcattrs.py ++++ b/Lib/test/test_funcattrs.py +@@ -1,8 +1,10 @@ ++import gc + import textwrap + import types + import typing + import unittest + import warnings ++import weakref + from test import support + + +@@ -50,6 +52,35 @@ + def test_module(self): + self.assertEqual(self.b.__module__, __name__) + ++ def test_qualname_replaced_during_argument_error(self): ++ class Qualname(str): ++ pass ++ ++ def target(x, /): ++ pass ++ ++ target.__qualname__ = Qualname('original_qualname') ++ ref = weakref.ref(target.__qualname__) ++ ++ class Keyword(str): ++ __hash__ = str.__hash__ ++ ++ def __eq__(self, other): ++ # Replacing and freeing the qualname while comparisons ++ # execute Python must not leave a dangling pointer. ++ target.__qualname__ = 'new_qualname' ++ gc.collect() ++ return True ++ ++ with self.assertRaisesRegex( ++ TypeError, ++ r"new_qualname\(\) got some positional-only arguments " ++ r"passed as keyword arguments: 'x'", ++ ): ++ target(**{Keyword('x'): 1}) ++ gc.collect() ++ self.assertIsNone(ref()) ++ + def test_dir_includes_correct_attrs(self): + self.b.known_attr = 7 + self.assertIn('known_attr', dir(self.b), +diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py +index ad28e921978..1e5ec0d4e43 100644 +--- a/Lib/test/test_httplib.py ++++ b/Lib/test/test_httplib.py +@@ -1351,7 +1351,7 @@ + def test_malformed_content_length(self): + # RFC 9112: Content-Length = 1*DIGIT. Values that int() accepts but + # the grammar forbids must not be used to frame the body. +- for value in ('+5', '5_0'): ++ for value in ('+5', '5_0', '5 0'): + with self.subTest(value=value): + sock = FakeSocket( + 'HTTP/1.1 200 OK\r\nContent-Length: %s\r\n\r\nHello\r\n' % value) +@@ -1361,6 +1361,22 @@ + self.assertEqual(resp.read(), b'Hello\r\n') + resp.close() + ++ def test_content_length_with_whitespace(self): ++ # RFC 9112, section 5.1: optional whitespace (SP / HTAB) around ++ # the field value is not part of the value, so it must not ++ # prevent the Content-Length from being used to frame the body. ++ for value in ('7 ', '7\t', '7 \t ', ' 7 '): ++ with self.subTest(value=value): ++ sock = FakeSocket( ++ 'HTTP/1.1 200 OK\r\nContent-Length: %s\r\n\r\n' ++ 'Hello\r\nextra' % value) ++ resp = client.HTTPResponse(sock, method="GET") ++ resp.begin() ++ self.assertEqual(resp.length, 7) ++ self.assertEqual(resp.read(), b'Hello\r\n') ++ self.assertTrue(resp.isclosed()) ++ resp.close() ++ + def test_malformed_chunk_size(self): + # RFC 9112: chunk-size = 1*HEXDIG. Reject sizes that int(_, 16) accepts + # but the grammar forbids (a sign, an "0x" prefix, underscores or +@@ -2793,6 +2809,23 @@ + self.assertIsNotNone(exc) + self.assertTrue(sock.file_closed) + ++ def test_tunnel_nonstr_headers(self): ++ class CustomObject: ++ def __str__(self): ++ return 'custom-str' ++ ++ self.conn.set_tunnel('destination.com', headers={ ++ 'None-Value': None, ++ 'Integer-Value': 9, ++ 'Custom-Value': CustomObject(), ++ }) ++ ++ self.conn.request('HEAD', '/', '') ++ ++ self.assertIn(b'None-Value: None\r\n', self.conn.sock.data) ++ self.assertIn(b'Integer-Value: 9\r\n', self.conn.sock.data) ++ self.assertIn(b'Custom-Value: custom-str\r\n', self.conn.sock.data) ++ + + if __name__ == '__main__': + unittest.main(verbosity=2) +diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py +index 16d879a3831..a3f4659d871 100644 +--- a/Lib/test/test_isinstance.py ++++ b/Lib/test/test_isinstance.py +@@ -263,6 +263,20 @@ + self.assertEqual(True, issubclass(int, (int, (float, int)))) + self.assertEqual(True, issubclass(str, (str, (Child, str)))) + ++ def test_tuple_subclass_methods_not_called(self): ++ # gh-83731: make sure isinstance() reads the items of a tuple ++ # subclass directly, without calling its __iter__(), ++ # __getitem__() or __len__(). ++ class T(tuple): ++ def __iter__(self): ++ raise AssertionError('__iter__ called') ++ def __getitem__(self, index): ++ raise AssertionError('__getitem__ called') ++ def __len__(self): ++ raise AssertionError('__len__ called') ++ ++ self.assertTrue(isinstance(1, T((int,)))) ++ + @support.skip_if_huge_c_stack() + @support.skip_wasi_stack_overflow() + @support.skip_emscripten_stack_overflow() +diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py +index f1169a2aa20..fd74ce6827f 100644 +--- a/Lib/test/test_lazy_import/__init__.py ++++ b/Lib/test/test_lazy_import/__init__.py +@@ -308,6 +308,22 @@ + """) + assert_python_ok("-c", code) + ++ @support.run_with_limited_c_stack(depth=10_000, size=256 * 1024) ++ def test_deeply_dotted_name(self): ++ """A long dotted name must not overflow the C stack.""" ++ m = types.SimpleNamespace() ++ # Every lookup in m.m.m... succeeds, so resolve() only fails if the ++ # C stack runs out. ++ m.m = m ++ ns = {"__builtins__": {"__lazy_import__": __lazy_import__, ++ "__import__": lambda *args: m}} ++ # Free the chain on the main thread: a thread with a small C stack ++ # postpones deep deallocations and never gets back to them. ++ self.addCleanup(ns.clear) ++ exec("lazy import " + ".".join(["m"] * 10_000) + " as x", ns) ++ with contextlib.suppress(RecursionError): ++ ns["x"].resolve() ++ + + class SyntaxRestrictionTests(LazyImportTestCase): + """Tests for syntax restrictions on lazy imports.""" +diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py +index 1e0f6664af0..8e39552dba8 100644 +--- a/Lib/test/test_mimetypes.py ++++ b/Lib/test/test_mimetypes.py +@@ -271,6 +271,10 @@ + mimetypes.init() + self.assertEqual(guess_file_type("file.test2")[0], "testing/test2") + ++ @unittest.skipIf(sys.platform == "win32", "non-Windows only") ++ def test_default_knownfiles(self): ++ self.assertIn("/etc/mime.types", knownfiles) ++ + def test_added_types_are_used(self): + mimetypes.add_type('testing/default-type', '') + mime_type, _ = mimetypes.guess_type('') +@@ -555,6 +559,9 @@ + eq(self.db.guess_type("image.jpg"), ("image/jpeg", None)) + eq(self.db.guess_type("image.png"), ("image/png", None)) + ++ def test_default_knownfiles(self): ++ self.assertEqual(knownfiles, []) ++ + @unittest.skipIf(not hasattr(_winapi, "_mimetypes_read_windows_registry"), + "read_windows_registry accelerator unavailable") + def test_registry_accelerator(self): +diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py +index 5c2d69934b0..d1b2a4380be 100644 +--- a/Lib/test/test_monitoring.py ++++ b/Lib/test/test_monitoring.py +@@ -2529,6 +2529,37 @@ + foo() + sys.monitoring.set_local_events(0, foo.__code__, 0) + ++ def test_130279(self): ++ # gh-130279 ++ # An exception raised by the BRANCH_LEFT callback of a forward ++ # branch must be caught by the enclosing try ++ class Boom(Exception): ++ pass ++ ++ raised = False ++ ++ def callback(code, src, dst): ++ nonlocal raised ++ if not raised: ++ raised = True ++ raise Boom ++ ++ def foo(x): ++ try: ++ if x: ++ pass ++ except Boom: ++ return "caught" ++ return "not raised" ++ ++ sys.monitoring.register_callback(TEST_TOOL, E.BRANCH_LEFT, callback) ++ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.BRANCH_LEFT) ++ try: ++ self.assertEqual(foo(True), "caught") ++ finally: ++ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) ++ sys.monitoring.register_callback(TEST_TOOL, E.BRANCH_LEFT, None) ++ + + class TestOptimizer(MonitoringTestBase, unittest.TestCase): + +diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py +index f13ad46aac4..82115c33890 100644 +--- a/Lib/test/test_os/test_posix.py ++++ b/Lib/test/test_os/test_posix.py +@@ -1881,6 +1881,76 @@ + self.addCleanup(posix.unlink, fullname) + self.assertEqual(posix.readlink(name, dir_fd=dir_fd), 'symlink') + ++ ++ _support_readlink_with_fd = ( ++ hasattr(os, 'readlink') ++ and os.readlink in os.supports_fd ++ ) ++ ++ def _open_symlink_as_fd(self, path): ++ open_flags = os.O_RDONLY ++ if hasattr(os, "O_SYMLINK"): # macOS/iOS ++ open_flags |= os.O_SYMLINK ++ elif hasattr(os, "O_NOFOLLOW") and hasattr(os, "O_PATH"): # Linux/Android ++ open_flags |= os.O_NOFOLLOW | os.O_PATH ++ else: ++ self.fail("lacking open flags for this test") ++ return os.open(path, open_flags) ++ ++ @unittest.skipUnless(_support_readlink_with_fd, ++ "feature not supported on this platform") ++ def test_readlink_with_fd(self): ++ with self.prepare() as (dir_fd, name, fullname): ++ os.symlink("symlink", fullname) ++ self.addCleanup(posix.unlink, fullname) ++ fd = self._open_symlink_as_fd(fullname) ++ self.addCleanup(os.close, fd) ++ self.assertEqual(os.readlink(fd), "symlink") ++ ++ @unittest.skipUnless(_support_readlink_with_fd, ++ "feature not supported on this platform") ++ def test_readlink_with_fd_not_referring_to_symlink_throws(self): ++ with self.prepare_file() as (dir_fd, name, fullname): ++ fd = os.open(fullname, os.O_RDONLY) ++ self.addCleanup(os.close, fd) ++ # on Linux/Android, readlinkat("", fd, ...) fails with ENOENT, which ++ # Python translates to a FileNotFoundError, a subclass of OSError. ++ # On macOS, freadlink(fd, ...) fails with EINVAL, which gets raised ++ # as a OSError. ++ # So catching OSError here covers both cases. ++ with self.assertRaises(OSError): ++ os.readlink(fd) ++ ++ @unittest.skipUnless(_support_readlink_with_fd, ++ "feature not supported on this platform") ++ def test_readlink_with_fd_throws_if_both_fd_and_dir_fd_are_given(self): ++ with self.prepare() as (dir_fd, name, fullname): ++ os.symlink("symlink", fullname) ++ self.addCleanup(posix.unlink, fullname) ++ fd = self._open_symlink_as_fd(fullname) ++ self.addCleanup(os.close, fd) ++ with self.assertRaises(ValueError): ++ os.readlink(fd, dir_fd=dir_fd) ++ ++ @unittest.skipIf(_support_readlink_with_fd, ++ "feature is supported on this platform") ++ def test_readlink_with_fd_throws_not_implemented_error(self): ++ # on unsupported platforms, we may not even be able to get a ++ # file descriptor for a symlink, so use a fd for an ordinary file ++ os_helper.create_empty_file(os_helper.TESTFN) ++ self.addCleanup(os_helper.unlink, os_helper.TESTFN) ++ fd = os.open(os_helper.TESTFN, os.O_RDONLY) ++ self.addCleanup(os.close, fd) ++ with self.assertRaises(NotImplementedError): ++ os.readlink(fd) ++ ++ @unittest.skipUnless(sys.platform in ["linux", "android", "darwin", "ios"], ++ "feature not supported on this platform") ++ @support.requires_mac_ver(13, 0) ++ @support.requires_ios_ver(16, 0) ++ def test_readlink_is_marked_as_supports_fd(self): ++ self.assertIn(os.readlink, os.supports_fd) ++ + @unittest.skipUnless(os.rename in os.supports_dir_fd, "test needs dir_fd support in os.rename()") + def test_rename_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname), \ +@@ -2620,6 +2690,17 @@ + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.readlink("path", dir_fd=0) + ++ def test_freadlink(self): ++ self._verify_available("HAVE_FREADLINK") ++ if self.mac_ver >= (13, 0): ++ self.assertIn("HAVE_FREADLINK", posix._have_functions) ++ ++ else: ++ self.assertNotIn("HAVE_FREADLINK", posix._have_functions) ++ errmsg = "readlink cannot read file descriptors on this platform" ++ with self.assertRaisesRegex(NotImplementedError, errmsg): ++ os.readlink(0) ++ + def test_symlink(self): + self._verify_available("HAVE_SYMLINKAT") + if self.mac_ver >= (10, 10): +diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py +index 3503eaefaf8..b8e637a7729 100644 +--- a/Lib/test/test_platform.py ++++ b/Lib/test/test_platform.py +@@ -287,13 +287,21 @@ + if sys.platform == "android": + self.assertEqual(res.system, "Android") + self.assertEqual(res.release, platform.android_ver().release) +- elif sys.platform == "ios": + elif support.is_apple_mobile: # Platform module needs ctypes for full operation. If ctypes # isn't available, there's no ObjC module, and dummy values are @@ -368,6 +3527,231 @@ index 63c130813ec..5baa6145f4a 100644 else: self.assertEqual(res.system, "") self.assertEqual(res.release, "") +diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py +index 1e0f99f5f97..d748c07d60d 100644 +--- a/Lib/test/test_pyrepl/test_pyrepl.py ++++ b/Lib/test/test_pyrepl/test_pyrepl.py +@@ -690,6 +690,64 @@ + wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader) + self.assertIs(type(wrapper.get_line_buffer()), str) + ++ def test_input_does_not_add_history(self): ++ for history in ([], ["previous input"]): ++ with self.subTest(history=history): ++ reader = self.prepare_reader( ++ code_to_events("first input\nsecond input\n1+1\n") ++ ) ++ reader.history[:] = history ++ wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader) ++ ++ self.assertEqual(wrapper.input(), "first input") ++ self.assertEqual(reader.history, history) ++ self.assertEqual(wrapper.input(), "second input") ++ self.assertEqual(reader.history, history) ++ self.assertEqual(multiline_input(reader), "1+1") ++ self.assertEqual(reader.history, [*history, "1+1"]) ++ ++ def test_input_can_read_history(self): ++ events = [ ++ Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), ++ Event(evt="key", data="\n", raw=bytearray(b"\n")), ++ ] ++ reader = self.prepare_reader(events) ++ reader.history.append("previous input") ++ wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader) ++ ++ self.assertEqual(wrapper.input(), "previous input") ++ self.assertEqual(reader.history, ["previous input"]) ++ ++ def test_input_does_not_modify_history(self): ++ events = itertools.chain( ++ [Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))], ++ code_to_events(" edited"), ++ [Event(evt="key", data="down", raw=bytearray(b"\x1bOB"))], ++ code_to_events("new input\n"), ++ ) ++ reader = self.prepare_reader(events) ++ reader.history.append("previous input") ++ wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader) ++ ++ self.assertEqual(wrapper.input(), "new input") ++ self.assertEqual(reader.history, ["previous input"]) ++ ++ def test_input_error_does_not_modify_history(self): ++ for text, exception in ( ++ ("partial input\x03", KeyboardInterrupt), ++ ("\x04", EOFError), ++ ): ++ with self.subTest(exception=exception): ++ reader = self.prepare_reader(code_to_events(text + "1+1\n")) ++ reader.history.append("previous input") ++ wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader) ++ ++ with self.assertRaises(exception): ++ wrapper.input() ++ self.assertEqual(reader.history, ["previous input"]) ++ self.assertEqual(multiline_input(reader), "1+1") ++ self.assertEqual(reader.history, ["previous input", "1+1"]) ++ + def test_multiline_edit(self): + events = itertools.chain( + code_to_events("def f():\n...\n\n"), +diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py +index c74ca05ca66..af99437821d 100644 +--- a/Lib/test/test_sys.py ++++ b/Lib/test/test_sys.py +@@ -824,6 +824,16 @@ + self.assertIsInstance(sys._emscripten_info.pthreads, bool) + self.assertIsInstance(sys._emscripten_info.shared_memory, bool) + ++ @unittest.skipUnless(support.is_wasi, "only available on WASI") ++ def test_wasi_info(self): ++ info = sys._wasi_info ++ self.assertIn(info.wasi_version, {'p1', 'p2', 'p3'}) ++ self.assertIn('.', info.wasi_sdk_version) ++ sdk_major, _, sdk_minor = info.wasi_sdk_version.partition('.') ++ self.assertGreaterEqual(int(sdk_major), 1) ++ self.assertGreaterEqual(int(sdk_minor), 0) ++ self.assertIsInstance(info.cooperative_threads, bool) ++ + def test_43581(self): + # Can't use sys.stdout, as this is a StringIO object when + # the test runs under regrtest. +diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py +index 5b5d5c2f70f..7262fb0d383 100644 +--- a/Lib/test/test_warnings/__init__.py ++++ b/Lib/test/test_warnings/__init__.py +@@ -15,6 +15,7 @@ + from test.support import import_helper + from test.support import isolation + from test.support import os_helper ++from test.support import threading_helper + from test.support import warnings_helper + from test.support import force_not_colorized + from test.support.script_helper import assert_python_ok, assert_python_failure +@@ -1632,6 +1633,100 @@ + ): + c_warnings._release_lock() + ++ @support.cpython_only ++ @unittest.skipUnless(support.has_fork_support, 'requires working os.fork') ++ @threading_helper.requires_working_threading() ++ def test_fork_other_thread_holds_lock(self): ++ code = textwrap.dedent(''' ++ import _warnings ++ import os ++ import threading ++ import warnings ++ from test import support ++ ++ parent_pid = os.getpid() ++ read_fd, write_fd = os.pipe() ++ local = threading.local() ++ ready = threading.Event() ++ release = threading.Event() ++ warnings.simplefilter('ignore', DeprecationWarning) ++ warnings.simplefilter('ignore', ResourceWarning) ++ ++ class WarnOnDelete: ++ def __del__(self): ++ if os.getpid() != parent_pid: ++ # Even an ignored warning must acquire the lock. ++ warnings.warn('child cleanup', ResourceWarning) ++ os.write(write_fd, b'finalized') ++ ++ def worker(): ++ local.obj = WarnOnDelete() ++ _warnings._acquire_lock() ++ _warnings._acquire_lock() ++ try: ++ ready.set() ++ release.wait() ++ finally: ++ _warnings._release_lock() ++ _warnings._release_lock() ++ ++ thread = threading.Thread(target=worker) ++ thread.start() ++ # Release the worker before fork's parent-side warning attempts ++ # to acquire the warnings lock. ++ os.register_at_fork(after_in_parent=release.set) ++ try: ++ assert ready.wait(support.LONG_TIMEOUT) ++ pid = os.fork() ++ if pid == 0: ++ # The finalizer must run before os.fork() returns. ++ os._exit(0) ++ support.wait_process(pid, exitcode=0) ++ finally: ++ release.set() ++ thread.join() ++ os.close(write_fd) ++ assert os.read(read_fd, 100) == b'finalized' ++ os.close(read_fd) ++ ''') ++ assert_python_ok('-c', code) ++ ++ @support.cpython_only ++ @unittest.skipUnless(support.has_fork_support, 'requires working os.fork') ++ def test_fork_current_thread_holds_lock(self): ++ code = textwrap.dedent(''' ++ import _warnings ++ import os ++ import warnings ++ from test import support ++ ++ warnings.simplefilter('ignore') ++ for _ in range(3): ++ _warnings._acquire_lock() ++ pid = os.fork() ++ try: ++ # A warning must not change the inherited recursion depth. ++ warnings.warn('after fork') ++ for _ in range(3): ++ _warnings._release_lock() ++ try: ++ _warnings._release_lock() ++ except RuntimeError: ++ pass ++ else: ++ raise AssertionError('unexpected recursion depth') ++ _warnings._acquire_lock() ++ _warnings._release_lock() ++ except BaseException: ++ if pid == 0: ++ os._exit(1) ++ raise ++ if pid == 0: ++ os._exit(0) ++ support.wait_process(pid, exitcode=0) ++ ''') ++ assert_python_ok('-c', code) ++ + + class _DeprecatedTest(BaseTest, unittest.TestCase): + +diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py +index ce93481e273..bf9f17ce591 100644 +--- a/Lib/test/test_weakref.py ++++ b/Lib/test/test_weakref.py +@@ -162,6 +162,17 @@ + self.assertIn('MyConfig', repr(obj_weakref)) + self.assertIn('MyConfig', str(obj_weakref)) + ++ def test_repr_name_lookup_error(self): ++ # An error raised while looking up __name__ propagates. ++ class BadName: ++ @property ++ def __name__(self): ++ raise ZeroDivisionError ++ ++ obj = BadName() ++ with self.assertRaises(ZeroDivisionError): ++ repr(weakref.ref(obj)) ++ + def test_basic_callback(self): + self.check_basic_callback(C) + self.check_basic_callback(create_function) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 82f14ca968f..52e06832e6a 100644 --- a/Lib/test/test_webbrowser.py @@ -382,6 +3766,35 @@ index 82f14ca968f..52e06832e6a 100644 class IOSBrowserTest(unittest.TestCase): def _obj_ref(self, *args): # Construct a string representation of the arguments that can be used +diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py +index 8fb0a68deba..88cc55716f5 100644 +--- a/Lib/test/test_zipapp.py ++++ b/Lib/test/test_zipapp.py +@@ -366,6 +366,24 @@ + zipapp.create_archive(str(source), str(target), interpreter='python') + self.assertTrue(target.stat().st_mode & stat.S_IEXEC) + ++ @unittest.skipIf(sys.platform == 'win32', ++ 'Windows does not support an executable bit') ++ @os_helper.skip_unless_working_chmod ++ def test_copied_archive_with_pathlike_target_is_executable(self): ++ # Test that copying an archive to a PathLike target makes it executable. ++ source = self.tmpdir / 'source' ++ source.mkdir() ++ (source / '__main__.py').touch() ++ target = self.tmpdir / 'source.pyz' ++ zipapp.create_archive(source, target, interpreter='python') ++ # Copying an archive uses a different code path than creating ++ # one from scratch. Ensure that the executable bit is set ++ # even if the target is a path-like object. ++ # See https://github.com/python/cpython/issues/156568 ++ new_target = self.tmpdir / 'changed.pyz' ++ zipapp.create_archive(target, new_target, interpreter='python') ++ self.assertTrue(new_target.stat().st_mode & stat.S_IEXEC) ++ + @unittest.skipIf(sys.platform == 'win32', + 'Windows does not support an executable bit') + def test_no_shebang_is_not_executable(self): diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index ec8b544a6b0..26020844023 100644 --- a/Lib/webbrowser.py @@ -409,13 +3822,26 @@ index ec8b544a6b0..26020844023 100644 from _ios_support import objc if objc: # If objc exists, we know ctypes is also importable. +diff --git a/Lib/zipapp.py b/Lib/zipapp.py +index a1cef18ada9..39e765d1ffd 100644 +--- a/Lib/zipapp.py ++++ b/Lib/zipapp.py +@@ -69,7 +69,7 @@ + dst.write(first_2) + shutil.copyfileobj(src, dst) + +- if interpreter and isinstance(new_archive, str): ++ if interpreter and isinstance(new_archive, (str, os.PathLike)): + os.chmod(new_archive, os.stat(new_archive).st_mode | stat.S_IEXEC) + + diff --git a/Makefile.pre.in b/Makefile.pre.in -index 965244d8d2d..c623be1776b 100644 +index 36515dbd146..6206b6a3ad0 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in -@@ -215,6 +215,12 @@ - # the build, and is only listed here so it will be included in sysconfigdata. - IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@ +@@ -217,6 +217,12 @@ + + BUILD_DETAILS=@BUILD_DETAILS@ +# visionOS Deployment target is *actually* used during the build, by the +# compiler shims; export. @@ -426,6 +3852,40 @@ index 965244d8d2d..c623be1776b 100644 # Option to install to strip binaries STRIPFLAG=-s +@@ -2286,7 +2292,9 @@ + # Run the test suite on the iOS simulator. Must be run on a macOS machine with + # a full Xcode install that has an iPhone SE (3rd edition) simulator available. + # This must be run *after* a `make install` has completed the build. The +-# `--with-framework-name` argument *cannot* be used when configuring the build. ++# testbed is configured for the framework name used to configure the build; ++# that name is expected to be `Python`, optionally followed by uppercased ABI ++# flags (e.g., `PythonT`). + XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID + .PHONY: testios + testios: +@@ -2298,10 +2306,6 @@ + echo "Cannot run the iOS testbed for non-simulator builds."; \ + exit 1;\ + fi +- @if test $(PYTHONFRAMEWORK) != "Python"; then \ +- echo "Cannot run the iOS testbed with a non-default framework name."; \ +- exit 1;\ +- fi + @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \ + echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \ + exit 1;\ +diff --git a/Misc/ACKS b/Misc/ACKS +index ad1b1119aa2..00ed03ff51c 100644 +--- a/Misc/ACKS ++++ b/Misc/ACKS +@@ -1144,6 +1144,7 @@ + Ben Lewis + William Lewis + Akira Li ++Han Li + Jiahao Li + Robert Li + Xuanji Li diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index f5cd73bdea8..6c1863c943b 100644 --- a/Misc/platform_triplet.c @@ -463,576 +3923,4241 @@ index f5cd73bdea8..6c1863c943b 100644 // Older macOS SDKs do not define TARGET_OS_OSX # elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX PLATFORM_TRIPLET=darwin -diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py -index 4d29c240baa..704bd2fd5b9 100644 ---- a/Platforms/Apple/__main__.py -+++ b/Platforms/Apple/__main__.py -@@ -5,17 +5,20 @@ - # This script simplifies the process of configuring, compiling and packaging an - # XCframework for an Apple platform. - # --# At present, it only supports iOS, but it has been constructed so that it --# could be used on any Apple platform. -+# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been -+# constructed so that it could be used on any Apple platform. - # - # The simplest entry point is: - # - # $ python Platforms/Apple ci iOS - # -+# (replace iOS with tvOS, visionOS or watchOS as required.) -+# - # which will: - # * Clean any pre-existing build artefacts - # * Configure and make a Python that can be used for the build --# * Configure and make a Python for each supported iOS architecture and ABI -+# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS -+# architecture and ABI - # * Combine the outputs of the builds from the previous step into a single - # XCframework, merging binaries into a "fat" binary if necessary - # * Clone a copy of the testbed, configured to use the XCframework -@@ -75,6 +78,32 @@ - "x86_64-apple-ios-simulator": "x86_64-iphonesimulator", - }, - }, -+ "tvOS": { -+ "tvos-arm64": { -+ "arm64-apple-tvos": "arm64-appletvos", -+ }, -+ "tvos-arm64_x86_64-simulator": { -+ "arm64-apple-tvos-simulator": "arm64-appletvsimulator", -+ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator", -+ }, -+ }, -+ "visionOS": { -+ "xros-arm64": { -+ "arm64-apple-xros": "arm64-xros", -+ }, -+ "xros-arm64-simulator": { -+ "arm64-apple-xros-simulator": "arm64-xrsimulator", -+ }, -+ }, -+ "watchOS": { -+ "watchos-arm64_32": { -+ "arm64_32-apple-watchos": "arm64_32-watchos", -+ }, -+ "watchos-arm64_x86_64-simulator": { -+ "arm64-apple-watchos-simulator": "arm64-watchsimulator", -+ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator", -+ }, -+ }, +diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c +index 401b777be23..b2b410cfe5f 100644 +--- a/Modules/_cursesmodule.c ++++ b/Modules/_cursesmodule.c +@@ -209,6 +209,8 @@ + /* Tells whether start_color() has been called to initialise color usage. */ + static int curses_start_color_called = FALSE; + ++static int update_lines_cols(PyObject *private_module); ++ + /* Encoding of the initial screen, used by module-level functions that have + no window object to take it from (e.g. unctrl(), ungetch()). This is a + private copy: the window object that initscr() returns may be deallocated +@@ -5480,7 +5482,13 @@ + static int + curses_use_screen_cb(SCREEN *Py_UNUSED(sp), void *data) + { +- curses_use_call((curses_use_data *)data); ++ curses_use_data *u = (curses_use_data *)data; ++ PyObject *module = PyType_GetModule(Py_TYPE(u->obj)); ++ if (module == NULL || !update_lines_cols(module)) { ++ u->result = NULL; ++ return 0; ++ } ++ curses_use_call(u); + return 0; } +@@ -5502,12 +5510,27 @@ + if (curses_use_parse(args, &func, &extra) < 0) { + return NULL; + } ++ PyObject *module = PyType_GetModule(Py_TYPE(self)); ++ if (module == NULL) { ++ Py_DECREF(extra); ++ return NULL; ++ } + curses_use_data data = {self, func, extra, kwargs, NULL}; + PyObject *prev = state->topscreen; + state->topscreen = Py_NewRef(self); + use_screen(so->screen, curses_use_screen_cb, &data); + Py_SETREF(state->topscreen, prev); + Py_DECREF(extra); ++ if (data.result == NULL) { ++ PyObject *exc = PyErr_GetRaisedException(); ++ (void)update_lines_cols(module); ++ _PyErr_ChainExceptions1(exc); ++ } ++ else { ++ if (!update_lines_cols(module)) { ++ Py_CLEAR(data.result); ++ } ++ } + return data.result; + } + #endif /* HAVE_CURSES_USE_SCREEN */ +@@ -7073,8 +7096,6 @@ + Py_RETURN_NONE; + } -@@ -136,11 +165,24 @@ - print(f"export {key}={shlex.quote(value)}") +-static int update_lines_cols(PyObject *private_module); /* defined below */ +- + /* Return a file descriptor for obj, or, if obj is NULL or None, for the + sys. stream. Returns -1 with an exception set on error. */ + static int +@@ -7930,7 +7951,11 @@ + goto error; + } +- o = PyLong_FromLong(LINES); ++ int lines = LINES; ++ if (stdscr != NULL) { ++ lines = getmaxy(stdscr); ++ } ++ o = PyLong_FromLong(lines); + if (o == NULL) { + goto error; + } +@@ -7942,7 +7967,11 @@ + } + Py_DECREF(o); -+def platform_for_host(host): -+ """Determine the platform for a given host triple.""" -+ for plat, slices in HOSTS.items(): -+ for _, candidates in slices.items(): -+ for candidate in candidates: -+ if candidate == host: -+ return plat -+ raise KeyError(host) +- o = PyLong_FromLong(COLS); ++ int cols = COLS; ++ if (stdscr != NULL) { ++ cols = getmaxx(stdscr); ++ } ++ o = PyLong_FromLong(cols); + if (o == NULL) { + goto error; + } +@@ -7953,6 +7982,32 @@ + goto error; + } + Py_DECREF(o); ++ ++ if (curses_start_color_called) { ++ o = PyLong_FromUnsignedLongLong((unsigned long long)COLORS); ++ if (o == NULL) { ++ goto error; ++ } ++ if (PyDict_SetItemString(exposed_module_dict, "COLORS", o) < 0) { ++ goto error; ++ } ++ if (PyDict_SetItemString(private_module_dict, "COLORS", o) < 0) { ++ goto error; ++ } ++ Py_DECREF(o); ++ ++ o = PyLong_FromUnsignedLongLong((unsigned long long)COLOR_PAIRS); ++ if (o == NULL) { ++ goto error; ++ } ++ if (PyDict_SetItemString(exposed_module_dict, "COLOR_PAIRS", o) < 0) { ++ goto error; ++ } ++ if (PyDict_SetItemString(private_module_dict, "COLOR_PAIRS", o) < 0) { ++ goto error; ++ } ++ Py_DECREF(o); ++ } + Py_DECREF(exposed_module); + return 1; + +diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c +index bd76b3bd81c..dbd14f1d707 100644 +--- a/Modules/_datetimemodule.c ++++ b/Modules/_datetimemodule.c +@@ -1783,17 +1783,19 @@ + normalize_century(void) + { + static int cache = -1; +- if (cache < 0) { ++ int res = FT_ATOMIC_LOAD_INT_RELAXED(cache); ++ if (res < 0) { + char year[5]; + struct tm date = { + .tm_year = -1801, + .tm_mon = 0, + .tm_mday = 1 + }; +- cache = (strftime(year, sizeof(year), "%Y", &date) && +- strcmp(year, "0099") != 0); ++ res = (strftime(year, sizeof(year), "%Y", &date) && ++ strcmp(year, "0099") != 0); ++ FT_ATOMIC_STORE_INT_RELAXED(cache, res); + } +- return cache; ++ return res; + } + + static PyObject * +diff --git a/Modules/_remote_debugging/asyncio.c b/Modules/_remote_debugging/asyncio.c +index c8d79774a47..884065b6efd 100644 +--- a/Modules/_remote_debugging/asyncio.c ++++ b/Modules/_remote_debugging/asyncio.c +@@ -240,11 +240,63 @@ + * COROUTINE CHAIN PARSING + * ============================================================================ */ + ++// gh-159620 ++/* Return in *coro_addr the coroutine or async generator to walk for the ++ awaitable at addr, or 0 if it is neither (for example, a future). */ ++static int ++get_coro_like_address( ++ RemoteUnwinderObject *unwinder, ++ uintptr_t addr, ++ uintptr_t *coro_addr ++) { ++ *coro_addr = 0; ++ ++ // Skip user classes that share a name with the generator types below ++ uintptr_t type_addr; ++ unsigned long tp_flags; ++ if (read_ptr(unwinder, addr + (uintptr_t)unwinder->debug_offsets.pyobject.ob_type, ++ &type_addr) < 0 ++ || _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, ++ type_addr + (uintptr_t)unwinder->debug_offsets.type_object.tp_flags, ++ sizeof(tp_flags), &tp_flags) < 0) ++ { ++ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read awaitable type"); ++ return -1; ++ } + ++ if (tp_flags & Py_TPFLAGS_HEAPTYPE) { ++ return 0; ++ } + - def apple_env(host: str) -> EnvironmentT: - """Construct an Apple development environment for the given host.""" - env = { - "PATH": ":".join([ -- str(PYTHON_DIR / "Platforms/Apple/iOS/Resources/bin"), -+ str( -+ PYTHON_DIR -+ / f"Platforms/Apple/{platform_for_host(host)}/Resources/bin" -+ ), - str(subdir(host) / "prefix"), - "/usr/bin", - "/bin", -@@ -314,8 +356,8 @@ - Downloads binaries if they aren't already present. Downloads will be stored - in provided cache directory. ++ uintptr_t name_addr; ++ char name[32] = {0}; ++ if (read_ptr(unwinder, type_addr + (uintptr_t)unwinder->debug_offsets.type_object.tp_name, ++ &name_addr) < 0 ++ || _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, name_addr, ++ sizeof(name) - 1, name) < 0) ++ { ++ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read awaitable type name"); ++ return -1; ++ } ++ ++ if (strcmp(name, "coroutine") == 0) { ++ *coro_addr = addr; ++ } ++ else if (strcmp(name, "async_generator_asend") == 0 ++ || strcmp(name, "async_generator_athrow") == 0) ++ { ++ // asend and athrow are separate wrapper objects: read the pointer ++ // to their parent async generator, stored right after PyObject_HEAD ++ return read_ptr(unwinder, addr + (uintptr_t)unwinder->debug_offsets.pyobject.size, ++ coro_addr); ++ } ++ return 0; ++} ++ + static int + get_awaited_coro_address( + RemoteUnwinderObject *unwinder, + uintptr_t gi_iframe_addr, +- uintptr_t gen_type_addr, + uintptr_t *next_coro + ) { + *next_coro = 0; +@@ -272,41 +324,14 @@ + uintptr_t stackpointer_addr = GET_MEMBER_NO_TAG(uintptr_t, iframe, unwinder->debug_offsets.interpreter_frame.stackpointer); -- On iOS, as a safety mechanism, any dynamic libraries will be purged from -- the unpacked dependencies. -+ On non-macOS platforms, as a safety mechanism, any dynamic libraries will -+ be purged from the unpacked dependencies. - """ - # To create new builds of these dependencies, usually all that's necessary - # is to push a tag to the cpython-apple-source-deps repository, and GitHub -@@ -340,9 +382,9 @@ - ) - shutil.unpack_archive(archive_path, prefix_dir) + if ((void*)stackpointer_addr != NULL) { +- uintptr_t gi_await_addr; + err = read_py_ptr( + unwinder, + stackpointer_addr - sizeof(void*) * 2, +- &gi_await_addr); ++ next_coro); + if (err) { + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read gi_await address"); + return -1; + } +- +- if ((void*)gi_await_addr != NULL) { +- uintptr_t gi_await_addr_type_addr; +- err = read_ptr( +- unwinder, +- gi_await_addr + (uintptr_t)unwinder->debug_offsets.pyobject.ob_type, +- &gi_await_addr_type_addr); +- if (err) { +- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read gi_await type address"); +- return -1; +- } +- +- if (gen_type_addr == gi_await_addr_type_addr) { +- /* This needs an explanation. We always start with parsing +- native coroutine / generator frames. Ultimately they +- are awaiting on something. That something can be +- a native coroutine frame or... an iterator. +- If it's the latter -- we can't continue building +- our chain. So the condition to bail out of this is +- to do that when the type of the current coroutine +- doesn't match the type of whatever it points to +- in its cr_await. +- */ +- *next_coro = gi_await_addr; +- } +- } + } -- # Dynamic libraries will be preferentially linked over static; -- # On iOS, ensure that no dylibs are available in the prefix folder. -- if platform == "iOS": -+ # Dynamic libraries will be preferentially linked over static; On non-macOS -+ # platforms, ensure that no dylibs are available in the prefix folder. -+ if platform != "macOS": - for dylib in prefix_dir.glob("**/*.dylib"): - dylib.unlink() + return 0; +@@ -329,6 +354,13 @@ + return -1; + } -@@ -409,6 +451,7 @@ - f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}", - f"--with-build-python={build_python_path()}", - "--with-system-libmpdec", -+ "--enable-ipv6", - "--enable-framework", - # Dependent libraries. - f"--with-openssl={prefix_dir}", -@@ -451,7 +494,8 @@ - """ - return ( - CROSS_BUILD_DIR -- / f"{host_triple}/Platforms/Apple/iOS/Frameworks/{multiarch}" -+ / f"{host_triple}/Platforms/Apple/{platform_for_host(host_triple)}" -+ / f"Frameworks/{multiarch}" - ) ++ if (get_coro_like_address(unwinder, coro_address, &coro_address) < 0) { ++ return -1; ++ } ++ if ((void*)coro_address == NULL) { ++ return 0; ++ } ++ + // Read the entire generator object at once + char gen_object[SIZEOF_GEN_OBJ]; + int err = _Py_RemoteDebug_PagedReadRemoteMemory( +@@ -346,8 +378,6 @@ + return 0; + } +- uintptr_t gen_type_addr = GET_MEMBER(uintptr_t, gen_object, unwinder->debug_offsets.pyobject.ob_type); +- + PyObject* name = NULL; -@@ -673,7 +717,7 @@ - host_path = ( - CROSS_BUILD_DIR - / host_triple -- / "Platforms/Apple/iOS/Frameworks" -+ / f"Platforms/Apple/{platform}/Frameworks" - / multiarch - ) - host_framework = host_path / "Python.framework" -@@ -719,18 +763,20 @@ - # Create an XCframework - version = create_xcframework(context.platform) - -- # Clone testbed -- print() -- run([ -- sys.executable, -- "Platforms/Apple/testbed", -- "clone", -- "--platform", -- context.platform, -- "--framework", -- CROSS_BUILD_DIR / context.platform / "Python.xcframework", -- CROSS_BUILD_DIR / context.platform / "testbed", -- ]) -+ # watchOS doesn't have a testbed (yet!) -+ if context.platform != "watchOS": -+ # Clone testbed -+ print() -+ run([ -+ sys.executable, -+ "Platforms/Apple/testbed", -+ "clone", -+ "--platform", -+ context.platform, -+ "--framework", -+ CROSS_BUILD_DIR / context.platform / "Python.xcframework", -+ CROSS_BUILD_DIR / context.platform / "testbed", -+ ]) - - # Build the final archive - archive_name = ( -diff --git a/Platforms/Apple/iOS/Resources/Info.plist.in b/Platforms/Apple/iOS/Resources/Info.plist.in -index c3e261ecd9e..26ef7a95de4 100644 ---- a/Platforms/Apple/iOS/Resources/Info.plist.in -+++ b/Platforms/Apple/iOS/Resources/Info.plist.in -@@ -17,13 +17,13 @@ - CFBundlePackageType - FMWK - CFBundleShortVersionString -- @VERSION@ -+ %VERSION% - CFBundleLongVersionString - %VERSION%, (c) 2001-2024 Python Software Foundation. - CFBundleSignature - ???? - CFBundleVersion -- 1 -+ %VERSION% - CFBundleSupportedPlatforms - - iPhoneOS -diff --git a/Platforms/Apple/testbed/Python.xcframework/Info.plist b/Platforms/Apple/testbed/Python.xcframework/Info.plist -index c6418de6e74..0587f4735f7 100644 ---- a/Platforms/Apple/testbed/Python.xcframework/Info.plist -+++ b/Platforms/Apple/testbed/Python.xcframework/Info.plist -@@ -35,6 +35,98 @@ - SupportedPlatformVariant - simulator - -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ tvos-arm64 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ -+ SupportedPlatform -+ tvos -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ tvos-arm64_x86_64-simulator -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ x86_64 -+ -+ SupportedPlatform -+ tvos -+ SupportedPlatformVariant -+ simulator -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ xros-arm64-simulator -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ -+ SupportedPlatform -+ xros -+ SupportedPlatformVariant -+ simulator -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ xros-arm64 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ -+ SupportedPlatform -+ xros -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ watchos-arm64_x86_64-simulator -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ x86_64 -+ -+ SupportedPlatform -+ watchos -+ SupportedPlatformVariant -+ simulator -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ watchos-arm64_32 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64_32 -+ -+ SupportedPlatform -+ watchos -+ - - CFBundlePackageType - XFWK -diff --git a/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist -new file mode 100644 -index 00000000000..a20d476fa7b ---- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ tvOS -+ -+ MinimumOSVersion -+ 9.0 -+ CFBundleVersion -+ 1 -+ -+ -diff --git a/Platforms/Apple/testbed/Python.xcframework/build/utils.sh b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh -index e54471f68b7..2d3b9320b3c 100755 ---- a/Platforms/Apple/testbed/Python.xcframework/build/utils.sh -+++ b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh -@@ -34,9 +34,38 @@ - else - SLICE_FOLDER="ios-arm64_x86_64-simulator" - fi + // Parse the previous frame using the gi_iframe from local copy +@@ -374,8 +404,7 @@ + return 0; + } + +- if (get_awaited_coro_address(unwinder, gi_iframe_addr, gen_type_addr, +- &coro_address) < 0) { ++ if (get_awaited_coro_address(unwinder, gi_iframe_addr, &coro_address) < 0) { + return -1; + } + } +@@ -733,6 +762,10 @@ + return -1; + } + ++ if (get_coro_like_address(unwinder, running_coro_addr, &running_coro_addr) < 0) { ++ return -1; ++ } ++ + *frame_addr = running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe; + return 0; + } +diff --git a/Modules/_testcapi/function.c b/Modules/_testcapi/function.c +index 40767adbd3f..28420ce19f1 100644 +--- a/Modules/_testcapi/function.c ++++ b/Modules/_testcapi/function.c +@@ -38,6 +38,13 @@ + } + + ++static PyObject * ++function_get_module_macro(PyObject *self, PyObject *func) ++{ ++ return Py_XNewRef(PyFunction_GET_MODULE(func)); ++} ++ ++ + static PyObject * + function_get_defaults(PyObject *self, PyObject *func) + { +@@ -134,6 +141,7 @@ + {"function_get_code", function_get_code, METH_O, NULL}, + {"function_get_globals", function_get_globals, METH_O, NULL}, + {"function_get_module", function_get_module, METH_O, NULL}, ++ {"function_get_module_macro", function_get_module_macro, METH_O, NULL}, + {"function_get_defaults", function_get_defaults, METH_O, NULL}, + {"function_set_defaults", function_set_defaults, METH_VARARGS, NULL}, + {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL}, +diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c +index f629b8e528f..479297a996c 100644 +--- a/Modules/_testinternalcapi.c ++++ b/Modules/_testinternalcapi.c +@@ -1182,7 +1182,8 @@ + assert(module != NULL); + module_state *state = get_module_state(module); + Py_DECREF(module); +- int res = PyList_Append(state->record_list, func->func_name); ++ int res = PyList_Append(state->record_list, ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name)); + if (res < 0) { + return NULL; + } +@@ -1243,7 +1244,8 @@ + assert(module != NULL); + module_state *state = get_module_state(module); + Py_DECREF(module); +- int res = PyList_Append(state->record_list, func->func_name); ++ int res = PyList_Append(state->record_list, ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name)); + if (res < 0) { + return NULL; + } +diff --git a/Modules/_testinternalcapi/tokenizer.c b/Modules/_testinternalcapi/tokenizer.c +index 0c2bdee2e80..f41a517deef 100644 +--- a/Modules/_testinternalcapi/tokenizer.c ++++ b/Modules/_testinternalcapi/tokenizer.c +@@ -95,6 +95,35 @@ + goto error; + } + ++ _PyTok_SourceClear(&source); ++ if (check(_PyTok_SourceAppendLineWithNewline(&source, "", 0) == 0, ++ "cannot append empty terminated line") < 0 || ++ check(_PyTok_SourceAppendLineWithNewline(&source, tail, 4) == 1, ++ "wrong terminated line offset") < 0 || ++ check(source.len == 6 && source.nlines == 2 && ++ memcmp(source.bytes, "\ntail\n", 7) == 0, ++ "wrong terminated source contents") < 0 || ++ check_system_error(_PyTok_SourceAppendLineWithNewline( ++ &source, terminated_line, 2) < 0, ++ "accepted newline in line payload") < 0) { ++ goto error; ++ } ++ source.base_offset = PY_SSIZE_T_MAX - source.len; ++ if (check(_PyTok_SourceAppendLineWithNewline(&source, "", 0) < 0 && ++ PyErr_ExceptionMatches(PyExc_MemoryError), ++ "accepted overflowing appended newline") < 0) { ++ goto error; ++ } ++ PyErr_Clear(); ++ source.base_offset = 0; ++ source.nlines = INT_MAX - 1; ++ if (check(_PyTok_SourceAppendLineWithNewline(&source, "", 0) < 0 && ++ PyErr_ExceptionMatches(PyExc_OverflowError), ++ "accepted overflowing newline count") < 0) { ++ goto error; ++ } ++ PyErr_Clear(); ++ + _PyTok_SourceClear(&source); + Py_RETURN_NONE; + +diff --git a/Modules/_testlimitedcapi/unicode.c b/Modules/_testlimitedcapi/unicode.c +index 3d88cbb9fb3..6e3b15ef76a 100644 +--- a/Modules/_testlimitedcapi/unicode.c ++++ b/Modules/_testlimitedcapi/unicode.c +@@ -993,7 +993,7 @@ + { + int code_page; + PyObject *unicode; +- const char *errors; ++ const char *errors = NULL; + + if (!PyArg_ParseTuple(args, "iO|z", &code_page, &unicode, &errors)) + return NULL; +@@ -1010,7 +1010,7 @@ + { + const char *data; + Py_ssize_t size; +- const char *errors; ++ const char *errors = NULL; + + if (!PyArg_ParseTuple(args, "y#|z", &data, &size, &errors)) + return NULL; +@@ -1024,7 +1024,7 @@ + { + const char *data; + Py_ssize_t size; +- const char *errors; ++ const char *errors = NULL; + + if (!PyArg_ParseTuple(args, "y#|z", &data, &size, &errors)) + return NULL; +@@ -1037,7 +1037,7 @@ + unicode_encodelocale(PyObject *self, PyObject *args) + { + PyObject *unicode; +- const char *errors; ++ const char *errors = NULL; + + if (!PyArg_ParseTuple(args, "O|z", &unicode, &errors)) + return NULL; +@@ -1331,7 +1331,7 @@ + { + PyObject *left; + const char *right = NULL; +- Py_ssize_t right_len; ++ Py_ssize_t right_len = 0; + int result; + + if (!PyArg_ParseTuple(args, "O|y#", &left, &right, &right_len)) +@@ -1370,7 +1370,7 @@ + { + PyObject *left; + const char *right = NULL; +- Py_ssize_t right_len; ++ Py_ssize_t right_len = 0; + Py_ssize_t size = -100; + int result; + +diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h +index 7f4bab7de94..3c3e46e065f 100644 +--- a/Modules/clinic/posixmodule.c.h ++++ b/Modules/clinic/posixmodule.c.h +@@ -6647,7 +6647,10 @@ + "that directory.\n" + "\n" + "dir_fd may not be implemented on your platform. If it is unavailable,\n" +-"using it will raise a NotImplementedError."); ++"using it will raise a NotImplementedError.\n" ++"\n" ++"On Linux, Android, macOS and iOS, path may be a file descriptor\n" ++"referring to a symlink. If it is, dir_fd must be None."); + + #define OS_READLINK_METHODDEF \ + {"readlink", _PyCFunction_CAST(os_readlink), METH_FASTCALL|METH_KEYWORDS, os_readlink__doc__}, +@@ -6688,7 +6691,7 @@ + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; +- path_t path = PATH_T_INITIALIZE_P("readlink", "path", 0, 0, 0, 0); ++ path_t path = PATH_T_INITIALIZE_P("readlink", "path", 0, 0, 0, 1); + int dir_fd = DEFAULT_DIR_FD; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, +@@ -13760,4 +13763,4 @@ + #ifndef OS__EMSCRIPTEN_LOG_METHODDEF + #define OS__EMSCRIPTEN_LOG_METHODDEF + #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ +-/*[clinic end generated code: output=f1113bf7c270d271 input=a9049054013a1b77]*/ ++/*[clinic end generated code: output=8bf88a95035a0188 input=a9049054013a1b77]*/ +diff --git a/Modules/main.c b/Modules/main.c +index 097c10504d6..44a025d500e 100644 +--- a/Modules/main.c ++++ b/Modules/main.c +@@ -847,7 +847,6 @@ + _PyPathConfig_ClearGlobal(); + _Py_ClearArgcArgv(); + _PyRuntime_Finalize(); +- _Py_GetVersion_Fini(); + } + + +diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c +index 028270e7b84..301afe1fa27 100644 +--- a/Modules/posixmodule.c ++++ b/Modules/posixmodule.c +@@ -31,6 +31,7 @@ + #include "pycore_time.h" // _PyLong_FromTime_t() + #include "pycore_tuple.h" // _PyTuple_FromPairSteal + #include "pycore_typeobject.h" // _PyType_AddMethod() ++#include "pycore_warnings.h" // _PyWarnings_AfterFork() + + #ifndef MS_WINDOWS + # include "posixmodule.h" // _PyLong_FromUid() +@@ -505,6 +506,7 @@ + # define HAVE_UNLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) + # define HAVE_OPENAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) + # define HAVE_READLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) ++# define HAVE_FREADLINK_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, *) + # define HAVE_SYMLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) + # define HAVE_FUTIMENS_RUNTIME __builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + # define HAVE_UTIMENSAT_RUNTIME __builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) +@@ -512,8 +514,8 @@ + # define HAVE_MKFIFOAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) + # define HAVE_MKNODAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) + # define HAVE_PTSNAME_R_RUNTIME __builtin_available(macOS 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.3, *) +-# define HAVE_DUP3_RUNTIME __builtin_available(macOS 27.0, *) +-# define HAVE_PIPE2_RUNTIME __builtin_available(macOS 27.0, *) ++# define HAVE_DUP3_RUNTIME __builtin_available(macOS 27.0, iOS 27.0, tvOS 27.0, watchOS 27.0, visionOS 27.0, *) ++# define HAVE_PIPE2_RUNTIME __builtin_available(macOS 27.0, iOS 27.0, tvOS 27.0, watchOS 27.0, visionOS 27.0, *) + + # define HAVE_POSIX_SPAWN_SETSID_RUNTIME __builtin_available(macOS 10.15, *) + +@@ -571,6 +573,10 @@ + # define HAVE_READLINKAT_RUNTIME (readlinkat != NULL) + # endif + ++# ifdef _Py_HAVE_FREADLINK ++# define HAVE_FREADLINK_RUNTIME (freadlink != NULL) ++# endif ++ + # ifdef HAVE_SYMLINKAT + # define HAVE_SYMLINKAT_RUNTIME (symlinkat != NULL) + # endif +@@ -639,6 +645,7 @@ + # define HAVE_PTSNAME_R_RUNTIME 1 + # define HAVE_DUP3_RUNTIME 1 + # define HAVE_PIPE2_RUNTIME 1 ++# define HAVE_FREADLINK_RUNTIME 1 + #endif + + +@@ -716,6 +723,7 @@ + _PyImport_AcquireLock(interp); + _PyEval_StopTheWorldAll(&_PyRuntime); + HEAD_LOCK(&_PyRuntime); ++ _PyWarnings_BeforeFork(interp); + } + + void +@@ -786,6 +794,9 @@ + _Py_jit_debug_mutex = (PyMutex){0}; + #endif + ++ // Thread-state cleanup can run finalizers that issue warnings. ++ _PyWarnings_AfterFork(tstate->interp); ++ + reset_remotedebug_data(tstate); + + reset_asyncio_state((_PyThreadStateImpl *)tstate); +@@ -10979,11 +10990,19 @@ + #endif + + ++ + #if defined(HAVE_READLINK) || defined(MS_WINDOWS) ++ ++#if (defined(__linux__) || defined(__ANDROID__)) && defined(O_PATH) ++// readlinkat(fd, "", ...) reads the symlink that fd refers to. ++// supported since Linux 2.6.39 (same version that O_PATH was introduced). ++#define _Py_READLINKAT_SUPPORTS_EMPTY_PATH ++#endif ++ + /*[clinic input] + os.readlink + +- path: path_t ++ path: path_t(allow_fd=True) + * + dir_fd: dir_fd(requires='readlinkat') = None + +@@ -10995,48 +11014,74 @@ + + dir_fd may not be implemented on your platform. If it is unavailable, + using it will raise a NotImplementedError. ++ ++On Linux, Android, macOS and iOS, path may be a file descriptor ++referring to a symlink. If it is, dir_fd must be None. + [clinic start generated code]*/ + + static PyObject * + os_readlink_impl(PyObject *module, path_t *path, int dir_fd) +-/*[clinic end generated code: output=d21b732a2e814030 input=03d10130870dbca8]*/ ++/*[clinic end generated code: output=d21b732a2e814030 input=d90bddfeee86f673]*/ + { + #if defined(HAVE_READLINK) + char buffer[MAXPATHLEN+1]; + ssize_t length; +-#ifdef HAVE_READLINKAT +- int readlinkat_unavailable = 0; +-#endif + +- Py_BEGIN_ALLOW_THREADS ++ if (path_and_dir_fd_invalid("readlink", path, dir_fd)) { ++ return NULL; ++ } ++ ++ if (path->is_fd) { ++#if defined(_Py_HAVE_FREADLINK) ++ if (HAVE_FREADLINK_RUNTIME) { ++ Py_BEGIN_ALLOW_THREADS ++ length = freadlink(path->fd, buffer, MAXPATHLEN); ++ Py_END_ALLOW_THREADS ++ } else ++#elif defined(HAVE_READLINKAT) && defined(_Py_READLINKAT_SUPPORTS_EMPTY_PATH) ++ // linux/android: ++ // readlinkat(fd, "", ...) reads the link that fd refers to. ++ if (HAVE_READLINKAT_RUNTIME) { ++ Py_BEGIN_ALLOW_THREADS ++ length = readlinkat(path->fd, "", buffer, MAXPATHLEN); ++ Py_END_ALLOW_THREADS ++ } else ++#endif ++ { ++ PyErr_SetString(PyExc_NotImplementedError, ++ "readlink cannot read file descriptors on this platform"); ++ return NULL; ++ } ++ } else + #ifdef HAVE_READLINKAT + if (dir_fd != DEFAULT_DIR_FD) { + if (HAVE_READLINKAT_RUNTIME) { ++ Py_BEGIN_ALLOW_THREADS + length = readlinkat(dir_fd, path->narrow, buffer, MAXPATHLEN); ++ Py_END_ALLOW_THREADS + } else { +- readlinkat_unavailable = 1; ++ argument_unavailable_error(NULL, "dir_fd"); ++ return NULL; + } + } else + #endif ++ { ++ Py_BEGIN_ALLOW_THREADS + length = readlink(path->narrow, buffer, MAXPATHLEN); +- Py_END_ALLOW_THREADS +- +-#ifdef HAVE_READLINKAT +- if (readlinkat_unavailable) { +- argument_unavailable_error(NULL, "dir_fd"); +- return NULL; ++ Py_END_ALLOW_THREADS + } +-#endif + + if (length < 0) { + return path_error(path); + } + buffer[length] = '\0'; + +- if (PyUnicode_Check(path->object)) ++ if (path->is_fd || PyUnicode_Check(path->object)) { + return PyUnicode_DecodeFSDefaultAndSize(buffer, length); - else -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then - echo "Installing Python modules for iOS Device" - SLICE_FOLDER="ios-arm64" -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then -+ echo "Installing Python modules for tvOS Simulator" -+ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then -+ SLICE_FOLDER="tvos-arm64-simulator" -+ else -+ SLICE_FOLDER="tvos-arm64_x86_64-simulator" -+ fi -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then -+ echo "Installing Python modules for tvOS Device" -+ SLICE_FOLDER="tvos-arm64" -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then -+ echo "Installing Python modules for watchOS Simulator" -+ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then -+ SLICE_FOLDER="watchos-arm64-simulator" -+ else -+ SLICE_FOLDER="watchos-arm64_x86_64-simulator" -+ fi -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then -+ echo "Installing Python modules for watchOS Device" -+ SLICE_FOLDER="watchos-arm64" -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xrsimulator" ]; then -+ echo "Installing Python modules for visionOS Simulator" -+ SLICE_FOLDER="xros-arm64-simulator" -+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xros" ]; then -+ echo "Installing Python modules for visionOS Device" -+ SLICE_FOLDER="xros-arm64" -+ else -+ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME" -+ exit 1 - fi ++ } ++ else { + return PyBytes_FromStringAndSize(buffer, length); ++ } + #elif defined(MS_WINDOWS) + DWORD n_bytes_returned; + DWORD io_result = 0; +@@ -11045,6 +11090,12 @@ + _Py_REPARSE_DATA_BUFFER *rdb = (_Py_REPARSE_DATA_BUFFER *)target_buffer; + PyObject *result = NULL; - # If the XCframework has a shared lib folder, then it's a full framework. -diff --git a/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist ++ if (path->is_fd) { ++ PyErr_SetString(PyExc_NotImplementedError, ++ "readlink cannot read file descriptors on this platform"); ++ return NULL; ++ } ++ + /* First get a handle to the reparse point */ + Py_BEGIN_ALLOW_THREADS + reparse_point_handle = CreateFileW( +@@ -18878,6 +18929,10 @@ + PROBE(probe_readlinkat, HAVE_READLINKAT_RUNTIME) + #endif + ++#ifdef _Py_HAVE_FREADLINK ++PROBE(probe_freadlink, HAVE_FREADLINK_RUNTIME) ++#endif ++ + #ifdef HAVE_SYMLINKAT + PROBE(probe_symlinkat, HAVE_SYMLINKAT_RUNTIME) + #endif +@@ -18945,6 +19000,10 @@ + { "HAVE_FPATHCONF", NULL }, + #endif + ++#ifdef _Py_HAVE_FREADLINK ++ { "HAVE_FREADLINK", probe_freadlink }, ++#endif ++ + #ifdef HAVE_FSTATAT + { "HAVE_FSTATAT", probe_fstatat }, + #endif +diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c +index 5fec77a2b82..21a00a31201 100644 +--- a/Modules/socketmodule.c ++++ b/Modules/socketmodule.c +@@ -3420,11 +3420,6 @@ + + #ifdef AF_VSOCK + if (s->sock_family == AF_VSOCK) { +- if (!PyIndex_Check(optval)) { +- PyErr_Format(PyExc_TypeError, +- "setsockopt() argument 3 for AF_VSOCK must be an int (got %T)", +- optval); +- } + uint64_t vflag; // Must be set width of 64 bits + /* setsockopt(level, opt, flag) */ + if (!PyArg_Parse(optval, "K", &vflag)) { +diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c +index 1917055d251..bfd54176e5f 100644 +--- a/Objects/bytesobject.c ++++ b/Objects/bytesobject.c +@@ -652,6 +652,9 @@ + *plen = PyBytes_GET_SIZE(result); + return result; + } ++ else if (PyErr_Occurred()) { ++ return NULL; ++ } + /* does it support buffer protocol? */ + if (PyObject_CheckBuffer(v)) { + /* maybe we can avoid making a copy of the buffer object here? */ +diff --git a/Objects/dictobject.c b/Objects/dictobject.c +index 13c9e2e4b39..168a5a4118f 100644 +--- a/Objects/dictobject.c ++++ b/Objects/dictobject.c +@@ -2938,9 +2938,6 @@ + + PyObject *old_key; + +- Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); +- assert(hashpos >= 0); +- + STORE_USED(mp, mp->ma_used - 1); + if (_PyDict_HasSplitTable(mp)) { + assert(old_value == mp->ma_values->values[ix]); +@@ -2951,6 +2948,9 @@ + ASSERT_CONSISTENT(mp); + } + else { ++ Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); ++ assert(hashpos >= 0); ++ + FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0); + dictkeys_set_index(mp->ma_keys, hashpos, DKIX_DUMMY); + if (DK_IS_UNICODE(mp->ma_keys)) { +diff --git a/Objects/funcobject.c b/Objects/funcobject.c +index 4d3da234f87..f4adaf390e0 100644 +--- a/Objects/funcobject.c ++++ b/Objects/funcobject.c +@@ -41,7 +41,8 @@ + if (cb(event, func, new_value) < 0) { + PyErr_FormatUnraisable( + "Exception ignored in %s watcher callback for function %U at %p", +- func_event_name(event), func->func_qualname, func); ++ func_event_name(event), ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname), func); + } + } + i++; +@@ -405,7 +406,7 @@ + PyErr_BadInternalCall(); + return NULL; + } +- return ((PyFunctionObject *) op) -> func_module; ++ return PyFunction_GET_MODULE(op); + } + + PyObject * +@@ -535,9 +536,29 @@ + return 0; + } + ++static void ++func_set_annotations_and_annotate(PyFunctionObject *op, PyObject *annotations, ++ PyObject *annotate) ++{ ++ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); ++ // Care is needed, finalizers can run when references are dropped. Store ++ // both new values before dropping references to the old. ++ PyObject *old_annotations = op->func_annotations; ++ PyObject *old_annotate = op->func_annotate; ++ FT_ATOMIC_STORE_PTR_RELEASE(op->func_annotations, annotations); ++ FT_ATOMIC_STORE_PTR_RELEASE(op->func_annotate, annotate); ++ if (old_annotations != NULL && !_Py_IsImmortal(old_annotations)) { ++ _PyObject_XDecRefDelayed(old_annotations); ++ } ++ if (old_annotate != NULL && !_Py_IsImmortal(old_annotate)) { ++ _PyObject_XDecRefDelayed(old_annotate); ++ } ++} ++ + static PyObject * + func_get_annotation_dict(PyFunctionObject *op) + { ++ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); + if (op->func_annotations == NULL) { + if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate)) { + Py_RETURN_NONE; +@@ -554,7 +575,7 @@ + Py_DECREF(ann_dict); + return NULL; + } +- Py_XSETREF(op->func_annotations, ann_dict); ++ _PyObject_XSetRefDelayed(&op->func_annotations, ann_dict); + return ann_dict; + } + if (PyTuple_CheckExact(op->func_annotations)) { +@@ -576,7 +597,7 @@ + return NULL; + } + } +- Py_SETREF(op->func_annotations, ann_dict); ++ _PyObject_XSetRefDelayed(&op->func_annotations, ann_dict); + } + assert(PyDict_Check(op->func_annotations)); + return op->func_annotations; +@@ -589,7 +610,11 @@ + PyErr_BadInternalCall(); + return NULL; + } +- return func_get_annotation_dict((PyFunctionObject *)op); ++ PyObject *d; ++ Py_BEGIN_CRITICAL_SECTION(op); ++ d = func_get_annotation_dict((PyFunctionObject *)op); ++ Py_END_CRITICAL_SECTION(); ++ return d; + } + + int +@@ -610,15 +635,9 @@ + return -1; + } + PyFunctionObject *func = (PyFunctionObject *)op; +- PyInterpreterState *interp = _PyInterpreterState_GET(); +- _PyEval_StopTheWorld(interp); +- PyObject *old_annotations = func->func_annotations; +- func->func_annotations = annotations; +- PyObject *old_annotate = func->func_annotate; +- func->func_annotate = NULL; +- _PyEval_StartTheWorld(interp); +- Py_XDECREF(old_annotations); +- Py_XDECREF(old_annotate); ++ Py_BEGIN_CRITICAL_SECTION(func); ++ func_set_annotations_and_annotate(func, annotations, NULL); ++ Py_END_CRITICAL_SECTION(); + return 0; + } + +@@ -673,11 +692,11 @@ + Py_ssize_t nclosure = (op->func_closure == NULL ? 0 : + PyTuple_GET_SIZE(op->func_closure)); + if (nclosure != nfree) { ++ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_name); + PyErr_Format(PyExc_ValueError, + "%U() requires a code object with %zd free vars," + " not %d", +- op->func_name, +- nclosure, nfree); ++ name, nclosure, nfree); + return -1; + } + +@@ -709,7 +728,8 @@ + func_get_name(PyObject *self, void *Py_UNUSED(ignored)) + { + PyFunctionObject *op = _PyFunction_CAST(self); +- return Py_NewRef(op->func_name); ++ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_name); ++ return Py_NewRef(name); + } + + static int +@@ -723,12 +743,9 @@ + "__name__ must be set to a string object"); + return -1; + } +- PyInterpreterState *interp = _PyInterpreterState_GET(); +- _PyEval_StopTheWorld(interp); +- PyObject *old_name = op->func_name; +- op->func_name = Py_NewRef(value); +- _PyEval_StartTheWorld(interp); +- Py_XDECREF(old_name); ++ Py_BEGIN_CRITICAL_SECTION(op); ++ _PyObject_XSetRefDelayed(&op->func_name, Py_NewRef(value)); ++ Py_END_CRITICAL_SECTION(); + return 0; + } + +@@ -736,7 +753,8 @@ + func_get_qualname(PyObject *self, void *Py_UNUSED(ignored)) + { + PyFunctionObject *op = _PyFunction_CAST(self); +- return Py_NewRef(op->func_qualname); ++ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_qualname); ++ return Py_NewRef(qualname); + } + + static int +@@ -751,12 +769,9 @@ + return -1; + } + handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject *) op, value); +- PyInterpreterState *interp = _PyInterpreterState_GET(); +- _PyEval_StopTheWorld(interp); +- PyObject *old_qualname = op->func_qualname; +- op->func_qualname = Py_NewRef(value); +- _PyEval_StartTheWorld(interp); +- Py_XDECREF(old_qualname); ++ Py_BEGIN_CRITICAL_SECTION(op); ++ _PyObject_XSetRefDelayed(&op->func_qualname, Py_NewRef(value)); ++ Py_END_CRITICAL_SECTION(); + return 0; + } + +@@ -764,7 +779,7 @@ + func_get_doc(PyObject *self, void *Py_UNUSED(ignored)) + { + PyFunctionObject *op = _PyFunction_CAST(self); +- PyObject *doc = op->func_doc; ++ PyObject *doc = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_doc); + if (doc == NULL) { + doc = Py_None; + } +@@ -776,12 +791,9 @@ + { + /* Legal to del f.__doc__ or to set it to any object. */ + PyFunctionObject *op = _PyFunction_CAST(self); +- PyInterpreterState *interp = _PyInterpreterState_GET(); +- _PyEval_StopTheWorld(interp); +- PyObject *old_doc = op->func_doc; +- op->func_doc = Py_XNewRef(value); +- _PyEval_StartTheWorld(interp); +- Py_XDECREF(old_doc); ++ Py_BEGIN_CRITICAL_SECTION(op); ++ _PyObject_XSetRefDelayed(&op->func_doc, Py_XNewRef(value)); ++ Py_END_CRITICAL_SECTION(); + return 0; + } + +@@ -789,7 +801,7 @@ + func_get_module(PyObject *self, void *Py_UNUSED(ignored)) + { + PyFunctionObject *op = _PyFunction_CAST(self); +- PyObject *module = op->func_module; ++ PyObject *module = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_module); + if (module == NULL) { + module = Py_None; + } +@@ -801,12 +813,9 @@ + { + /* Legal to del f.__module__ or to set it to any object. */ + PyFunctionObject *op = _PyFunction_CAST(self); +- PyInterpreterState *interp = _PyInterpreterState_GET(); +- _PyEval_StopTheWorld(interp); +- PyObject *old_module = op->func_module; +- op->func_module = Py_XNewRef(value); +- _PyEval_StartTheWorld(interp); +- Py_XDECREF(old_module); ++ Py_BEGIN_CRITICAL_SECTION(op); ++ _PyObject_XSetRefDelayed(&op->func_module, Py_XNewRef(value)); ++ Py_END_CRITICAL_SECTION(); + return 0; + } + +@@ -934,12 +943,11 @@ + /*[clinic end generated code: output=05b7dfc07ada66cd input=eb6225e358d97448]*/ + { + if (Py_IsNone(value)) { +- Py_XSETREF(self->func_annotate, Py_NewRef(value)); ++ _PyObject_XSetRefDelayed(&self->func_annotate, Py_NewRef(value)); + return 0; + } + else if (PyCallable_Check(value)) { +- Py_XSETREF(self->func_annotate, Py_NewRef(value)); +- Py_CLEAR(self->func_annotations); ++ func_set_annotations_and_annotate(self, NULL, Py_NewRef(value)); + return 0; + } + else { +@@ -964,9 +972,10 @@ + PyObject *d = NULL; + if (self->func_annotations == NULL && + (self->func_annotate == NULL || !PyCallable_Check(self->func_annotate))) { +- self->func_annotations = PyDict_New(); +- if (self->func_annotations == NULL) ++ PyObject *ann_dict = PyDict_New(); ++ if (ann_dict == NULL) + return NULL; ++ FT_ATOMIC_STORE_PTR_RELEASE(self->func_annotations, ann_dict); + } + d = func_get_annotation_dict(self); + return Py_XNewRef(d); +@@ -993,8 +1002,7 @@ + "__annotations__ must be set to a dict object"); + return -1; + } +- Py_XSETREF(self->func_annotations, Py_XNewRef(value)); +- Py_CLEAR(self->func_annotate); ++ func_set_annotations_and_annotate(self, Py_XNewRef(value), NULL); + return 0; + } + +@@ -1224,8 +1232,8 @@ + func_repr(PyObject *self) + { + PyFunctionObject *op = _PyFunction_CAST(self); +- return PyUnicode_FromFormat("", +- op->func_qualname, op); ++ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_qualname); ++ return PyUnicode_FromFormat("", qualname, op); + } + + static int +diff --git a/Objects/genobject.c b/Objects/genobject.c +index c313002c723..05a331e88b8 100644 +--- a/Objects/genobject.c ++++ b/Objects/genobject.c +@@ -1117,10 +1117,12 @@ + gen->gi_exc_state.exc_value = NULL; + gen->gi_exc_state.previous_item = NULL; + gen->gi_iframe.f_executable = PyStackRef_None; +- assert(func->func_name != NULL); +- gen->gi_name = Py_NewRef(func->func_name); +- assert(func->func_qualname != NULL); +- gen->gi_qualname = Py_NewRef(func->func_qualname); ++ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name); ++ assert(name != NULL); ++ gen->gi_name = Py_NewRef(name); ++ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname); ++ assert(qualname != NULL); ++ gen->gi_qualname = Py_NewRef(qualname); + _PyObject_GC_TRACK(gen); + return (PyObject *)gen; + } +diff --git a/Objects/lazyimportobject.c b/Objects/lazyimportobject.c +index bb35ddcdae1..aafd2ce764a 100644 +--- a/Objects/lazyimportobject.c ++++ b/Objects/lazyimportobject.c +@@ -204,7 +204,11 @@ + lazy_import_path(PyLazyImportObject *m) + { + if (PyLazyImport_CheckExact(m->lz_from)) { ++ if (_Py_EnterRecursiveCall(" while getting the name of a lazy import")) { ++ return NULL; ++ } + PyObject *base = lazy_import_path((PyLazyImportObject *)m->lz_from); ++ _Py_LeaveRecursiveCall(); + if (base == NULL) { + return NULL; + } +@@ -277,8 +281,12 @@ + if (!PyLazyImport_CheckExact(lz->lz_from)) { + return Py_NewRef(mod); + } ++ if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) { ++ return NULL; ++ } + PyObject *from = lazy_import_replay_from( + tstate, mod, (PyLazyImportObject *)lz->lz_from); ++ _Py_LeaveRecursiveCallTstate(tstate); + if (from == NULL) { + return NULL; + } +diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c +index 841fb796e1c..096b8b08aa7 100644 +--- a/Objects/obmalloc.c ++++ b/Objects/obmalloc.c +@@ -1384,6 +1384,10 @@ + return; + } + assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); ++ if (_Py_IsImmortal(obj)) { ++ // The object may have become immortal after it was queued. ++ return; ++ } + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1); + if (refcount == 0) { + cb(obj, state); +@@ -1476,9 +1480,11 @@ + _PyEval_StopTheWorld(tstate->base.interp); + if (work_item_should_decref(ptr)) { + PyObject *obj = (PyObject *)(ptr - 1); +- Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1); +- if (refcount == 0) { +- to_dealloc = obj; ++ if (!_Py_IsImmortal(obj)) { ++ Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1); ++ if (refcount == 0) { ++ to_dealloc = obj; ++ } + } + } + else { +@@ -1526,7 +1532,7 @@ + _PyObject_XDecRefDelayed(PyObject *ptr) + { + assert(!((uintptr_t)ptr & 0x01)); +- if (ptr != NULL) { ++ if (ptr != NULL && !_Py_IsImmortal(ptr)) { + // We use 0 as the size since we don't have an easy way to know the + // actual size. If we are freeing many objects, the write sequence + // will be advanced due to QSBR_DEFERRED_LIMIT. +@@ -1541,12 +1547,7 @@ + { + PyObject *old = *ptr; + FT_ATOMIC_STORE_PTR_RELEASE(*ptr, value); +- if (old == NULL) { +- return; +- } +- if (!_Py_IsImmortal(old)) { +- _PyObject_XDecRefDelayed(old); +- } ++ _PyObject_XDecRefDelayed(old); + } + #endif + +diff --git a/Objects/typeobject.c b/Objects/typeobject.c +index 15c3367013e..5e772e5608d 100644 +--- a/Objects/typeobject.c ++++ b/Objects/typeobject.c +@@ -6738,9 +6738,7 @@ + Py_DECREF(name); + Py_XDECREF(descr); + // delay decref of the old value as lock-free type cache readers may access it +- if (old_value != NULL && !_Py_IsImmortal(old_value)) { +- _PyObject_XDecRefDelayed(old_value); +- } ++ _PyObject_XDecRefDelayed(old_value); + return res; + } + +diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c +index daeba5368b2..db95bb926d5 100644 +--- a/Objects/weakrefobject.c ++++ b/Objects/weakrefobject.c +@@ -221,6 +221,10 @@ + } + + PyObject *name = _PyObject_LookupSpecial(obj, &_Py_ID(__name__)); ++ if (name == NULL && PyErr_Occurred()) { ++ Py_DECREF(obj); ++ return NULL; ++ } + PyObject *repr; + if (name == NULL || !PyUnicode_Check(name)) { + repr = PyUnicode_FromFormat( +diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c +index 2d51795d887..2f8a5d84dd9 100644 +--- a/Parser/action_helpers.c ++++ b/Parser/action_helpers.c +@@ -1497,11 +1497,11 @@ + } + + expr_ty _PyPegen_decoded_constant_from_token(Parser* p, Token* tok) { +- Py_ssize_t bsize; +- char* bstr; +- if (PyBytes_AsStringAndSize(tok->bytes, &bstr, &bsize) == -1) { +- return NULL; ++ if (tok->bytes == NULL) { ++ return _PyPegen_raise_incomplete_input(p); + } ++ Py_ssize_t bsize = PyBytes_GET_SIZE(tok->bytes); ++ const char *bstr = PyBytes_AS_STRING(tok->bytes); + + int is_raw = tok->is_raw; + +@@ -1519,10 +1519,10 @@ + } + + expr_ty _PyPegen_constant_from_token(Parser* p, Token* tok) { +- const char* bstr = PyBytes_AsString(tok->bytes); +- if (bstr == NULL) { +- return NULL; ++ if (tok->bytes == NULL) { ++ return _PyPegen_raise_incomplete_input(p); + } ++ const char *bstr = PyBytes_AS_STRING(tok->bytes); + PyObject* str = PyUnicode_FromString(bstr); + if (str == NULL) { + return NULL; +@@ -1537,10 +1537,10 @@ + } + + expr_ty _PyPegen_constant_from_string(Parser* p, Token* tok) { +- const char* the_str = PyBytes_AsString(tok->bytes); +- if (the_str == NULL) { +- return NULL; ++ if (tok->bytes == NULL) { ++ return _PyPegen_raise_incomplete_input(p); + } ++ const char *the_str = PyBytes_AS_STRING(tok->bytes); + PyObject *s = _PyPegen_parse_string(p, tok); + if (s == NULL) { + _Pypegen_raise_decode_error(p); +diff --git a/Parser/lexer/layout.c b/Parser/lexer/layout.c +index 6091f9adeac..f503f36f76f 100644 +--- a/Parser/lexer/layout.c ++++ b/Parser/lexer/layout.c +@@ -21,6 +21,9 @@ + return -1; + } + c = tok_nextc(tok); ++ if (tok->implicit_newline == _PyTokenizer_APPEND_NEWLINE) { ++ tok->needs_newline |= _PyTokenizer_NEWLINE_CONTINUATION; ++ } + if (c == EOF) { + tok->done = E_EOF; + tok->cur = tok->inp; +@@ -114,6 +117,11 @@ + } + } + tok_backup(tok, c); ++ if (c == '\n' && tok->implicit_newline == _PyTokenizer_IMPLY_NEWLINE && ++ tok->level == 0 && (cont_line_col || col)) { ++ // Without the newline, a whitespace-only line affects indentation. ++ tok->needs_newline |= _PyTokenizer_NEWLINE_INDENT; ++ } + if (c == '#' || c == '\n' || c == '\r') { + int interactive = _PyTok_ReaderIsInteractive(tok); + /* Lines with only whitespace and/or comments +@@ -171,6 +179,10 @@ + int + _PyLexer_Newline(struct tok_state *tok, struct token *token, int blankline) + { ++ if (!blankline && tok->implicit_newline == _PyTokenizer_IMPLY_NEWLINE) { ++ // The statement's terminating newline was not in the input. ++ tok->needs_newline |= _PyTokenizer_NEWLINE_STATEMENT; ++ } + tok->layout.at_bol = 1; + if (blankline || tok->level > 0) { + if (!tok->tok_extra_tokens) { +diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c +index 83071e9e0b8..39b39b981c1 100644 +--- a/Parser/lexer/lexer.c ++++ b/Parser/lexer/lexer.c +@@ -235,6 +235,11 @@ + return type; + } + } ++ if (tok->implicit_newline == _PyTokenizer_IMPLY_NEWLINE && ++ tok->layout.depth > 0) { ++ // Ending this comment lets the next line dedent the suite. ++ tok->needs_newline |= _PyTokenizer_NEWLINE_STATEMENT; ++ } + if (tok->tok_extra_tokens) { + tok_backup(tok, c); /* don't eat the newline or EOF */ + p_start = comment; +@@ -516,7 +521,7 @@ + Py_UNREACHABLE(); + } + } +- if (tok_failed(tok)) { ++ if (tok_failed(tok) && tok->done != E_INCOMPLETE) { + result = ERRORTOKEN; + } + token->type = result; +diff --git a/Parser/lexer/state.h b/Parser/lexer/state.h +index 51ad278b13c..9c1475356b0 100644 +--- a/Parser/lexer/state.h ++++ b/Parser/lexer/state.h +@@ -105,6 +105,8 @@ + int debug; + #endif + int barry_as_bdfl; ++ unsigned char allow_incomplete_input; ++ unsigned char needs_newline; + }; + + static inline ftstring_state * +diff --git a/Parser/lexer/string.c b/Parser/lexer/string.c +index d0f568dfa85..c2bef9773ea 100644 +--- a/Parser/lexer/string.c ++++ b/Parser/lexer/string.c +@@ -21,6 +21,15 @@ + return type; + } + ++static int ++incomplete_string_token(struct tok_state *tok, struct token *token, ++ int type, _PyTok_Off start, _PyTok_Loc location) ++{ ++ string_error_token(tok, token, start, location); ++ tok->done = E_INCOMPLETE; ++ return type; ++} ++ + int + _PyLexer_record_ftstring_comment(struct tok_state *tok, ftstring_state *state, + _PyTok_Off start, _PyTok_Off end) +@@ -337,6 +346,10 @@ + if (tok_failed(tok)) { + return MAKE_TOKEN(ERRORTOKEN); + } ++ if (c == EOF && tok->allow_incomplete_input) { ++ return incomplete_string_token(tok, token, STRING, ++ tok->start, tok->start_loc); ++ } + int end_lineno = tok->lineno; + _PyTok_Loc location = tok->start_loc; + const char *line = _PyTok_SourcePointer( +@@ -450,6 +463,10 @@ + if (tok_failed(tok)) { + return MAKE_TOKEN(ERRORTOKEN); + } ++ if (c == EOF && tok->allow_incomplete_input) { ++ return incomplete_string_token(tok, token, ++ FTSTRING_MIDDLE(current), current->start, current->start_loc); ++ } + + if (in_format_spec && c == '\n') { + return MAKE_TOKEN(_PyTokenizer_syntaxerror( +diff --git a/Parser/peg_api.c b/Parser/peg_api.c +index e30ca0453bd..6791478ba68 100644 +--- a/Parser/peg_api.c ++++ b/Parser/peg_api.c +@@ -5,14 +5,14 @@ + mod_ty + _PyParser_ASTFromString(const char *str, PyObject* filename, int mode, + PyCompilerFlags *flags, PyArena *arena, +- PyObject *module) ++ PyObject *module, PyObject **incomplete) + { + if (PySys_Audit("compile", "yO", str, filename) < 0) { + return NULL; + } + + mod_ty result = _PyPegen_run_parser_from_string(str, mode, filename, flags, +- arena, module); ++ arena, module, incomplete); + return result; + } + +@@ -39,4 +39,4 @@ + } + return _PyPegen_run_parser_from_file_pointer(fp, mode, filename_ob, enc, ps1, ps2, + flags, errcode, interactive_src, arena); +-} +\ No newline at end of file ++} +diff --git a/Parser/pegen.c b/Parser/pegen.c +index 4ca42a407bc..bb14a1e78c5 100644 +--- a/Parser/pegen.c ++++ b/Parser/pegen.c +@@ -247,23 +247,26 @@ + initialize_token(Parser *p, Token *parser_token, struct token *new_token, int token_type) { + assert(parser_token != NULL); + +- Py_ssize_t length; +- const char *text = _PyToken_TextView(p->tok, new_token, &length); +- parser_token->type = token_type == NAME +- ? _get_keyword_or_name_type(p, text, length) : token_type; +- if (token_needs_text(parser_token->type)) { +- parser_token->bytes = PyBytes_FromStringAndSize(text, length); +- if (parser_token->bytes == NULL) { +- return -1; ++ parser_token->type = token_type; ++ parser_token->bytes = NULL; ++ if (token_needs_text(token_type) && new_token->span.start >= 0) { ++ Py_ssize_t length; ++ const char *text = _PyToken_TextView(p->tok, new_token, &length); ++ if (token_type == NAME) { ++ parser_token->type = _get_keyword_or_name_type(p, text, length); + } +- if (_PyArena_AddPyObject(p->arena, parser_token->bytes) < 0) { +- Py_DECREF(parser_token->bytes); +- return -1; ++ // A name reclassified as a keyword does not need its text. ++ if (parser_token->type == token_type) { ++ parser_token->bytes = PyBytes_FromStringAndSize(text, length); ++ if (parser_token->bytes == NULL) { ++ return -1; ++ } ++ if (_PyArena_AddPyObject(p->arena, parser_token->bytes) < 0) { ++ Py_DECREF(parser_token->bytes); ++ return -1; ++ } + } + } +- else { +- parser_token->bytes = NULL; +- } + + parser_token->metadata = NULL; + if (new_token->metadata != NULL) { +@@ -287,7 +290,7 @@ + + p->fill += 1; + +- if (token_type == ERRORTOKEN && _PyTokenizer_GetInfo(p->tok).status == E_DECODE) { ++ if (token_type == ERRORTOKEN && _PyTokenizer_GetStatus(p->tok) == E_DECODE) { + return _Pypegen_raise_decode_error(p); + } + +@@ -352,6 +355,13 @@ + } + + int type = new_token.type; ++ // EOF can finish an ordinary token. Only a boundary token means that ++ // the grammar requested input beyond the last physical line. ++ if (type == ENDMARKER || ++ (type == DEDENT && (p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && ++ _PyTokenizer_GetStatus(p->tok) == E_EOF)) { ++ p->input_exhausted = 1; ++ } + + // If we have reached the end and we are in single input mode we need to insert a newline and reset the parsing + if (p->start_rule == Py_single_input && type == ENDMARKER && p->parsing_started) { +@@ -953,6 +963,7 @@ + } + assert(tok != NULL); + _PyTokenizer_SetOptions(tok, 0, (flags & PyPARSE_TYPE_COMMENTS) > 0); ++ _PyTokenizer_AllowIncompleteInput(tok, flags & PyPARSE_ALLOW_INCOMPLETE_INPUT); + p->tok = tok; + p->keywords = NULL; + p->n_keyword_lists = -1; +@@ -998,6 +1009,7 @@ + #endif + p->level = 0; + p->call_invalid_rules = 0; ++ p->input_exhausted = 0; + p->last_stmt_location.lineno = 0; + p->last_stmt_location.col_offset = 0; + p->last_stmt_location.end_lineno = 0; +@@ -1035,16 +1047,12 @@ + p->tokens[i]->memo_mask = 0; + } + p->mark = 0; ++ p->flags &= ~PyPARSE_ALLOW_INCOMPLETE_INPUT; ++ _PyTokenizer_AllowIncompleteInput(p->tok, 0); + p->call_invalid_rules = 1; + _PyTokenizer_StopInteractive(p->tok); + } + +-static inline int +-_is_end_of_source(Parser *p) { +- int err = _PyTokenizer_GetInfo(p->tok).status; +- return err == E_EOF || err == E_EOFS || err == E_EOLS; +-} +- + static void + _PyPegen_set_syntax_error_metadata(Parser *p) { + PyObject *exc = PyErr_GetRaisedException(); +@@ -1095,13 +1103,15 @@ + return NULL; + } + if (res == NULL) { +- if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && _is_end_of_source(p)) { +- PyErr_Clear(); +- return _PyPegen_raise_error(p, PyExc_IncompleteInputError, 0, "incomplete input"); +- } +- if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_SyntaxError)) { ++ if (PyErr_Occurred() && ++ (PyErr_ExceptionMatches(PyExc_IncompleteInputError) || ++ !PyErr_ExceptionMatches(PyExc_SyntaxError))) { + return NULL; + } ++ if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && ++ p->input_exhausted && !PyErr_Occurred()) { ++ return _PyPegen_raise_incomplete_input(p); ++ } + // Make a second parser pass. In this pass we activate heavier and slower checks + // to produce better error messages and more complete diagnostics. Extra "invalid_*" + // rules will be active during parsing. +@@ -1194,16 +1204,21 @@ + + mod_ty + _PyPegen_run_parser_from_string(const char *str, int start_rule, PyObject *filename_ob, +- PyCompilerFlags *flags, PyArena *arena, PyObject *module) ++ PyCompilerFlags *flags, PyArena *arena, PyObject *module, ++ PyObject **incomplete) + { +- int exec_input = start_rule == Py_file_input; ++ _PyTokenizer_Newline newline_mode = start_rule == Py_file_input ++ ? _PyTokenizer_IMPLY_NEWLINE : _PyTokenizer_KEEP_NEWLINE; + int parser_flags = compute_parser_flags(flags); ++ if (parser_flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) { ++ newline_mode = _PyTokenizer_APPEND_NEWLINE; ++ } + + struct tok_state *tok; + if (flags != NULL && flags->cf_flags & PyCF_IGNORE_COOKIE) { +- tok = _PyTokenizer_FromUTF8(str, exec_input, 0); ++ tok = _PyTokenizer_FromUTF8(str, newline_mode, 0); + } else { +- tok = _PyTokenizer_FromString(str, exec_input, 0); ++ tok = _PyTokenizer_FromString(str, newline_mode, 0); + } + if (tok == NULL) { + if (PyErr_Occurred()) { +@@ -1231,6 +1246,22 @@ + } + + result = _PyPegen_run_parser(p); ++ if (result != NULL && (parser_flags & PyPARSE_ALLOW_INCOMPLETE_INPUT)) { ++ int needs_newline = _PyTokenizer_GetNewlineRequirements(tok); ++ if ((needs_newline & _PyTokenizer_NEWLINE_CONTINUATION) || ++ (start_rule != Py_file_input && ++ (needs_newline & _PyTokenizer_NEWLINE_INDENT)) || ++ (start_rule == Py_single_input && p->input_exhausted && ++ (needs_newline & _PyTokenizer_NEWLINE_STATEMENT))) { ++ _PyPegen_raise_incomplete_input(p); ++ if (incomplete != NULL && PyErr_ExceptionMatches(PyExc_IncompleteInputError)) { ++ *incomplete = PyErr_GetRaisedException(); ++ } ++ else { ++ result = NULL; ++ } ++ } ++ } + _PyPegen_Parser_Free(p); + + error: +diff --git a/Parser/pegen.h b/Parser/pegen.h +index 7454289ef6d..44f99310132 100644 +--- a/Parser/pegen.h ++++ b/Parser/pegen.h +@@ -39,6 +39,8 @@ + typedef struct { + int type; + // Text required by grammar actions or helpers; otherwise NULL. ++ // An unfinished string token also has NULL text. Its grammar action ++ // requests more input only if the token is valid at that position. + PyObject *bytes; + int level; + int is_raw; +@@ -86,7 +88,8 @@ + int n_keyword_lists; + int start_rule; + int *errcode; +- int parsing_started; ++ unsigned char parsing_started; ++ unsigned char input_exhausted; + PyObject* normalize; + int starting_lineno; + int starting_col_offset; +@@ -210,6 +213,7 @@ + // Error handling functions and APIs + int _Pypegen_raise_decode_error(Parser *p); + int _Pypegen_tokenizer_error(Parser *p); ++void *_PyPegen_raise_incomplete_input(Parser *p); + void *_PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...); + void *_PyPegen_raise_error_known_location(Parser *p, PyObject *errtype, + Py_ssize_t lineno, Py_ssize_t col_offset, +@@ -422,7 +426,7 @@ + const char *, const char *, PyCompilerFlags *, int *, PyObject **, + PyArena *); + void *_PyPegen_run_parser(Parser *); +-mod_ty _PyPegen_run_parser_from_string(const char *, int, PyObject *, PyCompilerFlags *, PyArena *, PyObject *); ++mod_ty _PyPegen_run_parser_from_string(const char *, int, PyObject *, PyCompilerFlags *, PyArena *, PyObject *, PyObject **); + asdl_stmt_seq *_PyPegen_interactive_exit(Parser *); + + // Generated function in parse.c - function definition in python.gram +diff --git a/Parser/pegen_errors.c b/Parser/pegen_errors.c +index 6997a1158e9..007397fa035 100644 +--- a/Parser/pegen_errors.c ++++ b/Parser/pegen_errors.c +@@ -9,6 +9,16 @@ + + // TOKENIZER ERRORS + ++void * ++_PyPegen_raise_incomplete_input(Parser *p) ++{ ++ if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && !PyErr_Occurred()) { ++ return _PyPegen_raise_error(p, PyExc_IncompleteInputError, 0, ++ "incomplete input"); ++ } ++ return NULL; ++} ++ + static inline void + raise_unclosed_parentheses_error(Parser *p) { + _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); +@@ -32,7 +42,17 @@ + Py_ssize_t col_offset = -1; + p->error_indicator = 1; + _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); ++ if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && ++ (info.status == E_EOF || info.status == E_INCOMPLETE)) ++ { ++ _PyPegen_raise_incomplete_input(p); ++ return -1; ++ } + switch (info.status) { ++ case E_INCOMPLETE: ++ // The diagnostic pass rejected this unfinished token. Let the ++ // parser report the syntax error at its original failure point. ++ return -1; + case E_TOKEN: + msg = "invalid token"; + break; +@@ -298,6 +318,12 @@ + Py_ssize_t size; + const char *line = _PyTokenizer_SpanView( + p->tok, text_span, &size); ++ if (errtype == PyExc_IncompleteInputError && size > 0 && ++ (info.implicit_newline == _PyTokenizer_APPEND_NEWLINE || ++ (info.implicit_newline == _PyTokenizer_IMPLY_NEWLINE && ++ p->start_rule != Py_file_input))) { ++ size--; ++ } + error_line = PyUnicode_DecodeUTF8(line, size, "replace"); + } + else if (!info.is_file) { +diff --git a/Parser/tokenizer/api.c b/Parser/tokenizer/api.c +index d955c6b335e..99f0e4e0ddc 100644 +--- a/Parser/tokenizer/api.c ++++ b/Parser/tokenizer/api.c +@@ -7,11 +7,24 @@ + #include "reader_internal.h" + #include "../lexer/state.h" + ++int ++_PyTokenizer_GetStatus(const struct tok_state *tok) ++{ ++ return tok->done; ++} ++ ++int ++_PyTokenizer_GetNewlineRequirements(const struct tok_state *tok) ++{ ++ return tok->needs_newline; ++} ++ + _PyTokenizer_Info + _PyTokenizer_GetInfo(const struct tok_state *tok) + { + _PyTokenizer_Info info = { + .status = tok->done, ++ .implicit_newline = tok->implicit_newline, + .diagnostic = tok->diagnostic, + .location = {tok->lineno, tok->line_start < 0 + ? -1 : (int)(tok->cur - tok->line_start)}, +@@ -103,6 +116,12 @@ + _PyLexer_ImplyDedents(tok); + } + ++void ++_PyTokenizer_AllowIncompleteInput(struct tok_state *tok, int allow) ++{ ++ tok->allow_incomplete_input = allow != 0; ++} ++ + int + _PyTokenizer_HasTrailingStatement(const struct tok_state *tok) + { +diff --git a/Parser/tokenizer/decoder.c b/Parser/tokenizer/decoder.c +index 48f90684565..6388b71e635 100644 +--- a/Parser/tokenizer/decoder.c ++++ b/Parser/tokenizer/decoder.c +@@ -14,7 +14,6 @@ + #define UTF16_BOM_LENGTH ((Py_ssize_t)(sizeof(UTF16_LE_BOM) - 1)) + #define UTF8_BOM "\xef\xbb\xbf" + #define UTF8_BOM_LENGTH ((Py_ssize_t)(sizeof(UTF8_BOM) - 1)) +-#define NORMALIZED_LINE_INITIAL_CAPACITY 256 + + char * + _PyTok_CopyBytes(const char *data, Py_ssize_t len) +@@ -284,9 +283,6 @@ + int preserve_crlf, int add_final_newline) + { + Py_ssize_t pos = 0; +- // SourceAppendLine copies the bytes, so reuse this buffer for each line. +- char *normalized = NULL; +- Py_ssize_t capacity = 0; + while (pos < len) { + Py_ssize_t raw_line_len; + if (preserve_crlf) { +@@ -304,52 +300,45 @@ + int add_newline = add_final_newline && + pos + raw_line_len == len && !terminated; + const char *line = data + pos; +- Py_ssize_t line_len = raw_line_len; + // raw_line_length stops at the first CR or LF, so any CR is in + // the line terminator. + Py_ssize_t terminator_len = 0; + if (!preserve_crlf) { +- if (line[line_len - 1] == '\r') { ++ if (line[raw_line_len - 1] == '\r') { + terminator_len = 1; + } +- else if (line_len > 1 && line[line_len - 2] == '\r') { ++ else if (raw_line_len > 1 && line[raw_line_len - 2] == '\r') { + terminator_len = 2; + } + } ++ _PyTok_Off appended; + if (terminator_len || add_newline) { +- Py_ssize_t payload_len = line_len - terminator_len; +- if (payload_len > PY_SSIZE_T_MAX - 2) { +- PyErr_NoMemory(); +- tok->done = E_NOMEM; +- goto error; +- } +- Py_ssize_t needed = payload_len + 2; +- if (_PyTok_ReserveBuffer(&normalized, &capacity, needed, +- NORMALIZED_LINE_INITIAL_CAPACITY) < 0) { +- tok->done = E_NOMEM; +- goto error; +- } +- memcpy(normalized, line, payload_len); +- normalized[payload_len] = '\n'; +- line_len = payload_len + 1; +- normalized[line_len] = '\0'; +- line = normalized; ++ appended = _PyTok_SourceAppendLineWithNewline( ++ &tok->source, line, raw_line_len - terminator_len); ++ } ++ else { ++ appended = _PyTok_SourceAppendLine(&tok->source, line, raw_line_len); + } +- _PyTok_Off appended = _PyTok_SourceAppendLine( +- &tok->source, line, line_len); + if (appended < 0) { + tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) + ? E_NOMEM : E_ERROR; +- goto error; ++ return -1; + } +- tok->reader->prepared_final_newline_is_implicit = add_newline; ++ tok->reader->prepared_final_newline = add_newline; + pos += raw_line_len; + } +- PyMem_Free(normalized); ++ // Partial input includes the newline submitted by the next Enter key. ++ // A final CR pairs with that newline instead of starting a blank line. ++ if (add_final_newline == _PyTokenizer_APPEND_NEWLINE && ++ !tok->reader->prepared_final_newline && ++ (len == 0 || data[len - 1] != '\r')) ++ { ++ if (_PyTok_SourceAppendLine(&tok->source, "\n", 1) < 0) { ++ return -1; ++ } ++ tok->reader->prepared_final_newline = _PyTokenizer_APPEND_NEWLINE; ++ } + return 0; +-error: +- PyMem_Free(normalized); +- return -1; + } + + int +diff --git a/Parser/tokenizer/reader.c b/Parser/tokenizer/reader.c +index ce2bb1cf5c4..86cec96bdcc 100644 +--- a/Parser/tokenizer/reader.c ++++ b/Parser/tokenizer/reader.c +@@ -180,8 +180,8 @@ + chunk->data = (char *)_PyTok_SourceSpanView( + &tok->source, line, &chunk->len); + chunk->ownership = _PYTOK_CHUNK_BORROWED; +- chunk->implicit_newline = line.end == tail.end && +- tok->reader->prepared_final_newline_is_implicit; ++ chunk->implicit_newline = line.end == tail.end ++ ? tok->reader->prepared_final_newline : 0; + return _PYTOK_READ_LINE; + } + +diff --git a/Parser/tokenizer/reader_internal.h b/Parser/tokenizer/reader_internal.h +index a7a68269fff..c080af3c05e 100644 +--- a/Parser/tokenizer/reader_internal.h ++++ b/Parser/tokenizer/reader_internal.h +@@ -58,7 +58,7 @@ + Py_ssize_t decoded_len; + Py_ssize_t decoded_cap; + _PyTok_ReaderKind kind; +- unsigned char prepared_final_newline_is_implicit; ++ unsigned char prepared_final_newline; // _PyTokenizer_Newline + unsigned char decoded_tail_is_implicit; + unsigned char file_initialized; + unsigned char file_eof; +diff --git a/Parser/tokenizer/source.c b/Parser/tokenizer/source.c +index ebb7b05507c..8688ef0f06e 100644 +--- a/Parser/tokenizer/source.c ++++ b/Parser/tokenizer/source.c +@@ -71,51 +71,68 @@ + + static int + validate_line(const _PyTok_SourceText *source, const char *bytes, +- Py_ssize_t len) ++ Py_ssize_t len, int append_newline) + { +- if (len <= 0 || bytes == NULL || ++ if (len < 0 || (len == 0 && !append_newline) || bytes == NULL || + (source->nlines > 0 && + (source->len == 0 || source->bytes[source->len - 1] != '\n'))) { + PyErr_SetString(PyExc_SystemError, "invalid tokenizer source line"); + return -1; + } + const char *newline = memchr(bytes, '\n', len); +- if (newline != NULL && newline != bytes + len - 1) { ++ if (newline != NULL && (append_newline || newline != bytes + len - 1)) { + PyErr_SetString(PyExc_SystemError, "invalid tokenizer source line"); + return -1; + } + if (source->nlines == INT_MAX || +- (source->nlines == INT_MAX - 1 && newline != NULL)) { ++ (source->nlines == INT_MAX - 1 && (newline != NULL || append_newline))) { + PyErr_SetString(PyExc_OverflowError, "too many tokenizer source lines"); + return -1; + } + return 0; + } + +-_PyTok_Off +-_PyTok_SourceAppendLine(_PyTok_SourceText *source, const char *bytes, +- Py_ssize_t len) ++static _PyTok_Off ++append_line(_PyTok_SourceText *source, const char *bytes, ++ Py_ssize_t len, int append_newline) + { +- if (validate_line(source, bytes, len) < 0) { ++ if (validate_line(source, bytes, len, append_newline) < 0) { + return -1; + } +- if (source->len > PY_SSIZE_T_MAX - len - 1 || +- source->base_offset > PY_SSIZE_T_MAX - source->len - len) { ++ if (source->len > PY_SSIZE_T_MAX - len - append_newline - 1 || ++ source->base_offset > PY_SSIZE_T_MAX - source->len - len - append_newline) { + PyErr_NoMemory(); + return -1; + } +- if (reserve_bytes(source, source->len + len + 1) < 0) { ++ if (reserve_bytes(source, source->len + len + append_newline + 1) < 0) { + return -1; + } + + _PyTok_Off start = source->len; + memcpy(source->bytes + start, bytes, len); + source->len += len; ++ if (append_newline) { ++ source->bytes[source->len++] = '\n'; ++ } + source->bytes[source->len] = '\0'; + source->nlines++; + return source->base_offset + start; + } + ++_PyTok_Off ++_PyTok_SourceAppendLine(_PyTok_SourceText *source, const char *bytes, ++ Py_ssize_t len) ++{ ++ return append_line(source, bytes, len, 0); ++} ++ ++_PyTok_Off ++_PyTok_SourceAppendLineWithNewline(_PyTok_SourceText *source, const char *bytes, ++ Py_ssize_t len) ++{ ++ return append_line(source, bytes, len, 1); ++} ++ + const char * + _PyTok_SourceLineView(const _PyTok_SourceText *source, Py_ssize_t lineno, + Py_ssize_t *len) +diff --git a/Parser/tokenizer/source.h b/Parser/tokenizer/source.h +index 0fd9fe1877b..d2c20283047 100644 +--- a/Parser/tokenizer/source.h ++++ b/Parser/tokenizer/source.h +@@ -70,6 +70,10 @@ + final line. The input must not point into source storage. */ + PyAPI_FUNC(_PyTok_Off) _PyTok_SourceAppendLine( + _PyTok_SourceText *source, const char *bytes, Py_ssize_t len); ++/* Append a line payload followed by '\n'. The payload may be empty and must ++ contain no newline. Other SourceAppendLine constraints still apply. */ ++PyAPI_FUNC(_PyTok_Off) _PyTok_SourceAppendLineWithNewline( ++ _PyTok_SourceText *source, const char *bytes, Py_ssize_t len); + /* Return borrowed bytes excluding '\n', writing the byte length to *len. + Line numbers are 1-based and clamp to the first or final line; a trailing + '\n' adds an empty final line. The view need not be NUL-terminated. +diff --git a/Parser/tokenizer/tokenizer.h b/Parser/tokenizer/tokenizer.h +index bc4009700fd..149bca06b8b 100644 +--- a/Parser/tokenizer/tokenizer.h ++++ b/Parser/tokenizer/tokenizer.h +@@ -6,6 +6,19 @@ + + struct tok_state; + ++typedef enum { ++ _PyTokenizer_KEEP_NEWLINE, // Preserve the original line terminator. ++ _PyTokenizer_IMPLY_NEWLINE, // Add a missing line terminator. ++ _PyTokenizer_APPEND_NEWLINE, // Also append a blank line to terminated input. ++} _PyTokenizer_Newline; ++ ++/* Reasons that the appended newline can change the token stream. */ ++enum { ++ _PyTokenizer_NEWLINE_STATEMENT = 1, ++ _PyTokenizer_NEWLINE_INDENT = 2, ++ _PyTokenizer_NEWLINE_CONTINUATION = 4, ++}; ++ + /* Initialize before use. metadata owns a reference released by _PyToken_Free; + a consumer taking that reference must set metadata to NULL. */ + struct token { +@@ -37,6 +50,7 @@ + + typedef struct { + int status; ++ unsigned char implicit_newline; // _PyTokenizer_Newline + _PyTokenizer_Diagnostic diagnostic; + _PyTok_Loc location; + _PyTok_Off cursor; +@@ -54,7 +68,9 @@ + } _PyTokenizer_Info; + + /* Get replaces the initialized token, releasing its previous metadata. +- Errors are returned as ERRORTOKEN, with or without a Python exception. */ ++ Errors are returned as ERRORTOKEN, with or without a Python exception. ++ In incomplete-input mode, an unfinished string retains its token type ++ with an absent span and E_INCOMPLETE status. No Python exception is set. */ + void _PyTokenizer_Get(struct tok_state *, struct token *); + void _PyTokenizer_Free(struct tok_state *); + int _PyTokenizer_Traverse(struct tok_state *, visitproc, void *); +@@ -69,6 +85,8 @@ + /* Views and borrowed snapshot references remain valid until the tokenizer is + mutated or freed. Source spans may be discarded when reading more input. */ + _PyTokenizer_Info _PyTokenizer_GetInfo(const struct tok_state *); ++int _PyTokenizer_GetStatus(const struct tok_state *); ++int _PyTokenizer_GetNewlineRequirements(const struct tok_state *); + /* An absent token span has a nonnull empty text view. */ + const char *_PyToken_TextView( + const struct tok_state *, const struct token *, Py_ssize_t *); +@@ -91,6 +109,7 @@ + struct tok_state *tok, PyObject *filename, PyObject *module); + void _PyTokenizer_SetOptions( + struct tok_state *tok, int extra_tokens, int type_comments); ++void _PyTokenizer_AllowIncompleteInput(struct tok_state *, int); + void _PyTokenizer_ImplyDedents(struct tok_state *); + int _PyTokenizer_HasTrailingStatement(const struct tok_state *); + int _PyTokenizer_IsInteractive(const struct tok_state *); +diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py +index 86efbd20eac..f703620cbb4 100644 +--- a/Platforms/Apple/__main__.py ++++ b/Platforms/Apple/__main__.py +@@ -5,17 +5,20 @@ + # This script simplifies the process of configuring, compiling and packaging an + # XCframework for an Apple platform. + # +-# At present, it only supports iOS, but it has been constructed so that it +-# could be used on any Apple platform. ++# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been ++# constructed so that it could be used on any Apple platform. + # + # The simplest entry point is: + # + # $ python Platforms/Apple ci iOS + # ++# (replace iOS with tvOS, visionOS or watchOS as required.) ++# + # which will: + # * Clean any pre-existing build artefacts + # * Configure and make a Python that can be used for the build +-# * Configure and make a Python for each supported iOS architecture and ABI ++# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS ++# architecture and ABI + # * Combine the outputs of the builds from the previous step into a single + # XCframework, merging binaries into a "fat" binary if necessary + # * Clone a copy of the testbed, configured to use the XCframework +@@ -75,9 +78,74 @@ + "x86_64-apple-ios-simulator": "x86_64-iphonesimulator", + }, + }, ++ "tvOS": { ++ "tvos-arm64": { ++ "arm64-apple-tvos": "arm64-appletvos", ++ }, ++ "tvos-arm64_x86_64-simulator": { ++ "arm64-apple-tvos-simulator": "arm64-appletvsimulator", ++ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator", ++ }, ++ }, ++ "visionOS": { ++ "xros-arm64": { ++ "arm64-apple-xros": "arm64-xros", ++ }, ++ "xros-arm64-simulator": { ++ "arm64-apple-xros-simulator": "arm64-xrsimulator", ++ }, ++ }, ++ "watchOS": { ++ "watchos-arm64_32": { ++ "arm64_32-apple-watchos": "arm64_32-watchos", ++ }, ++ "watchos-arm64_x86_64-simulator": { ++ "arm64-apple-watchos-simulator": "arm64-watchsimulator", ++ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator", ++ }, ++ }, ++} ++ ++# The configure arguments needed to produce each ABI flag. Flags are listed in ++# the order that configure adds them to ABIFLAGS; any `--abiflags` value must ++# use the same order. ++ABIFLAG_CONFIGURE_ARGS: dict[str, list[str]] = { ++ "t": ["--disable-gil"], ++ "d": ["--with-pydebug"], + } + + ++def validate_abiflags(value: str) -> str: ++ """Validate an `--abiflags` value (for use as an argparse type).""" ++ known = "".join(ABIFLAG_CONFIGURE_ARGS) ++ for flag in value: ++ if flag not in ABIFLAG_CONFIGURE_ARGS: ++ raise argparse.ArgumentTypeError( ++ f"unknown ABI flag {flag!r} (known flags: {known!r})" ++ ) ++ positions = [known.index(flag) for flag in value] ++ if positions != sorted(set(positions)): ++ raise argparse.ArgumentTypeError( ++ f"ABI flags must be unique, and in the order {known!r}" ++ ) ++ return value ++ ++ ++def framework_name(abiflags: str) -> str: ++ """The name of the Python framework for the given ABI flags.""" ++ return f"Python{abiflags.upper()}" ++ ++ ++def abiflag_configure_args(abiflags: str) -> list[str]: ++ """The configure arguments needed to produce the given ABI flags.""" ++ return [arg for flag in abiflags for arg in ABIFLAG_CONFIGURE_ARGS[flag]] ++ ++ ++def cross_build_dir(base: Path, abiflags: str) -> Path: ++ """The cross-build directory for the given base and ABI flags.""" ++ return base.with_name(f"{base.name}-{abiflags}") if abiflags else base ++ ++ + def subdir(name: str, create: bool = False) -> Path: + """Ensure that a cross-build directory for the given name exists.""" + path = CROSS_BUILD_DIR / name +@@ -136,11 +204,24 @@ + print(f"export {key}={shlex.quote(value)}") + + ++def platform_for_host(host): ++ """Determine the platform for a given host triple.""" ++ for plat, slices in HOSTS.items(): ++ for _, candidates in slices.items(): ++ for candidate in candidates: ++ if candidate == host: ++ return plat ++ raise KeyError(host) ++ ++ + def apple_env(host: str) -> EnvironmentT: + """Construct an Apple development environment for the given host.""" + env = { + "PATH": ":".join([ +- str(PYTHON_DIR / "Platforms/Apple/iOS/Resources/bin"), ++ str( ++ PYTHON_DIR ++ / f"Platforms/Apple/{platform_for_host(host)}/Resources/bin" ++ ), + str(subdir(host) / "prefix"), + "/usr/bin", + "/bin", +@@ -258,6 +339,7 @@ + cwd(subdir("build", create=True)), + ): + command = [relpath(PYTHON_DIR / "configure")] ++ command.extend(abiflag_configure_args(context.abiflags)) + if context.args: + command.extend(context.args) + run(command) +@@ -314,8 +396,8 @@ + Downloads binaries if they aren't already present. Downloads will be stored + in provided cache directory. + +- On iOS, as a safety mechanism, any dynamic libraries will be purged from +- the unpacked dependencies. ++ On non-macOS platforms, as a safety mechanism, any dynamic libraries will ++ be purged from the unpacked dependencies. + """ + # To create new builds of these dependencies, usually all that's necessary + # is to push a tag to the cpython-apple-source-deps repository, and GitHub +@@ -340,9 +422,9 @@ + ) + shutil.unpack_archive(archive_path, prefix_dir) + +- # Dynamic libraries will be preferentially linked over static; +- # On iOS, ensure that no dylibs are available in the prefix folder. +- if platform == "iOS": ++ # Dynamic libraries will be preferentially linked over static; On non-macOS ++ # platforms, ensure that no dylibs are available in the prefix folder. ++ if platform != "macOS": + for dylib in prefix_dir.glob("**/*.dylib"): + dylib.unlink() + +@@ -408,6 +490,7 @@ + f"--host={host}", + f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}", + f"--with-build-python={build_python_path()}", ++ "--enable-ipv6", + "--enable-framework", + # Dependent libraries. + f"--with-openssl={prefix_dir}", +@@ -420,6 +503,11 @@ + f"LIBZSTD_CFLAGS=-I{prefix_dir}/include", + f"LIBZSTD_LIBS=-L{prefix_dir}/lib -lzstd", + ] ++ command.extend(abiflag_configure_args(context.abiflags)) ++ if context.abiflags: ++ command.append( ++ f"--with-framework-name={framework_name(context.abiflags)}" ++ ) + + if context.args: + command.extend(context.args) +@@ -450,7 +538,8 @@ + """ + return ( + CROSS_BUILD_DIR +- / f"{host_triple}/Platforms/Apple/iOS/Frameworks/{multiarch}" ++ / f"{host_triple}/Platforms/Apple/{platform_for_host(host_triple)}" ++ / f"Frameworks/{multiarch}" + ) + + +@@ -521,11 +610,16 @@ + return set() + + +-def create_xcframework(platform: str) -> str: ++def create_xcframework(platform: str, abiflags: str) -> str: + """Build an XCframework from the component parts for the platform. + ++ :param platform: The platform being packaged (e.g., iOS). ++ :param abiflags: The ABI flags of the build (e.g., "t"). + :return: The version number of the Python version that was packaged. + """ ++ fw_name = framework_name(abiflags) ++ # The stdlib directory only carries the threading ABI flag. ++ abi_thread = "t" if "t" in abiflags else "" + package_path = CROSS_BUILD_DIR / platform + try: + package_path.mkdir() +@@ -547,7 +641,7 @@ + first_host_triple, first_multiarch = next(iter(slice_parts.items())) + first_framework = ( + framework_path(first_host_triple, first_multiarch) +- / "Python.framework" ++ / f"{fw_name}.framework" + ) + + if len(slice_parts) == 1: +@@ -557,7 +651,7 @@ + else: + print(f"Merging framework for {slice_name}...") + slice_path = CROSS_BUILD_DIR / slice_name +- slice_framework = slice_path / "Python.framework" ++ slice_framework = slice_path / f"{fw_name}.framework" + slice_framework.mkdir(exist_ok=True, parents=True) + + # Copy the Info.plist +@@ -574,11 +668,11 @@ + + # Create the "fat" library binary for the slice + run( +- ["lipo", "-create", "-output", slice_framework / "Python"] ++ ["lipo", "-create", "-output", slice_framework / fw_name] + + [ + ( + framework_path(host_triple, multiarch) +- / "Python.framework/Python" ++ / f"{fw_name}.framework/{fw_name}" + ) + for host_triple, multiarch in slice_parts.items() + ] +@@ -593,7 +687,7 @@ + "xcodebuild", + "-create-xcframework", + "-output", +- package_path / "Python.xcframework", ++ package_path / f"{fw_name}.xcframework", + ] + for framework in frameworks: + cmd.extend(["-framework", framework]) +@@ -601,7 +695,7 @@ + run(cmd) + + # Extract the package version from the merged framework +- version = package_version(package_path / "Python.xcframework") ++ version = package_version(package_path / f"{fw_name}.xcframework") + version_tag = ".".join(version.split(".")[:2]) + + # On non-macOS platforms, each framework in XCframework only contains the +@@ -616,10 +710,10 @@ + # host frameworks as the source for the merged version. + first_host_triple, first_multiarch = next(iter(slice_parts.items())) + first_path = framework_path(first_host_triple, first_multiarch) +- first_framework = first_path / "Python.framework" ++ first_framework = first_path / f"{fw_name}.framework" + +- slice_path = package_path / f"Python.xcframework/{slice_name}" +- slice_framework = slice_path / "Python.framework" ++ slice_path = package_path / f"{fw_name}.xcframework/{slice_name}" ++ slice_framework = slice_path / f"{fw_name}.framework" + + # Copy the binary helpers + print(f" - {slice_name} binaries") +@@ -641,9 +735,9 @@ + + print(f" - {slice_name} shared library") + # Create a simlink for the fat library +- shared_lib = slice_path / f"lib/libpython{version_tag}.dylib" ++ shared_lib = slice_path / f"lib/libpython{version_tag}{abiflags}.dylib" + shared_lib.parent.mkdir() +- shared_lib.symlink_to("../Python.framework/Python") ++ shared_lib.symlink_to(f"../{fw_name}.framework/{fw_name}") + + print(f" - {slice_name} architecture-specific files") + for host_triple, multiarch in slice_parts.items(): +@@ -654,7 +748,7 @@ + print(" - using this architecture as the common stdlib") + shutil.copytree( + framework_path(host_triple, multiarch) / "lib", +- package_path / "Python.xcframework/lib", ++ package_path / f"{fw_name}.xcframework/lib", + ignore=lib_platform_files, + symlinks=True, + ) +@@ -672,10 +766,10 @@ + host_path = ( + CROSS_BUILD_DIR + / host_triple +- / "Platforms/Apple/iOS/Frameworks" ++ / f"Platforms/Apple/{platform}/Frameworks" + / multiarch + ) +- host_framework = host_path / "Python.framework" ++ host_framework = host_path / f"{fw_name}.framework" + shutil.copy( + host_framework / "Headers/pyconfig.h", + slice_framework / f"Headers/pyconfig-{arch}.h", +@@ -696,14 +790,17 @@ + shutil.copy( + xcprivacy_file[lib], + slice_path +- / f"lib-{arch}/python{version_tag}" ++ / f"lib-{arch}/python{version_tag}{abi_thread}" + / f"lib-dynload/{module}.xcprivacy", + ) + + print(" - build tools") + shutil.copytree( +- PYTHON_DIR / "Platforms/Apple/testbed/Python.xcframework/build", +- package_path / "Python.xcframework/build", ++ PYTHON_DIR ++ / "Platforms/Apple/testbed" ++ / "Python.xcframework" ++ / "build", ++ package_path / f"{fw_name}.xcframework/build", + ) + + return version +@@ -716,7 +813,8 @@ + + with group("Building package"): + # Create an XCframework +- version = create_xcframework(context.platform) ++ version = create_xcframework(context.platform, context.abiflags) ++ fw_name = framework_name(context.abiflags) + + # Clone testbed + print() +@@ -727,7 +825,7 @@ + "--platform", + context.platform, + "--framework", +- CROSS_BUILD_DIR / context.platform / "Python.xcframework", ++ CROSS_BUILD_DIR / context.platform / f"{fw_name}.xcframework", + CROSS_BUILD_DIR / context.platform / "testbed", + ]) + +@@ -735,7 +833,10 @@ + archive_name = ( + CROSS_BUILD_DIR + / "dist" +- / f"python-{version}-{context.platform}-XCframework" ++ / ( ++ f"python-{version}{context.abiflags}" ++ f"-{context.platform}-XCframework" ++ ) + ) + + print() +@@ -799,7 +900,9 @@ + ) + if host in {"all", "hosts"}: + framework_path = ( +- CROSS_BUILD_DIR / context.platform / "Python.xcframework" ++ CROSS_BUILD_DIR ++ / context.platform ++ / f"{framework_name(context.abiflags)}.xcframework" + ) + else: + build_arch = platform.machine() +@@ -974,7 +1077,7 @@ + ), + ) + +- # --cross-build-dir argument ++ # --cross-build-dir and --abiflags arguments + for cmd in [ + clean, + configure_build, +@@ -994,9 +1097,20 @@ + dest="cross_build_dir", + type=Path, + help=( +- "Path to the cross-build directory " ++ "Base path of the cross-build directory " + f"(default: {CROSS_BUILD_DIR}). Can also be set " +- "with the CROSS_BUILD_DIR environment variable." ++ "with the CROSS_BUILD_DIR environment variable. If " ++ "--abiflags is specified, '-' is appended." ++ ), ++ ) ++ cmd.add_argument( ++ "--abiflags", ++ default="", ++ type=validate_abiflags, ++ help=( ++ "The ABI flags of the build: 't' (free-threaded; " ++ "--disable-gil), 'd' (debug; --with-pydebug), or 'td' (both). " ++ "Defaults to '' (a standard build)." + ), + ) + +@@ -1082,10 +1196,12 @@ + # Process command line arguments + context = parse_args() + +- # Set the CROSS_BUILD_DIR if an argument was provided ++ # Set the CROSS_BUILD_DIR, using any provided base directory, plus any ABI ++ # flags as a suffix. ++ global CROSS_BUILD_DIR + if context.cross_build_dir: +- global CROSS_BUILD_DIR + CROSS_BUILD_DIR = context.cross_build_dir.resolve() ++ CROSS_BUILD_DIR = cross_build_dir(CROSS_BUILD_DIR, context.abiflags) + + dispatch: dict[str, Callable] = { + "clean": clean, +diff --git a/Platforms/Apple/iOS/README.md b/Platforms/Apple/iOS/README.md +index 1a03664cd83..975c3056e98 100644 +--- a/Platforms/Apple/iOS/README.md ++++ b/Platforms/Apple/iOS/README.md +@@ -75,6 +75,39 @@ + individual commands to ensure that a stale build product are removed before + building. + ++### Building with ABI flags ++ ++By default, the `Platforms/Apple` build script produces a GIL-enabled, ++non-debug build of Python. The `--abiflags` option can be passed to any ++command of the build script to build a variant of Python with different ++ABI flags: ++ ++* `t` - a free-threaded build (`--disable-gil`) ++* `d` - a debug build (`--with-pydebug`) ++* `td` - a free-threaded debug build ++ ++For example, to build and test a free-threaded XCframework, run: ++ ++ python Platforms/Apple ci iOS --abiflags t ++ ++When ABI flags are specified: ++ ++* The ABI flags are appended to the name of the cross-build directory. The ++ free-threaded example above uses `cross-build-t`. If you specify a custom ++ location with `--cross-build-dir` (or the `CROSS_BUILD_DIR` environment ++ variable), that location is used as the base name, so `--cross-build-dir foo ++ --abiflags t` uses `foo-t`. This lets builds with different ABI flags ++ co-exist. ++* The framework is named `Python` followed by the uppercased ABI flags. For ++ example, a free-threaded build produces `PythonT.framework` and ++ `PythonT.xcframework`, and its headers are included as ++ `#include `. ++* The release archive includes the ABI flags in the version, e.g. ++ `python-3.X.Yt-iOS-XCframework.tar.gz`. ++ ++The same `--abiflags` value must be passed to every command that operates on a ++given build. ++ + ### Building a single-architecture framework + + If you're using the `Platforms/Apple` build script, you won't need to build +@@ -102,11 +135,13 @@ + Specify the name for the Python framework; defaults to `Python`. + + > [!NOTE] +- > Unless you know what you're doing, changing the name of the Python +- > framework on iOS is not advised. If you use this option, you won't be able +- > to run the `Platforms/Apple` build script without making significant manual +- > alterations, and you won't be able to use any binary packages unless you +- > compile them yourself using your own framework name. ++ > Builds that use ABI flags (such as free-threaded or debug builds) should ++ > use a framework name of `Python`, followed by the uppercased ABI flags ++ > (e.g., `PythonT` for a free-threaded build). The `Platforms/Apple` build ++ > script and the testbed rely on this naming convention. Other than this ++ > convention, changing the name of the Python framework on iOS is not ++ > advised; you won't be able to use any binary packages unless you compile ++ > them yourself using your own framework name. + + #### Building Python for iOS + +@@ -339,10 +374,11 @@ + + Running `python Platforms/Apple test iOS` generates a standalone version of the + `Platforms/Apple/testbed` project, and runs the full test suite. It does this using +-`Platforms/Apple/testbed` itself - the folder is an executable module that can be used +-to create and run a clone of the testbed project. The standalone version of the +-testbed will be created in a directory named +-`cross-build/iOS-testbed.`. ++`Platforms/Apple/testbed` itself - the folder is a template for an executable module ++that can be used to create and run a clone of the testbed project. The standalone ++version of the testbed will be created in a directory named ++`cross-build/iOS-testbed.`. Builds that use ABI flags use the ++`cross-build-` directory instead (e.g., `cross-build-t`). + + You can generate your own standalone testbed instance by running: + ``` +@@ -358,9 +394,22 @@ + python Platforms/Apple/testbed clone --platform iOS --framework my-testbed + ``` + +-The framework path can be the path path to a `Python.xcframework`, or the ++The framework path can be the path to a `Python.xcframework`, or the + path to a folder that contains a single-platform `Python.framework`. + ++The `Platforms/Apple/testbed` folder is a template. Any reference to the ++Python framework uses the placeholder `Python{{abiflags}}`. When the template ++project is cloned, the placeholder is replaced with the name of the framework ++being installed; the ABI flags are inferred automatically from the ++`--framework` argument. For example, to clone a testbed for a free-threaded ++framework: ++``` ++python Platforms/Apple/testbed clone --platform iOS --framework my-testbed ++``` ++ ++A cloned testbed is configured for one specific framework. It can be cloned ++again, but it can't be re-targeted to a framework with different ABI flags. ++ + You can then use the `my-testbed` folder to run the Python test suite, + passing in any command line arguments you may require. For example, if you're + trying to diagnose a failure in the `os` module, you might run: +diff --git a/Platforms/Apple/iOS/Resources/Info.plist.in b/Platforms/Apple/iOS/Resources/Info.plist.in +index c3e261ecd9e..1756208de40 100644 +--- a/Platforms/Apple/iOS/Resources/Info.plist.in ++++ b/Platforms/Apple/iOS/Resources/Info.plist.in +@@ -5,7 +5,7 @@ + CFBundleDevelopmentRegion + en + CFBundleExecutable +- Python ++ @PYTHONFRAMEWORK@ + CFBundleGetInfoString + Python Runtime and Library + CFBundleIdentifier +@@ -13,17 +13,17 @@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName +- Python ++ @PYTHONFRAMEWORK@ + CFBundlePackageType + FMWK + CFBundleShortVersionString +- @VERSION@ ++ %VERSION% + CFBundleLongVersionString + %VERSION%, (c) 2001-2024 Python Software Foundation. + CFBundleSignature + ???? + CFBundleVersion +- 1 ++ %VERSION% + CFBundleSupportedPlatforms + + iPhoneOS +diff --git a/Platforms/Apple/testbed/Python.xcframework/Info.plist b/Platforms/Apple/testbed/Python.xcframework/Info.plist +index c6418de6e74..fa9784c4330 100644 +--- a/Platforms/Apple/testbed/Python.xcframework/Info.plist ++++ b/Platforms/Apple/testbed/Python.xcframework/Info.plist +@@ -6,11 +6,11 @@ + + + BinaryPath +- Python.framework/Python ++ Python{{abiflags}}.framework/Python{{abiflags}} + LibraryIdentifier + ios-arm64 + LibraryPath +- Python.framework ++ Python{{abiflags}}.framework + SupportedArchitectures + + arm64 +@@ -20,11 +20,11 @@ + + + BinaryPath +- Python.framework/Python ++ Python{{abiflags}}.framework/Python{{abiflags}} + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath +- Python.framework ++ Python{{abiflags}}.framework + SupportedArchitectures + + arm64 +@@ -35,6 +35,98 @@ + SupportedPlatformVariant + simulator + ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ tvos-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ tvos ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ tvos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ tvos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ xros-arm64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ xros ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ xros-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ xros ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ watchos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_32 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64_32 ++ ++ SupportedPlatform ++ watchos ++ + + CFBundlePackageType + XFWK +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..a20d476fa7b +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ 9.0 ++ CFBundleVersion ++ 1 ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/utils.sh b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh +index e54471f68b7..bbe3620a139 100755 +--- a/Platforms/Apple/testbed/Python.xcframework/build/utils.sh ++++ b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh +@@ -34,9 +34,38 @@ + else + SLICE_FOLDER="ios-arm64_x86_64-simulator" + fi +- else ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then + echo "Installing Python modules for iOS Device" + SLICE_FOLDER="ios-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then ++ echo "Installing Python modules for tvOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then ++ SLICE_FOLDER="tvos-arm64-simulator" ++ else ++ SLICE_FOLDER="tvos-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then ++ echo "Installing Python modules for tvOS Device" ++ SLICE_FOLDER="tvos-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then ++ echo "Installing Python modules for watchOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then ++ SLICE_FOLDER="watchos-arm64-simulator" ++ else ++ SLICE_FOLDER="watchos-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then ++ echo "Installing Python modules for watchOS Device" ++ SLICE_FOLDER="watchos-arm64_32" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xrsimulator" ]; then ++ echo "Installing Python modules for visionOS Simulator" ++ SLICE_FOLDER="xros-arm64-simulator" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xros" ]; then ++ echo "Installing Python modules for visionOS Device" ++ SLICE_FOLDER="xros-arm64" ++ else ++ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME" ++ exit 1 + fi + + # If the XCframework has a shared lib folder, then it's a full framework. +@@ -140,7 +169,7 @@ + shift + + install_stdlib $PYTHON_XCFRAMEWORK_PATH +- PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib" | grep -E "^python3\.\d+$") ++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib" | grep -E "^python3\.\d+[a-z]*$") + echo "Install Python $PYTHON_VER standard library extension modules..." + process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload + +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..6f8c0bc2095 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ 4.0 ++ CFBundleVersion ++ 1 ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..e91673b4fbc +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist +@@ -0,0 +1,30 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ XROS ++ ++ CFBundleVersion ++ 1 ++ MinimumOSVersion ++ 2.0 ++ UIDeviceFamily ++ ++ 7 ++ ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README +new file mode 100644 +index 00000000000..ebd648d04bd +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling a tvOS ++on-device build for testing purposes. +diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..f8163468d8c +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling a tvOS ++simulator build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README +new file mode 100644 +index 00000000000..696af231df3 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling a watchOS on-device ++build for testing purposes. +diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..d38e1e98276 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling a watchOS ++simulator build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README +new file mode 100644 +index 00000000000..7bb0ad4b6ba +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling an visionOS simulator ++build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README +new file mode 100644 +index 00000000000..2fc2112e477 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling an visionOS on-device ++build for testing purposes. +diff --git a/Platforms/Apple/testbed/TestbedTests/TestbedTests.m b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m +index f7788c47f2c..435872e5e16 100644 +--- a/Platforms/Apple/testbed/TestbedTests/TestbedTests.m ++++ b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m +@@ -1,5 +1,5 @@ + #import +-#import ++#import + + @interface TestbedTests : XCTestCase + +diff --git a/Platforms/Apple/testbed/__main__.py b/Platforms/Apple/testbed/__main__.py +index c4c60e99ca9..1582986c0b8 100644 +--- a/Platforms/Apple/testbed/__main__.py ++++ b/Platforms/Apple/testbed/__main__.py +@@ -10,8 +10,20 @@ + + TEST_SLICES = { + "iOS": "ios-arm64_x86_64-simulator", ++ "tvOS": "tvos-arm64_x86_64-simulator", ++ "visionOS": "xros-arm64-simulator", ++ "watchOS": "watchos-arm64_x86_64-simulator", + } + ++# The checked-in testbed is a template. Any file containing this placeholder ++# is rewritten by `clone`, replacing the placeholder with the (uppercased) ++# ABI flags of the framework being installed. The placeholder is constructed ++# so that it doesn't appear literally in this file. ++ABIFLAGS_PLACEHOLDER = "{{" + "abiflags" + "}}" ++ ++FRAMEWORK_NAME_RE = re.compile(r"Python([A-Z]*)") ++ ++ + DECODE_ARGS = ("UTF-8", "backslashreplace") + + # The system log prefixes each line: +@@ -21,7 +33,7 @@ + LOG_PREFIX_REGEX = re.compile( + r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD + r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ +- r"\s+iOSTestbed\[\d+:\w+\] " # Process/thread ID ++ r"\s+.*Testbed\[\d+:\w+\] " # Process/thread ID + ) + + +@@ -54,6 +66,34 @@ + ) + ) + simulator = se_simulators[-1][1] ++ elif platform == "tvOS": ++ # Find the most recent tvOS release. ++ simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple TV" ++ ) ++ simulator = simulators[-1][1] ++ elif platform == "visionOS": ++ # Find the most recent visionOS release. ++ simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple Vision" ++ ) ++ simulator = simulators[-1][1] ++ elif platform == "watchOS": ++ # Any watchOS device will do; we'll look for "SE" devices. Select the ++ # one with the highest minimum runtime version - this is an indicator ++ # of the "newest" released device, which should always be supported ++ # on the "most recent" watchOS version. ++ se_simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple Watch" ++ and "Apple Watch SE" in devicetype["name"] ++ ) ++ simulator = se_simulators[-1][1] + else: + raise ValueError(f"Unknown platform {platform}") + +@@ -127,37 +167,156 @@ + shutil.copytree(src, tgt, symlinks=True) + + ++def framework_name(abiflags: str) -> str: ++ """The name of the Python framework for the given ABI flags.""" ++ return f"Python{abiflags.upper()}" ++ ++ ++def is_template(location: Path) -> bool: ++ """Is the testbed at the given location an unexpanded template? ++ ++ A template's XCframework `Info.plist` still contains the literal ++ ABIFLAGS_PLACEHOLDER; once expanded, the placeholder is gone. ++ """ ++ info_plist = location / "Python.xcframework/Info.plist" ++ return ( ++ info_plist.is_file() ++ and ABIFLAGS_PLACEHOLDER.encode() in info_plist.read_bytes() ++ ) ++ ++ ++def abiflags_from_framework(framework: Path) -> str: ++ """Infer the (lowercase) ABI flags from a `--framework` path. ++ ++ `framework` may be a `Python{FLAGS}.xcframework`, or a directory ++ containing a single simulator-slice `Python{FLAGS}.framework`. ++ ++ Raises ValueError if the ABI flags can't be determined. ++ """ ++ if framework.suffix == ".xcframework": ++ match = FRAMEWORK_NAME_RE.fullmatch(framework.stem) ++ if not match: ++ raise ValueError(f"{framework} is not a valid Python XCframework.") ++ return match.group(1).lower() ++ ++ candidates = sorted( ++ path ++ for path in framework.glob("Python*.framework") ++ if FRAMEWORK_NAME_RE.fullmatch(path.stem) ++ ) ++ if len(candidates) != 1: ++ raise ValueError( ++ f"Expected exactly one Python framework in {framework}; " ++ f"found {len(candidates)}." ++ ) ++ return FRAMEWORK_NAME_RE.fullmatch(candidates[0].stem).group(1).lower() ++ ++ ++def find_xcframework(location: Path) -> Path: ++ """Find the Python XCframework in a testbed project. ++ ++ Raises ValueError if more than one XCframework is found. ++ """ ++ candidates = sorted( ++ path ++ for path in location.glob("Python*.xcframework") ++ if re.fullmatch(r"Python[A-Z]*\.xcframework", path.name) ++ ) ++ if len(candidates) != 1: ++ raise ValueError( ++ f"Expected exactly one Python XCframework in {location}; " ++ f"found {len(candidates)}." ++ ) ++ return candidates[0] ++ ++ ++def expand_template(location: Path, abiflags: str) -> None: ++ """Expand the ABI flags placeholder in a cloned testbed template. ++ ++ The placeholder is replaced in the content of every template file, then ++ any file or directory whose name contains the placeholder is renamed. ++ Symlinks are neither followed nor modified. ++ """ ++ placeholder = ABIFLAGS_PLACEHOLDER ++ replacement = abiflags.upper() ++ for path in location.rglob("*"): ++ if ( ++ path.suffix in {".m", ".pbxproj", ".plist"} ++ and path.is_file() ++ and not path.is_symlink() ++ ): ++ content = path.read_text(encoding="utf-8") ++ if placeholder in content: ++ path.write_text( ++ content.replace(placeholder, replacement), ++ encoding="utf-8", ++ ) ++ ++ + def clone_testbed( + source: Path, + target: Path, +- framework: Path, ++ framework: Path | None, + platform: str, + apps: list[Path], + ) -> None: + if target.exists(): + print(f"{target} already exists; aborting without creating project.") + sys.exit(10) ++ source_is_template = is_template(source) ++ try: ++ orig_xc_framework_path = find_xcframework(source) ++ except ValueError as e: ++ print(e) ++ sys.exit(15) ++ fw_name = orig_xc_framework_path.stem + + if framework is None: +- if not ( +- source / "Python.xcframework" / TEST_SLICES[platform] / "bin" +- ).is_dir(): ++ # No --framework; we must be cloning an an already-expanded testbed. ++ # Raise an error if we're not. ++ if source_is_template: + print( +- f"The testbed being cloned ({source}) does not contain " +- "a framework with slices. Re-run with --framework" ++ "The initial testbed clone must provide a framework. " ++ "Re-run using --framework." + ) + sys.exit(11) ++ else: ++ # Look at the simulator slice in the existing framework; if ++ # it's a live framework, it will have a `bin` directory. ++ slice_path = orig_xc_framework_path / TEST_SLICES[platform] ++ if not (slice_path / "bin").is_dir(): ++ print( ++ f"The testbed being cloned ({source}) does not contain " ++ "a framework with slices. Re-run with --framework" ++ ) ++ sys.exit(11) ++ + else: + if not framework.is_dir(): + print(f"{framework} does not exist.") + sys.exit(12) ++ ++ if source_is_template: ++ # Initial testbed clone; the target framework name is ++ # taken directly from the provided framework, not the ++ # existing framework in the testbed. ++ try: ++ abiflags = abiflags_from_framework(framework) ++ except ValueError as e: ++ print(e) ++ sys.exit(13) ++ ++ fw_name = framework_name(abiflags) ++ + elif not ( +- framework.suffix == ".xcframework" +- or (framework / "Python.framework").is_dir() ++ framework.name == f"{fw_name}.xcframework" ++ or (framework / f"{fw_name}.framework").is_dir() + ): ++ # Re-cloning an already-expanded testbed: the ABI for the ++ # new framework must match the existing testbed. + print( +- f"{framework} is not an XCframework, " +- f"or a simulator slice of a framework build." ++ f"{framework} is not a {fw_name}.xcframework, " ++ f"or a simulator slice of a {fw_name}.framework build." + ) + sys.exit(13) + +@@ -179,9 +338,23 @@ + + print(" done") + +- orig_xc_framework_path = source / "Python.xcframework" +- xc_framework_path = target / "Python.xcframework" ++ xc_framework_path = target / f"{fw_name}.xcframework" + test_framework_path = xc_framework_path / TEST_SLICES[platform] ++ ++ if source_is_template: ++ # Unless an XCframework is being symlinked in, the testbed uses a copy ++ # of the template XCframework (either as-is, or with a simulator slice ++ # installed into it); that copy requires expansion. ++ if framework.suffix != ".xcframework": ++ shutil.copytree( ++ orig_xc_framework_path, ++ xc_framework_path, ++ symlinks=True, ++ ) ++ print(f" Expanding template for {fw_name}...", end="") ++ expand_template(target, abiflags) ++ print(" done") ++ + if framework is not None: + if framework.suffix == ".xcframework": + print(" Installing XCFramework...", end="") +@@ -193,9 +366,18 @@ + print(" Installing simulator framework...", end="") + # We're only installing a slice of a framework; we need + # to do a full tree copy to make sure we don't damage +- # symlinked content. +- shutil.copytree(orig_xc_framework_path, xc_framework_path) +- if test_framework_path.is_dir(): ++ # symlinked content. If this is an initial template clone, ++ # this copy has already been done as part of rolling out ++ # the template. ++ if not source_is_template: ++ shutil.copytree( ++ orig_xc_framework_path, xc_framework_path, symlinks=True ++ ) ++ ++ if ( ++ test_framework_path.is_dir() ++ and not test_framework_path.is_symlink() ++ ): + shutil.rmtree(test_framework_path) + else: + test_framework_path.unlink(missing_ok=True) +@@ -231,7 +413,7 @@ + # Simulator framework is a relative symlink. Rewrite the symlink + # relative to the new location. + orig_test_framework_path = ( +- source / "Python.XCframework" / test_framework_path.readlink() ++ orig_xc_framework_path / test_framework_path.readlink() + ).resolve() + test_framework_path.unlink() + test_framework_path.symlink_to( +@@ -303,7 +485,7 @@ + # many platforms, but when cloned, only one platform is preserved. + available_platforms = [ + platform +- for platform in ["iOS"] ++ for platform in ["iOS", "tvOS", "visionOS", "watchOS"] + if (Path(__file__).parent / f"{platform}Testbed").is_dir() + ] + +@@ -327,7 +509,9 @@ + "--framework", + help=( + "The location of the XCFramework (or simulator-only slice of an " +- "XCFramework) to use when running the testbed" ++ "XCFramework) to use when running the testbed. The framework's " ++ "ABI flags (e.g., free-threaded, debug) are inferred from its " ++ "name." + ), + ) + clone.add_argument( +@@ -357,7 +541,7 @@ + ), + description=( + "Run a testbed project. The arguments provided after `--` will be " +- "passed to the running iOS process as if they were arguments to " ++ "passed to the running test process as if they were arguments to " + "`python -m`." + ), + help="Run a testbed project", +@@ -408,12 +592,17 @@ + ) + elif context.subcommand == "run": + if test_args: ++ try: ++ xc_framework_path = find_xcframework(Path(__file__).parent) ++ except ValueError: ++ xc_framework_path = None ++ + if not ( +- Path(__file__).parent +- / "Python.xcframework" +- / TEST_SLICES[context.platform] +- / "bin" +- ).is_dir(): ++ xc_framework_path ++ and ( ++ xc_framework_path / TEST_SLICES[context.platform] / "bin" ++ ).is_dir() ++ ): + print( + "Testbed does not contain a compiled Python framework. " + f"Use `python {sys.argv[0]} clone ...` to create a " +diff --git a/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +index a0dd6851dc1..6ac40a32504 100644 +--- a/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj ++++ b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +@@ -7,10 +7,10 @@ + objects = { + + /* Begin PBXBuildFile section */ +- 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; +- 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +- 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; +- 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ 607A664C2B0EFC080010BFC8 /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python{{abiflags}}.xcframework */; }; ++ 607A664D2B0EFC080010BFC8 /* Python{{abiflags}}.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python{{abiflags}}.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ 607A66502B0EFFE00010BFC8 /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python{{abiflags}}.xcframework */; }; ++ 607A66512B0EFFE00010BFC8 /* Python{{abiflags}}.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python{{abiflags}}.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + /* End PBXBuildFile section */ + + /* Begin PBXContainerItemProxy section */ +@@ -30,7 +30,7 @@ + dstPath = ""; + dstSubfolderSpec = 10; + files = ( +- 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */, ++ 607A664D2B0EFC080010BFC8 /* Python{{abiflags}}.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; +@@ -41,7 +41,7 @@ + dstPath = ""; + dstSubfolderSpec = 10; + files = ( +- 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */, ++ 607A66512B0EFFE00010BFC8 /* Python{{abiflags}}.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; +@@ -51,7 +51,7 @@ + /* Begin PBXFileReference section */ + 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +- 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 607A664A2B0EFB310010BFC8 /* Python{{abiflags}}.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "Python{{abiflags}}.xcframework"; sourceTree = ""; }; + 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = iOSTestbed.xctestplan; sourceTree = ""; }; + /* End PBXFileReference section */ + +@@ -90,7 +90,7 @@ + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( +- 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */, ++ 607A664C2B0EFC080010BFC8 /* Python{{abiflags}}.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +@@ -98,7 +98,7 @@ + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( +- 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */, ++ 607A66502B0EFFE00010BFC8 /* Python{{abiflags}}.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +@@ -109,7 +109,7 @@ + isa = PBXGroup; + children = ( + 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */, +- 607A664A2B0EFB310010BFC8 /* Python.xcframework */, ++ 607A664A2B0EFB310010BFC8 /* Python{{abiflags}}.xcframework */, + 607A66142B0EFA380010BFC8 /* iOSTestbed */, + 607A66302B0EFA3A0010BFC8 /* TestbedTests */, + 607A66132B0EFA380010BFC8 /* Products */, +@@ -250,7 +250,7 @@ + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; +- shellScript = "set -e\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\ninstall_python Python.xcframework app app_packages\n"; ++ shellScript = "set -e\nsource $PROJECT_DIR/Python{{abiflags}}.xcframework/build/utils.sh\ninstall_python Python{{abiflags}}.xcframework app app_packages\n"; + showEnvVarsInLog = 0; + }; + /* End PBXShellScriptBuildPhase section */ +@@ -407,7 +407,7 @@ + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + ENABLE_USER_SCRIPT_SANDBOXING = NO; +- HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; + INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; +@@ -438,7 +438,7 @@ + DEVELOPMENT_TEAM = ""; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; +- HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; + INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; +@@ -467,7 +467,7 @@ + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; +- HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; +@@ -487,7 +487,7 @@ + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; +- HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; +diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..e14489ad077 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,506 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { ++ ++/* Begin PBXBuildFile section */ ++ EE7C8A1E2DCD6FF3003206DB /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python{{abiflags}}.xcframework */; }; ++ EE7C8A1F2DCD70CD003206DB /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python{{abiflags}}.xcframework */; }; ++ EE7C8A202DCD70CD003206DB /* Python{{abiflags}}.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python{{abiflags}}.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++/* End PBXBuildFile section */ ++ ++/* Begin PBXContainerItemProxy section */ ++ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = EE989E462DCD6E780036B268 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = EE989E4D2DCD6E780036B268; ++ remoteInfo = tvOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ ++ ++/* Begin PBXCopyFilesBuildPhase section */ ++ EE7C8A212DCD70CD003206DB /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EE7C8A202DCD70CD003206DB /* Python{{abiflags}}.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ ++ ++/* Begin PBXFileReference section */ ++ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = tvOSTestbed.xctestplan; sourceTree = ""; }; ++ EE7C8A1C2DCD6FF3003206DB /* Python{{abiflags}}.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python{{abiflags}}.xcframework; sourceTree = ""; }; ++ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "tvOSTestbed-Info.plist", ++ ); ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ ++/* Begin PBXFileSystemSynchronizedRootGroup section */ ++ EE989E502DCD6E780036B268 /* tvOSTestbed */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ exceptions = ( ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */, ++ ); ++ explicitFolders = ( ++ app, ++ app_packages, ++ ); ++ path = tvOSTestbed; ++ sourceTree = ""; ++ }; ++ EE989E682DCD6E7A0036B268 /* TestbedTests */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++/* End PBXFileSystemSynchronizedRootGroup section */ ++ ++/* Begin PBXFrameworksBuildPhase section */ ++ EE989E4B2DCD6E780036B268 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EE7C8A1F2DCD70CD003206DB /* Python{{abiflags}}.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E622DCD6E7A0036B268 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EE7C8A1E2DCD6FF3003206DB /* Python{{abiflags}}.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ ++ ++/* Begin PBXGroup section */ ++ EE989E452DCD6E780036B268 = { ++ isa = PBXGroup; ++ children = ( ++ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */, ++ EE7C8A1C2DCD6FF3003206DB /* Python{{abiflags}}.xcframework */, ++ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ EE989E4F2DCD6E780036B268 /* Products */, ++ ); ++ sourceTree = ""; ++ }; ++ EE989E4F2DCD6E780036B268 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */, ++ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ ++ ++/* Begin PBXNativeTarget section */ ++ EE989E4D2DCD6E780036B268 /* tvOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */; ++ buildPhases = ( ++ EE989E4A2DCD6E780036B268 /* Sources */, ++ EE989E4B2DCD6E780036B268 /* Frameworks */, ++ EE989E4C2DCD6E780036B268 /* Resources */, ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */, ++ EE7C8A212DCD70CD003206DB /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ fileSystemSynchronizedGroups = ( ++ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ ); ++ name = tvOSTestbed; ++ packageProductDependencies = ( ++ ); ++ productName = tvOSTestbed; ++ productReference = EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ EE989E642DCD6E7A0036B268 /* TestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildPhases = ( ++ EE989E612DCD6E7A0036B268 /* Sources */, ++ EE989E622DCD6E7A0036B268 /* Frameworks */, ++ EE989E632DCD6E7A0036B268 /* Resources */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */, ++ ); ++ fileSystemSynchronizedGroups = ( ++ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ ); ++ name = TestbedTests; ++ packageProductDependencies = ( ++ ); ++ productName = TestbedTests; ++ productReference = EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ ++ ++/* Begin PBXProject section */ ++ EE989E462DCD6E780036B268 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1620; ++ TargetAttributes = { ++ EE989E4D2DCD6E780036B268 = { ++ CreatedOnToolsVersion = 16.2; ++ }; ++ EE989E642DCD6E7A0036B268 = { ++ CreatedOnToolsVersion = 16.2; ++ TestTargetID = EE989E4D2DCD6E780036B268; ++ }; ++ }; ++ }; ++ buildConfigurationList = EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = EE989E452DCD6E780036B268; ++ minimizedProjectReferenceProxies = 1; ++ preferredProjectObjectVersion = 77; ++ productRefGroup = EE989E4F2DCD6E780036B268 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ EE989E4D2DCD6E780036B268 /* tvOSTestbed */, ++ EE989E642DCD6E7A0036B268 /* TestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ ++ ++/* Begin PBXResourcesBuildPhase section */ ++ EE989E4C2DCD6E780036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E632DCD6E7A0036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ ++ ++/* Begin PBXShellScriptBuildPhase section */ ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python{{abiflags}}.xcframework/build/utils.sh\n\ninstall_python Python{{abiflags}}.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; ++ }; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ EE989E4A2DCD6E780036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E612DCD6E7A0036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ targetProxy = EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ ++ ++/* Begin XCBuildConfiguration section */ ++ EE989E772DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = appletvos; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Debug; ++ }; ++ EE989E782DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = appletvos; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ VALIDATE_PRODUCT = YES; ++ }; ++ name = Release; ++ }; ++ EE989E7A2DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 3; ++ }; ++ name = Debug; ++ }; ++ EE989E7B2DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 3; ++ }; ++ name = Release; ++ }; ++ EE989E7D2DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 3; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Debug; ++ }; ++ EE989E7E2DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 3; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ ++ ++/* Begin XCConfigurationList section */ ++ EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E772DCD6E7A0036B268 /* Debug */, ++ EE989E782DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7A2DCD6E7A0036B268 /* Debug */, ++ EE989E7B2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7D2DCD6E7A0036B268 /* Debug */, ++ EE989E7E2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = EE989E462DCD6E780036B268 /* Project object */; ++} +diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme new file mode 100644 -index 00000000000..6f8c0bc2095 +index 00000000000..c3f3f894a1f --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist -@@ -0,0 +1,26 @@ ++++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme +@@ -0,0 +1,97 @@ + -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ watchOS -+ -+ MinimumOSVersion -+ 4.0 -+ CFBundleVersion -+ 1 -+ -+ -diff --git a/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/tvOSTestbed.xctestplan b/Platforms/Apple/testbed/tvOSTestbed.xctestplan new file mode 100644 -index 00000000000..e91673b4fbc +index 00000000000..f996facc178 --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist -@@ -0,0 +1,30 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ XROS -+ -+ CFBundleVersion -+ 1 -+ MinimumOSVersion -+ 2.0 -+ UIDeviceFamily -+ -+ 7 -+ -+ -+ -diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README ++++ b/Platforms/Apple/testbed/tvOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "name" : "Test Scheme Action", ++ "options" : { ++ ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "tvOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "EE989E642DCD6E7A0036B268", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +\ No newline at end of file +diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h new file mode 100644 -index 00000000000..ebd648d04bd +index 00000000000..112c9ed64b8 --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// tvOSTestbed ++// + -+It should be used as a target for `--enable-framework` when compiling a tvOS -+on-device build for testing purposes. -diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README ++#import ++ ++@interface AppDelegate : UIResponder ++ ++ ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m new file mode 100644 -index 00000000000..f8163468d8c +index 00000000000..7501ec7b6ef --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m +@@ -0,0 +1,24 @@ ++// ++// AppDelegate.m ++// tvOSTestbed ++// + -+It should be used as a target for `--enable-framework` when compiling a tvOS -+simulator build for testing purposes (either x86_64 or ARM64). -diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README ++#import "AppDelegate.h" ++#import "SceneDelegate.h" ++ ++@interface AppDelegate () ++ ++@end ++ ++@implementation AppDelegate ++ ++- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { ++ UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; ++ configuration.delegateClass = [SceneDelegate class]; ++ return configuration; ++} ++ ++- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { ++} ++ ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard new file mode 100644 -index 00000000000..696af231df3 +index 00000000000..660ba53de4f --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. -+ -+It should be used as a target for `--enable-framework` when compiling a watchOS on-device -+build for testing purposes. -diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README ++++ b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,24 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h new file mode 100644 -index 00000000000..d38e1e98276 +index 00000000000..264aacadca1 --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++++ b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h +@@ -0,0 +1,12 @@ ++// ++// SceneDelegate.h ++// iOSTestbed ++// + -+It should be used as a target for `--enable-framework` when compiling a watchOS -+simulator build for testing purposes (either x86_64 or ARM64). -diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README ++#import ++ ++@interface SceneDelegate : UIResponder ++ ++@property (strong, nonatomic) UIWindow *window; ++ ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m new file mode 100644 -index 00000000000..7bb0ad4b6ba +index 00000000000..e82e447985d --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++++ b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m +@@ -0,0 +1,21 @@ ++// ++// SceneDelegate.m ++// iOSTestbed ++// + -+It should be used as a target for `--enable-framework` when compiling an visionOS simulator -+build for testing purposes (either x86_64 or ARM64). -diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README ++#import "SceneDelegate.h" ++ ++@interface SceneDelegate () ++ ++@end ++ ++@implementation SceneDelegate ++ ++- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { ++ UIWindowScene *windowScene = (UIWindowScene *)scene; ++ self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; ++ self.window.rootViewController = [[UIViewController alloc] init]; ++ [self.window makeKeyAndVisible]; ++} ++ ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/app/README b/Platforms/Apple/testbed/tvOSTestbed/app/README new file mode 100644 -index 00000000000..2fc2112e477 +index 00000000000..46c0e8e2a29 --- /dev/null -+++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. -+ -+It should be used as a target for `--enable-framework` when compiling an visionOS on-device -+build for testing purposes. -diff --git a/Platforms/Apple/testbed/__main__.py b/Platforms/Apple/testbed/__main__.py -index 797b522678f..643034e9bca 100644 ---- a/Platforms/Apple/testbed/__main__.py -+++ b/Platforms/Apple/testbed/__main__.py -@@ -10,6 +10,9 @@ - - TEST_SLICES = { - "iOS": "ios-arm64_x86_64-simulator", -+ "tvOS": "tvos-arm64_x86_64-simulator", -+ "visionOS": "xros-arm64-simulator", -+ "watchOS": "watchos-arm64_x86_64-simulator", - } - - DECODE_ARGS = ("UTF-8", "backslashreplace") -@@ -21,7 +24,7 @@ - LOG_PREFIX_REGEX = re.compile( - r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD - r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ -- r"\s+iOSTestbed\[\d+:\w+\] " # Process/thread ID -+ r"\s+.*Testbed\[\d+:\w+\] " # Process/thread ID - ) - - -@@ -54,6 +57,24 @@ - ) - ) - simulator = se_simulators[-1][1] -+ elif platform == "tvOS": -+ # Find the most recent tvOS release. -+ simulators = sorted( -+ (devicetype["minRuntimeVersion"], devicetype["name"]) -+ for devicetype in json_data["devicetypes"] -+ if devicetype["productFamily"] == "Apple TV" -+ ) -+ simulator = simulators[-1][1] -+ elif platform == "visionOS": -+ # Find the most recent visionOS release. -+ simulators = sorted( -+ (devicetype["minRuntimeVersion"], devicetype["name"]) -+ for devicetype in json_data["devicetypes"] -+ if devicetype["productFamily"] == "Apple Vision" -+ ) -+ simulator = simulators[-1][1] -+ elif platform == "watchOS": -+ raise NotImplementedError("Don't know how to launch watchOS (yet)") - else: - raise ValueError(f"Unknown platform {platform}") - -@@ -295,7 +316,7 @@ - # many platforms, but when cloned, only one platform is preserved. - available_platforms = [ - platform -- for platform in ["iOS"] -+ for platform in ["iOS", "tvOS", "visionOS", "watchOS"] - if (Path(__file__).parent / f"{platform}Testbed").is_dir() - ] - -@@ -349,7 +370,7 @@ - ), - description=( - "Run a testbed project. The arguments provided after `--` will be " -- "passed to the running iOS process as if they were arguments to " -+ "passed to the running test process as if they were arguments to " - "`python -m`." - ), - help="Run a testbed project", -diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj ++++ b/Platforms/Apple/testbed/tvOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH, and will be the ++working directory for the test suite. +diff --git a/Platforms/Apple/testbed/tvOSTestbed/app_packages/README b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README new file mode 100644 -index 00000000000..5ddfba882f4 +index 00000000000..02c2beccfbd --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,505 @@ ++++ b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README +@@ -0,0 +1,7 @@ ++This folder can be a target for installing any Python dependencies needed by the ++test suite. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Platforms/Apple/testbed/tvOSTestbed/main.m b/Platforms/Apple/testbed/tvOSTestbed/main.m +new file mode 100644 +index 00000000000..d5808fbb933 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// tvOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +diff --git a/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +new file mode 100644 +index 00000000000..6d84447c031 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +@@ -0,0 +1,62 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.tvOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ UIWindowSceneSessionRoleApplication ++ ++ ++ UISceneDelegateClassName ++ SceneDelegate ++ UISceneConfigurationName ++ Default Configuration ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..ee19e22d533 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,550 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; @@ -1042,29 +8167,41 @@ index 00000000000..5ddfba882f4 + objects = { + +/* Begin PBXBuildFile section */ -+ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; -+ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; -+ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEB367CE2DADF5C900B9A1D7 /* Python{{abiflags}}.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEB367CF2DADF5D300B9A1D7 /* Python{{abiflags}}.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEE9C80D2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework */; }; ++ EEE9C80E2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ -+ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = { ++ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; -+ containerPortal = EE989E462DCD6E780036B268 /* Project object */; ++ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; + proxyType = 1; -+ remoteGlobalIDString = EE989E4D2DCD6E780036B268; -+ remoteInfo = tvOSTestbed; ++ remoteGlobalIDString = 607A66112B0EFA380010BFC8; ++ remoteInfo = iOSTestbed; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ -+ EE7C8A212DCD70CD003206DB /* Embed Frameworks */ = { ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EEB367CF2DADF5D300B9A1D7 /* Python{{abiflags}}.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( -+ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */, ++ EEB367CE2DADF5C900B9A1D7 /* Python{{abiflags}}.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; @@ -1072,36 +8209,36 @@ index 00000000000..5ddfba882f4 +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ -+ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = tvOSTestbed.xctestplan; sourceTree = ""; }; -+ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; -+ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; -+ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = visionOSTestbed.xctestplan; sourceTree = ""; }; ++ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ EEE9C80C2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python{{abiflags}}.xcframework; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ -+ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */ = { ++ 4172389B57E3372C38B3DB88 /* Exceptions for "visionOSTestbed" folder in "visionOSTestbed" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( -+ "tvOSTestbed-Info.plist", ++ "visionOSTestbed-Info.plist", + ); -+ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ -+ EE989E502DCD6E780036B268 /* tvOSTestbed */ = { ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( -+ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */, ++ 4172389B57E3372C38B3DB88 /* Exceptions for "visionOSTestbed" folder in "visionOSTestbed" target */, + ); + explicitFolders = ( + app, + app_packages, + ); -+ path = tvOSTestbed; ++ path = visionOSTestbed; + sourceTree = ""; + }; -+ EE989E682DCD6E7A0036B268 /* TestbedTests */ = { ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = TestbedTests; + sourceTree = ""; @@ -1109,41 +8246,41 @@ index 00000000000..5ddfba882f4 +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ -+ EE989E4B2DCD6E780036B268 /* Frameworks */ = { ++ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( -+ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */, ++ EEE9C80D2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; -+ EE989E622DCD6E7A0036B268 /* Frameworks */ = { ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( -+ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */, ++ EEE9C80E2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ -+ EE989E452DCD6E780036B268 = { ++ 607A66092B0EFA380010BFC8 = { + isa = PBXGroup; + children = ( -+ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */, -+ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */, -+ EE989E502DCD6E780036B268 /* tvOSTestbed */, -+ EE989E682DCD6E7A0036B268 /* TestbedTests */, -+ EE989E4F2DCD6E780036B268 /* Products */, ++ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */, ++ EEE9C80C2DAB5ECA0056F8C6 /* Python{{abiflags}}.xcframework */, ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ 607A66132B0EFA380010BFC8 /* Products */, + ); + sourceTree = ""; + }; -+ EE989E4F2DCD6E780036B268 /* Products */ = { ++ 607A66132B0EFA380010BFC8 /* Products */ = { + isa = PBXGroup; + children = ( -+ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */, -+ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */, ++ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */, + ); + name = Products; + sourceTree = ""; @@ -1151,100 +8288,102 @@ index 00000000000..5ddfba882f4 +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ -+ EE989E4D2DCD6E780036B268 /* tvOSTestbed */ = { ++ 607A66112B0EFA380010BFC8 /* visionOSTestbed */ = { + isa = PBXNativeTarget; -+ buildConfigurationList = EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */; ++ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */; + buildPhases = ( -+ EE989E4A2DCD6E780036B268 /* Sources */, -+ EE989E4B2DCD6E780036B268 /* Frameworks */, -+ EE989E4C2DCD6E780036B268 /* Resources */, -+ EE7C8A222DCD70F4003206DB /* Process Python libraries */, -+ EE7C8A212DCD70CD003206DB /* Embed Frameworks */, ++ 607A660E2B0EFA380010BFC8 /* Sources */, ++ 607A660F2B0EFA380010BFC8 /* Frameworks */, ++ 607A66102B0EFA380010BFC8 /* Resources */, ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */, ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( -+ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, + ); -+ name = tvOSTestbed; ++ name = visionOSTestbed; + packageProductDependencies = ( + ); -+ productName = tvOSTestbed; -+ productReference = EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */; ++ productName = iOSTestbed; ++ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */; + productType = "com.apple.product-type.application"; + }; -+ EE989E642DCD6E7A0036B268 /* TestbedTests */ = { ++ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */ = { + isa = PBXNativeTarget; -+ buildConfigurationList = EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */; + buildPhases = ( -+ EE989E612DCD6E7A0036B268 /* Sources */, -+ EE989E622DCD6E7A0036B268 /* Frameworks */, -+ EE989E632DCD6E7A0036B268 /* Resources */, ++ 607A66292B0EFA3A0010BFC8 /* Sources */, ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, ++ 607A662B2B0EFA3A0010BFC8 /* Resources */, ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( -+ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */, ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( -+ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, + ); + name = TestbedTests; + packageProductDependencies = ( + ); -+ productName = TestbedTests; -+ productReference = EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */; ++ productName = iOSTestbedTests; ++ productReference = 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ -+ EE989E462DCD6E780036B268 /* Project object */ = { ++ 607A660A2B0EFA380010BFC8 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; -+ LastUpgradeCheck = 1620; ++ LastUpgradeCheck = 1500; + TargetAttributes = { -+ EE989E4D2DCD6E780036B268 = { -+ CreatedOnToolsVersion = 16.2; ++ 607A66112B0EFA380010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; + }; -+ EE989E642DCD6E7A0036B268 = { -+ CreatedOnToolsVersion = 16.2; -+ TestTargetID = EE989E4D2DCD6E780036B268; ++ 607A662C2B0EFA3A0010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ TestTargetID = 607A66112B0EFA380010BFC8; + }; + }; + }; -+ buildConfigurationList = EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */; ++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */; ++ compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); -+ mainGroup = EE989E452DCD6E780036B268; ++ mainGroup = 607A66092B0EFA380010BFC8; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; -+ productRefGroup = EE989E4F2DCD6E780036B268 /* Products */; ++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( -+ EE989E4D2DCD6E780036B268 /* tvOSTestbed */, -+ EE989E642DCD6E7A0036B268 /* TestbedTests */, ++ 607A66112B0EFA380010BFC8 /* visionOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ -+ EE989E4C2DCD6E780036B268 /* Resources */ = { ++ 607A66102B0EFA380010BFC8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; -+ EE989E632DCD6E7A0036B268 /* Resources */ = { ++ 607A662B2B0EFA3A0010BFC8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( @@ -1254,7 +8393,7 @@ index 00000000000..5ddfba882f4 +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ -+ EE7C8A222DCD70F4003206DB /* Process Python libraries */ = { ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; @@ -1271,19 +8410,20 @@ index 00000000000..5ddfba882f4 + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; -+ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python{{abiflags}}.xcframework/build/utils.sh\n\ninstall_python Python{{abiflags}}.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ -+ EE989E4A2DCD6E780036B268 /* Sources */ = { ++ 607A660E2B0EFA380010BFC8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; -+ EE989E612DCD6E7A0036B268 /* Sources */ = { ++ 607A66292B0EFA3A0010BFC8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( @@ -1293,15 +8433,15 @@ index 00000000000..5ddfba882f4 +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ -+ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */ = { -+ isa = PBXTargetDependency; -+ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; -+ targetProxy = EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */; ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; ++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ -+ EE989E772DCD6E7A0036B268 /* Debug */ = { ++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; @@ -1327,7 +8467,7 @@ index 00000000000..5ddfba882f4 + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1338,8 +8478,7 @@ index 00000000000..5ddfba882f4 + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; @@ -1354,17 +8493,16 @@ index 00000000000..5ddfba882f4 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; -+ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; -+ SDKROOT = appletvos; -+ TVOS_DEPLOYMENT_TARGET = 18.2; ++ SDKROOT = xros; + }; + name = Debug; + }; -+ EE989E782DCD6E7A0036B268 /* Release */ = { ++ 607A66402B0EFA3A0010BFC8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; @@ -1390,7 +8528,7 @@ index 00000000000..5ddfba882f4 + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1401,9 +8539,7 @@ index 00000000000..5ddfba882f4 + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1412,137 +8548,171 @@ index 00000000000..5ddfba882f4 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; -+ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; -+ SDKROOT = appletvos; -+ TVOS_DEPLOYMENT_TARGET = 18.2; ++ SDKROOT = xros; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; -+ EE989E7A2DCD6E7A0036B268 /* Debug */ = { ++ 607A66422B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; -+ GENERATE_INFOPLIST_FILE = NO; -+ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; ++ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 3; ++ TARGETED_DEVICE_FAMILY = 7; ++ XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; -+ EE989E7B2DCD6E7A0036B268 /* Release */ = { ++ 607A66432B0EFA3A0010BFC8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; -+ GENERATE_INFOPLIST_FILE = NO; -+ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; ++ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 3; ++ TARGETED_DEVICE_FAMILY = 7; ++ XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; -+ EE989E7D2DCD6E7A0036B268 /* Debug */ = { ++ 607A66452B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 3; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; -+ TVOS_DEPLOYMENT_TARGET = 18.2; ++ TARGETED_DEVICE_FAMILY = 7; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; + }; + name = Debug; + }; -+ EE989E7E2DCD6E7A0036B268 /* Release */ = { ++ 607A66462B0EFA3A0010BFC8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 3; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; -+ TVOS_DEPLOYMENT_TARGET = 18.2; ++ TARGETED_DEVICE_FAMILY = 7; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ -+ EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */ = { ++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( -+ EE989E772DCD6E7A0036B268 /* Debug */, -+ EE989E782DCD6E7A0036B268 /* Release */, ++ 607A663F2B0EFA3A0010BFC8 /* Debug */, ++ 607A66402B0EFA3A0010BFC8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; -+ EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */ = { ++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( -+ EE989E7A2DCD6E7A0036B268 /* Debug */, -+ EE989E7B2DCD6E7A0036B268 /* Release */, ++ 607A66422B0EFA3A0010BFC8 /* Debug */, ++ 607A66432B0EFA3A0010BFC8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; -+ EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( -+ EE989E7D2DCD6E7A0036B268 /* Debug */, -+ EE989E7E2DCD6E7A0036B268 /* Release */, ++ 607A66452B0EFA3A0010BFC8 /* Debug */, ++ 607A66462B0EFA3A0010BFC8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; -+ rootObject = EE989E462DCD6E780036B268 /* Project object */; ++ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; +} -diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme new file mode 100644 -index 00000000000..c3f3f894a1f +index 00000000000..b8397ea7cf1 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme ++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme @@ -0,0 +1,97 @@ + + + ++ BlueprintIdentifier = "607A66112B0EFA380010BFC8" ++ BuildableName = "visionOSTestbed.app" ++ BlueprintName = "visionOSTestbed" ++ ReferencedContainer = "container:visionOSTestbed.xcodeproj"> + + + @@ -1577,7 +8747,7 @@ index 00000000000..c3f3f894a1f + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -1587,10 +8757,10 @@ index 00000000000..c3f3f894a1f + parallelizable = "YES"> + ++ ReferencedContainer = "container:visionOSTestbed.xcodeproj"> + + + @@ -1609,10 +8779,10 @@ index 00000000000..c3f3f894a1f + runnableDebuggingMode = "0"> + ++ BlueprintIdentifier = "607A66112B0EFA380010BFC8" ++ BuildableName = "visionOSTestbed.app" ++ BlueprintName = "visionOSTestbed" ++ ReferencedContainer = "container:visionOSTestbed.xcodeproj"> + + + @@ -1626,10 +8796,10 @@ index 00000000000..c3f3f894a1f + runnableDebuggingMode = "0"> + ++ BlueprintIdentifier = "607A66112B0EFA380010BFC8" ++ BuildableName = "visionOSTestbed.app" ++ BlueprintName = "visionOSTestbed" ++ ReferencedContainer = "container:visionOSTestbed.xcodeproj"> + + + @@ -1641,16 +8811,16 @@ index 00000000000..c3f3f894a1f + revealArchiveInOrganizer = "YES"> + + -diff --git a/Platforms/Apple/testbed/tvOSTestbed.xctestplan b/Platforms/Apple/testbed/tvOSTestbed.xctestplan +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xctestplan b/Platforms/Apple/testbed/visionOSTestbed.xctestplan new file mode 100644 -index 00000000000..f996facc178 +index 00000000000..73a2be8c22a --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed.xctestplan ++++ b/Platforms/Apple/testbed/visionOSTestbed.xctestplan @@ -0,0 +1,46 @@ +{ + "configurations" : [ + { -+ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "id" : "C17FA044-0B70-48CA-AFF8-BC252081002F", + "name" : "Test Scheme Action", + "options" : { + @@ -1676,17 +8846,17 @@ index 00000000000..f996facc178 + } + ], + "targetForVariableExpansion" : { -+ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "containerPath" : "container:visionOSTestbed.xcodeproj", + "identifier" : "607A66112B0EFA380010BFC8", -+ "name" : "tvOSTestbed" ++ "name" : "visionOSTestbed" + } + }, + "testTargets" : [ + { + "parallelizable" : false, + "target" : { -+ "containerPath" : "container:tvOSTestbed.xcodeproj", -+ "identifier" : "EE989E642DCD6E7A0036B268", ++ "containerPath" : "container:visionOSTestbed.xcodeproj", ++ "identifier" : "607A662C2B0EFA3A0010BFC8", + "name" : "TestbedTests" + } + } @@ -1694,15 +8864,15 @@ index 00000000000..f996facc178 + "version" : 1 +} \ No newline at end of file -diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h +diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h new file mode 100644 -index 00000000000..112c9ed64b8 +index 00000000000..8fe7ec8e64e --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h ++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h @@ -0,0 +1,11 @@ +// +// AppDelegate.h -+// tvOSTestbed ++// visionOSTestbed +// + +#import @@ -1711,15 +8881,15 @@ index 00000000000..112c9ed64b8 + + +@end -diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m +diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m new file mode 100644 -index 00000000000..7501ec7b6ef +index 00000000000..f46c65a3834 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m ++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m @@ -0,0 +1,24 @@ +// +// AppDelegate.m -+// tvOSTestbed ++// visionOSTestbed +// + +#import "AppDelegate.h" @@ -1741,41 +8911,59 @@ index 00000000000..7501ec7b6ef +} + +@end -diff --git a/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 -index 00000000000..660ba53de4f +index 00000000000..eb878970081 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard -@@ -0,0 +1,24 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +@@ -0,0 +1,11 @@ ++{ ++ "colors" : [ ++ { ++ "idiom" : "universal" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +new file mode 100644 +index 00000000000..13613e3ee1a +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +@@ -0,0 +1,13 @@ ++{ ++ "images" : [ ++ { ++ "idiom" : "universal", ++ "platform" : "ios", ++ "size" : "1024x1024" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json +new file mode 100644 +index 00000000000..73c00596a7f +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json +@@ -0,0 +1,6 @@ ++{ ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h new file mode 100644 index 00000000000..264aacadca1 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h ++++ b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h @@ -0,0 +1,12 @@ +// +// SceneDelegate.h @@ -1789,11 +8977,11 @@ index 00000000000..264aacadca1 +@property (strong, nonatomic) UIWindow *window; + +@end -diff --git a/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m +diff --git a/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m new file mode 100644 index 00000000000..e82e447985d --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m ++++ b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m @@ -0,0 +1,21 @@ +// +// SceneDelegate.m @@ -1816,41 +9004,41 @@ index 00000000000..e82e447985d +} + +@end -diff --git a/Platforms/Apple/testbed/tvOSTestbed/app/README b/Platforms/Apple/testbed/tvOSTestbed/app/README +diff --git a/Platforms/Apple/testbed/visionOSTestbed/app/README b/Platforms/Apple/testbed/visionOSTestbed/app/README new file mode 100644 -index 00000000000..46c0e8e2a29 +index 00000000000..af22c685f87 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/app/README ++++ b/Platforms/Apple/testbed/visionOSTestbed/app/README @@ -0,0 +1,7 @@ +This folder can contain any Python application code. + +During the build, any binary modules found in this folder will be processed into -+Framework form. ++iOS Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH, and will be the +working directory for the test suite. -diff --git a/Platforms/Apple/testbed/tvOSTestbed/app_packages/README b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README +diff --git a/Platforms/Apple/testbed/visionOSTestbed/app_packages/README b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README new file mode 100644 -index 00000000000..02c2beccfbd +index 00000000000..42d7fdeb813 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README ++++ b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README @@ -0,0 +1,7 @@ +This folder can be a target for installing any Python dependencies needed by the +test suite. + +During the build, any binary modules found in this folder will be processed into -+Framework form. ++iOS Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH. -diff --git a/Platforms/Apple/testbed/tvOSTestbed/main.m b/Platforms/Apple/testbed/tvOSTestbed/main.m +diff --git a/Platforms/Apple/testbed/visionOSTestbed/main.m b/Platforms/Apple/testbed/visionOSTestbed/main.m new file mode 100644 -index 00000000000..d5808fbb933 +index 00000000000..2bb491f25c8 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/main.m ++++ b/Platforms/Apple/testbed/visionOSTestbed/main.m @@ -0,0 +1,16 @@ +// +// main.m -+// tvOSTestbed ++// visionOSTestbed +// + +#import @@ -1864,12 +9052,12 @@ index 00000000000..d5808fbb933 + return UIApplicationMain(argc, argv, nil, appDelegateClassName); + } +} -diff --git a/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +diff --git a/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist new file mode 100644 -index 00000000000..6d84447c031 +index 00000000000..11a8230d1d2 --- /dev/null -+++ b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist -@@ -0,0 +1,62 @@ ++++ b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist +@@ -0,0 +1,66 @@ + + + @@ -1881,7 +9069,7 @@ index 00000000000..6d84447c031 + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier -+ org.python.tvOSTestbed ++ org.python.visionOSTestbed + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName @@ -1894,24 +9082,13 @@ index 00000000000..6d84447c031 + ???? + CFBundleVersion + 1 -+ LSRequiresIPhoneOS -+ -+ UIRequiresFullScreen -+ -+ UILaunchStoryboardName -+ Launch Screen -+ UISupportedInterfaceOrientations -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ UISupportedInterfaceOrientations~ipad ++ TestArgs + -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationPortraitUpsideDown -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight ++ test ++ -uall ++ --single-process ++ --rerun ++ -W + + UIApplicationSceneManifest + @@ -1930,14 +9107,29 @@ index 00000000000..6d84447c031 + + + ++ UIRequiresFullScreen ++ ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ + + -diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +diff --git a/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/project.pbxproj new file mode 100644 -index 00000000000..d3ca84bd32c +index 00000000000..d5220c60e65 --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,550 @@ ++++ b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,490 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; @@ -1947,41 +9139,29 @@ index 00000000000..d3ca84bd32c + objects = { + +/* Begin PBXBuildFile section */ -+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; -+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; ++ 60F7C0012EB0000000A1B2C3 /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F7C0072EB0000000A1B2C3 /* Python{{abiflags}}.xcframework */; }; ++ 60F7C0022EB0000000A1B2C3 /* Python{{abiflags}}.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F7C0072EB0000000A1B2C3 /* Python{{abiflags}}.xcframework */; }; ++ 60F7C0032EB0000000A1B2C3 /* Python{{abiflags}}.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 60F7C0072EB0000000A1B2C3 /* Python{{abiflags}}.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ -+ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { ++ 60F7C0042EB0000000A1B2C3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; -+ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; ++ containerPortal = 60F7C0132EB0000000A1B2C3 /* Project object */; + proxyType = 1; -+ remoteGlobalIDString = 607A66112B0EFA380010BFC8; -+ remoteInfo = iOSTestbed; ++ remoteGlobalIDString = 60F7C0112EB0000000A1B2C3; ++ remoteInfo = watchOSTestbed; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ -+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { -+ isa = PBXCopyFilesBuildPhase; -+ buildActionMask = 2147483647; -+ dstPath = ""; -+ dstSubfolderSpec = 10; -+ files = ( -+ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { ++ 60F7C0052EB0000000A1B2C3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( -+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ 60F7C0032EB0000000A1B2C3 /* Python{{abiflags}}.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; @@ -1989,36 +9169,36 @@ index 00000000000..d3ca84bd32c +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ -+ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = visionOSTestbed.xctestplan; sourceTree = ""; }; -+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; -+ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 60F7C0062EB0000000A1B2C3 /* watchOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = watchOSTestbed.xctestplan; sourceTree = ""; }; ++ 60F7C0072EB0000000A1B2C3 /* Python{{abiflags}}.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python{{abiflags}}.xcframework; sourceTree = ""; }; ++ 60F7C0082EB0000000A1B2C3 /* watchOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = watchOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 60F7C0092EB0000000A1B2C3 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ -+ 4172389B57E3372C38B3DB88 /* Exceptions for "visionOSTestbed" folder in "visionOSTestbed" target */ = { ++ 60F7C00A2EB0000000A1B2C3 /* Exceptions for "watchOSTestbed" folder in "watchOSTestbed" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( -+ "visionOSTestbed-Info.plist", ++ "watchOSTestbed-Info.plist", + ); -+ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; ++ target = 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = { ++ 60F7C00B2EB0000000A1B2C3 /* watchOSTestbed */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( -+ 4172389B57E3372C38B3DB88 /* Exceptions for "visionOSTestbed" folder in "visionOSTestbed" target */, ++ 60F7C00A2EB0000000A1B2C3 /* Exceptions for "watchOSTestbed" folder in "watchOSTestbed" target */, + ); + explicitFolders = ( + app, + app_packages, + ); -+ path = visionOSTestbed; ++ path = watchOSTestbed; + sourceTree = ""; + }; -+ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { ++ 60F7C00C2EB0000000A1B2C3 /* TestbedTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = TestbedTests; + sourceTree = ""; @@ -2026,41 +9206,41 @@ index 00000000000..d3ca84bd32c +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ -+ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { ++ 60F7C00D2EB0000000A1B2C3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( -+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ 60F7C0022EB0000000A1B2C3 /* Python{{abiflags}}.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; -+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { ++ 60F7C00E2EB0000000A1B2C3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( -+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ 60F7C0012EB0000000A1B2C3 /* Python{{abiflags}}.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ -+ 607A66092B0EFA380010BFC8 = { ++ 60F7C00F2EB0000000A1B2C3 = { + isa = PBXGroup; + children = ( -+ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */, -+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */, -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, -+ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, -+ 607A66132B0EFA380010BFC8 /* Products */, ++ 60F7C0062EB0000000A1B2C3 /* watchOSTestbed.xctestplan */, ++ 60F7C0072EB0000000A1B2C3 /* Python{{abiflags}}.xcframework */, ++ 60F7C00B2EB0000000A1B2C3 /* watchOSTestbed */, ++ 60F7C00C2EB0000000A1B2C3 /* TestbedTests */, ++ 60F7C0102EB0000000A1B2C3 /* Products */, + ); + sourceTree = ""; + }; -+ 607A66132B0EFA380010BFC8 /* Products */ = { ++ 60F7C0102EB0000000A1B2C3 /* Products */ = { + isa = PBXGroup; + children = ( -+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */, -+ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */, ++ 60F7C0082EB0000000A1B2C3 /* watchOSTestbed.app */, ++ 60F7C0092EB0000000A1B2C3 /* TestbedTests.xctest */, + ); + name = Products; + sourceTree = ""; @@ -2068,102 +9248,100 @@ index 00000000000..d3ca84bd32c +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ -+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */ = { ++ 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */ = { + isa = PBXNativeTarget; -+ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */; ++ buildConfigurationList = 60F7C0212EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "watchOSTestbed" */; + buildPhases = ( -+ 607A660E2B0EFA380010BFC8 /* Sources */, -+ 607A660F2B0EFA380010BFC8 /* Frameworks */, -+ 607A66102B0EFA380010BFC8 /* Resources */, -+ 607A66552B0F061D0010BFC8 /* Process Python libraries */, -+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, ++ 60F7C0172EB0000000A1B2C3 /* Sources */, ++ 60F7C00D2EB0000000A1B2C3 /* Frameworks */, ++ 60F7C0142EB0000000A1B2C3 /* Resources */, ++ 60F7C0162EB0000000A1B2C3 /* Process Python libraries */, ++ 60F7C0052EB0000000A1B2C3 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, ++ 60F7C00B2EB0000000A1B2C3 /* watchOSTestbed */, + ); -+ name = visionOSTestbed; ++ name = watchOSTestbed; + packageProductDependencies = ( + ); -+ productName = iOSTestbed; -+ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */; ++ productName = watchOSTestbed; ++ productReference = 60F7C0082EB0000000A1B2C3 /* watchOSTestbed.app */; + productType = "com.apple.product-type.application"; + }; -+ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */ = { ++ 60F7C0122EB0000000A1B2C3 /* TestbedTests */ = { + isa = PBXNativeTarget; -+ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildConfigurationList = 60F7C0222EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "TestbedTests" */; + buildPhases = ( -+ 607A66292B0EFA3A0010BFC8 /* Sources */, -+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, -+ 607A662B2B0EFA3A0010BFC8 /* Resources */, -+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, ++ 60F7C0182EB0000000A1B2C3 /* Sources */, ++ 60F7C00E2EB0000000A1B2C3 /* Frameworks */, ++ 60F7C0152EB0000000A1B2C3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, ++ 60F7C0192EB0000000A1B2C3 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( -+ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ 60F7C00C2EB0000000A1B2C3 /* TestbedTests */, + ); + name = TestbedTests; + packageProductDependencies = ( + ); -+ productName = iOSTestbedTests; -+ productReference = 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */; ++ productName = TestbedTests; ++ productReference = 60F7C0092EB0000000A1B2C3 /* TestbedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ -+ 607A660A2B0EFA380010BFC8 /* Project object */ = { ++ 60F7C0132EB0000000A1B2C3 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; -+ LastUpgradeCheck = 1500; ++ LastUpgradeCheck = 1620; + TargetAttributes = { -+ 607A66112B0EFA380010BFC8 = { -+ CreatedOnToolsVersion = 15.0.1; ++ 60F7C0112EB0000000A1B2C3 = { ++ CreatedOnToolsVersion = 16.2; + }; -+ 607A662C2B0EFA3A0010BFC8 = { -+ CreatedOnToolsVersion = 15.0.1; -+ TestTargetID = 607A66112B0EFA380010BFC8; ++ 60F7C0122EB0000000A1B2C3 = { ++ CreatedOnToolsVersion = 16.2; ++ TestTargetID = 60F7C0112EB0000000A1B2C3; + }; + }; + }; -+ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */; -+ compatibilityVersion = "Xcode 14.0"; ++ buildConfigurationList = 60F7C0202EB0000000A1B2C3 /* Build configuration list for PBXProject "watchOSTestbed" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); -+ mainGroup = 607A66092B0EFA380010BFC8; ++ mainGroup = 60F7C00F2EB0000000A1B2C3; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; -+ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; ++ productRefGroup = 60F7C0102EB0000000A1B2C3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( -+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */, -+ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */, ++ 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */, ++ 60F7C0122EB0000000A1B2C3 /* TestbedTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ -+ 607A66102B0EFA380010BFC8 /* Resources */ = { ++ 60F7C0142EB0000000A1B2C3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; -+ 607A662B2B0EFA3A0010BFC8 /* Resources */ = { ++ 60F7C0152EB0000000A1B2C3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( @@ -2173,7 +9351,7 @@ index 00000000000..d3ca84bd32c +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ -+ 607A66552B0F061D0010BFC8 /* Process Python libraries */ = { ++ 60F7C0162EB0000000A1B2C3 /* Process Python libraries */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; @@ -2190,20 +9368,20 @@ index 00000000000..d3ca84bd32c + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; -+ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python{{abiflags}}.xcframework/build/utils.sh\n\ninstall_python Python{{abiflags}}.xcframework app app_packages\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ -+ 607A660E2B0EFA380010BFC8 /* Sources */ = { ++ 60F7C0172EB0000000A1B2C3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; -+ 607A66292B0EFA3A0010BFC8 /* Sources */ = { ++ 60F7C0182EB0000000A1B2C3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( @@ -2213,19 +9391,18 @@ index 00000000000..d3ca84bd32c +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ 60F7C0192EB0000000A1B2C3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; -+ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; -+ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; ++ target = 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */; ++ targetProxy = 60F7C0042EB0000000A1B2C3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ -+ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { ++ 60F7C01A2EB0000000A1B2C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -2247,7 +9424,7 @@ index 00000000000..d3ca84bd32c + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -2258,7 +9435,8 @@ index 00000000000..d3ca84bd32c + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; @@ -2273,20 +9451,20 @@ index 00000000000..d3ca84bd32c + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; -+ SDKROOT = xros; ++ SDKROOT = watchos; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Debug; + }; -+ 607A66402B0EFA3A0010BFC8 /* Release */ = { ++ 60F7C01B2EB0000000A1B2C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -2308,7 +9486,7 @@ index 00000000000..d3ca84bd32c + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -2319,7 +9497,9 @@ index 00000000000..d3ca84bd32c + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2328,171 +9508,123 @@ index 00000000000..d3ca84bd32c + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python{{abiflags}}.framework/Headers"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; -+ SDKROOT = xros; ++ SDKROOT = watchos; + VALIDATE_PRODUCT = YES; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Release; + }; -+ 607A66422B0EFA3A0010BFC8 /* Debug */ = { ++ 60F7C01C2EB0000000A1B2C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = ""; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; -+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; -+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; -+ INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "watchOSTestbed/watchOSTestbed-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); -+ MARKETING_VERSION = 3.13.0a1; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.watchOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 7; -+ XROS_DEPLOYMENT_TARGET = 2.0; ++ TARGETED_DEVICE_FAMILY = 4; + }; + name = Debug; + }; -+ 607A66432B0EFA3A0010BFC8 /* Release */ = { ++ 60F7C01D2EB0000000A1B2C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = ""; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; -+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; -+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; -+ INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "watchOSTestbed/watchOSTestbed-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); -+ MARKETING_VERSION = 3.13.0a1; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.watchOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 7; -+ XROS_DEPLOYMENT_TARGET = 2.0; ++ TARGETED_DEVICE_FAMILY = 4; + }; + name = Release; + }; -+ 607A66452B0EFA3A0010BFC8 /* Debug */ = { ++ 60F7C01E2EB0000000A1B2C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = 3HEZE76D99; -+ GENERATE_INFOPLIST_FILE = YES; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 7; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ TARGETED_DEVICE_FAMILY = 4; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/watchOSTestbed"; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Debug; + }; -+ 607A66462B0EFA3A0010BFC8 /* Release */ = { ++ 60F7C01F2EB0000000A1B2C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 7; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ TARGETED_DEVICE_FAMILY = 4; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/watchOSTestbed"; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ -+ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = { ++ 60F7C0202EB0000000A1B2C3 /* Build configuration list for PBXProject "watchOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( -+ 607A663F2B0EFA3A0010BFC8 /* Debug */, -+ 607A66402B0EFA3A0010BFC8 /* Release */, ++ 60F7C01A2EB0000000A1B2C3 /* Debug */, ++ 60F7C01B2EB0000000A1B2C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; -+ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = { ++ 60F7C0212EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "watchOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( -+ 607A66422B0EFA3A0010BFC8 /* Debug */, -+ 607A66432B0EFA3A0010BFC8 /* Release */, ++ 60F7C01C2EB0000000A1B2C3 /* Debug */, ++ 60F7C01D2EB0000000A1B2C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; -+ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ 60F7C0222EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( -+ 607A66452B0EFA3A0010BFC8 /* Debug */, -+ 607A66462B0EFA3A0010BFC8 /* Release */, ++ 60F7C01E2EB0000000A1B2C3 /* Debug */, ++ 60F7C01F2EB0000000A1B2C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; -+ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; ++ rootObject = 60F7C0132EB0000000A1B2C3 /* Project object */; +} -diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme +diff --git a/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/xcshareddata/xcschemes/watchOSTestbed.xcscheme b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/xcshareddata/xcschemes/watchOSTestbed.xcscheme new file mode 100644 -index 00000000000..b8397ea7cf1 +index 00000000000..96689416946 --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme ++++ b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/xcshareddata/xcschemes/watchOSTestbed.xcscheme @@ -0,0 +1,97 @@ + + + ++ BlueprintIdentifier = "60F7C0112EB0000000A1B2C3" ++ BuildableName = "watchOSTestbed.app" ++ BlueprintName = "watchOSTestbed" ++ ReferencedContainer = "container:watchOSTestbed.xcodeproj"> + + + @@ -2527,7 +9659,7 @@ index 00000000000..b8397ea7cf1 + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -2537,10 +9669,10 @@ index 00000000000..b8397ea7cf1 + parallelizable = "YES"> + ++ ReferencedContainer = "container:watchOSTestbed.xcodeproj"> + + + @@ -2559,10 +9691,10 @@ index 00000000000..b8397ea7cf1 + runnableDebuggingMode = "0"> + ++ BlueprintIdentifier = "60F7C0112EB0000000A1B2C3" ++ BuildableName = "watchOSTestbed.app" ++ BlueprintName = "watchOSTestbed" ++ ReferencedContainer = "container:watchOSTestbed.xcodeproj"> + + + @@ -2576,10 +9708,10 @@ index 00000000000..b8397ea7cf1 + runnableDebuggingMode = "0"> + ++ BlueprintIdentifier = "60F7C0112EB0000000A1B2C3" ++ BuildableName = "watchOSTestbed.app" ++ BlueprintName = "watchOSTestbed" ++ ReferencedContainer = "container:watchOSTestbed.xcodeproj"> + + + @@ -2591,16 +9723,16 @@ index 00000000000..b8397ea7cf1 + revealArchiveInOrganizer = "YES"> + + -diff --git a/Platforms/Apple/testbed/visionOSTestbed.xctestplan b/Platforms/Apple/testbed/visionOSTestbed.xctestplan +diff --git a/Platforms/Apple/testbed/watchOSTestbed.xctestplan b/Platforms/Apple/testbed/watchOSTestbed.xctestplan new file mode 100644 -index 00000000000..73a2be8c22a +index 00000000000..f83ff3a5595 --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed.xctestplan ++++ b/Platforms/Apple/testbed/watchOSTestbed.xctestplan @@ -0,0 +1,46 @@ +{ + "configurations" : [ + { -+ "id" : "C17FA044-0B70-48CA-AFF8-BC252081002F", ++ "id" : "8E3B1C52-4F0A-4D7E-9B61-2C5A7E0D9F14", + "name" : "Test Scheme Action", + "options" : { + @@ -2626,54 +9758,52 @@ index 00000000000..73a2be8c22a + } + ], + "targetForVariableExpansion" : { -+ "containerPath" : "container:visionOSTestbed.xcodeproj", -+ "identifier" : "607A66112B0EFA380010BFC8", -+ "name" : "visionOSTestbed" ++ "containerPath" : "container:watchOSTestbed.xcodeproj", ++ "identifier" : "60F7C0112EB0000000A1B2C3", ++ "name" : "watchOSTestbed" + } + }, + "testTargets" : [ + { + "parallelizable" : false, + "target" : { -+ "containerPath" : "container:visionOSTestbed.xcodeproj", -+ "identifier" : "607A662C2B0EFA3A0010BFC8", ++ "containerPath" : "container:watchOSTestbed.xcodeproj", ++ "identifier" : "60F7C0122EB0000000A1B2C3", + "name" : "TestbedTests" + } + } + ], + "version" : 1 +} -\ No newline at end of file -diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h +diff --git a/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.h new file mode 100644 -index 00000000000..8fe7ec8e64e +index 00000000000..cc7359048ae --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h ++++ b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.h @@ -0,0 +1,11 @@ +// +// AppDelegate.h -+// visionOSTestbed ++// watchOSTestbed +// + -+#import ++#import + -+@interface AppDelegate : UIResponder ++@interface AppDelegate : NSObject + + +@end -diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m +diff --git a/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.m new file mode 100644 -index 00000000000..f46c65a3834 +index 00000000000..3390d2efc87 --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m -@@ -0,0 +1,24 @@ ++++ b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.m +@@ -0,0 +1,21 @@ +// +// AppDelegate.m -+// visionOSTestbed ++// watchOSTestbed +// + +#import "AppDelegate.h" -+#import "SceneDelegate.h" + +@interface AppDelegate () + @@ -2681,147 +9811,52 @@ index 00000000000..f46c65a3834 + +@implementation AppDelegate + -+- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { -+ UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; -+ configuration.delegateClass = [SceneDelegate class]; -+ return configuration; -+} -+ -+- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { -+} -+ -+@end -diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json -new file mode 100644 -index 00000000000..eb878970081 ---- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json -@@ -0,0 +1,11 @@ -+{ -+ "colors" : [ -+ { -+ "idiom" : "universal" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json -new file mode 100644 -index 00000000000..13613e3ee1a ---- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json -@@ -0,0 +1,13 @@ -+{ -+ "images" : [ -+ { -+ "idiom" : "universal", -+ "platform" : "ios", -+ "size" : "1024x1024" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json -new file mode 100644 -index 00000000000..73c00596a7f ---- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json -@@ -0,0 +1,6 @@ -+{ -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h -new file mode 100644 -index 00000000000..264aacadca1 ---- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h -@@ -0,0 +1,12 @@ -+// -+// SceneDelegate.h -+// iOSTestbed -+// -+ -+#import -+ -+@interface SceneDelegate : UIResponder -+ -+@property (strong, nonatomic) UIWindow *window; -+ -+@end -diff --git a/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m -new file mode 100644 -index 00000000000..e82e447985d ---- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m -@@ -0,0 +1,21 @@ -+// -+// SceneDelegate.m -+// iOSTestbed -+// -+ -+#import "SceneDelegate.h" -+ -+@interface SceneDelegate () -+ -+@end -+ -+@implementation SceneDelegate -+ -+- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { -+ UIWindowScene *windowScene = (UIWindowScene *)scene; -+ self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; -+ self.window.rootViewController = [[UIViewController alloc] init]; -+ [self.window makeKeyAndVisible]; ++// The testbed has no storyboard (Interface.plist), so WatchKit requires the ++// delegate to provide a root interface controller class, or the app crashes ++// on launch. ++- (Class)applicationRootInterfaceControllerClass { ++ return [WKInterfaceController class]; +} + +@end -diff --git a/Platforms/Apple/testbed/visionOSTestbed/app/README b/Platforms/Apple/testbed/visionOSTestbed/app/README +diff --git a/Platforms/Apple/testbed/watchOSTestbed/app/README b/Platforms/Apple/testbed/watchOSTestbed/app/README new file mode 100644 -index 00000000000..af22c685f87 +index 00000000000..46c0e8e2a29 --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/app/README ++++ b/Platforms/Apple/testbed/watchOSTestbed/app/README @@ -0,0 +1,7 @@ +This folder can contain any Python application code. + +During the build, any binary modules found in this folder will be processed into -+iOS Framework form. ++Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH, and will be the +working directory for the test suite. -diff --git a/Platforms/Apple/testbed/visionOSTestbed/app_packages/README b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README +diff --git a/Platforms/Apple/testbed/watchOSTestbed/app_packages/README b/Platforms/Apple/testbed/watchOSTestbed/app_packages/README new file mode 100644 -index 00000000000..42d7fdeb813 +index 00000000000..02c2beccfbd --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README ++++ b/Platforms/Apple/testbed/watchOSTestbed/app_packages/README @@ -0,0 +1,7 @@ +This folder can be a target for installing any Python dependencies needed by the +test suite. + +During the build, any binary modules found in this folder will be processed into -+iOS Framework form. ++Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH. -diff --git a/Platforms/Apple/testbed/visionOSTestbed/main.m b/Platforms/Apple/testbed/visionOSTestbed/main.m +diff --git a/Platforms/Apple/testbed/watchOSTestbed/main.m b/Platforms/Apple/testbed/watchOSTestbed/main.m new file mode 100644 -index 00000000000..2bb491f25c8 +index 00000000000..b0e43f1a06f --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/main.m ++++ b/Platforms/Apple/testbed/watchOSTestbed/main.m @@ -0,0 +1,16 @@ +// +// main.m -+// visionOSTestbed ++// watchOSTestbed +// + -+#import ++#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { @@ -2829,15 +9864,15 @@ index 00000000000..2bb491f25c8 + @autoreleasepool { + appDelegateClassName = NSStringFromClass([AppDelegate class]); + -+ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ return WKApplicationMain(argc, argv, appDelegateClassName); + } +} -diff --git a/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist +diff --git a/Platforms/Apple/testbed/watchOSTestbed/watchOSTestbed-Info.plist b/Platforms/Apple/testbed/watchOSTestbed/watchOSTestbed-Info.plist new file mode 100644 -index 00000000000..11a8230d1d2 +index 00000000000..4f25642a8e2 --- /dev/null -+++ b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist -@@ -0,0 +1,66 @@ ++++ b/Platforms/Apple/testbed/watchOSTestbed/watchOSTestbed-Info.plist +@@ -0,0 +1,30 @@ + + + @@ -2849,7 +9884,7 @@ index 00000000000..11a8230d1d2 + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier -+ org.python.visionOSTestbed ++ org.python.watchOSTestbed + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName @@ -2862,165 +9897,49 @@ index 00000000000..11a8230d1d2 + ???? + CFBundleVersion + 1 -+ TestArgs -+ -+ test -+ -uall -+ --single-process -+ --rerun -+ -W -+ -+ UIApplicationSceneManifest -+ -+ UIApplicationSupportsMultipleScenes -+ -+ UISceneConfigurations -+ -+ UIWindowSceneSessionRoleApplication -+ -+ -+ UISceneDelegateClassName -+ SceneDelegate -+ UISceneConfigurationName -+ Default Configuration -+ -+ -+ -+ -+ UIRequiresFullScreen ++ WKApplication ++ ++ WKWatchOnly + -+ UISupportedInterfaceOrientations -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ UISupportedInterfaceOrientations~ipad -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationPortraitUpsideDown -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ + -+ -diff --git a/Platforms/Apple/tvOS/README.rst b/Platforms/Apple/tvOS/README.rst -new file mode 100644 -index 00000000000..1f793252caf ---- /dev/null -+++ b/Platforms/Apple/tvOS/README.rst -@@ -0,0 +1,108 @@ -+===================== -+Python on tvOS README -+===================== -+ -+:Authors: -+ Russell Keith-Magee (2023-11) -+ -+This document provides a quick overview of some tvOS specific features in the -+Python distribution. -+ -+Compilers for building on tvOS -+============================== -+ -+Building for tvOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode, on the -+most recently released macOS. -+ -+tvOS specific arguments to configure -+=================================== -+ -+* ``--enable-framework[=DIR]`` -+ -+ This argument specifies the location where the Python.framework will -+ be installed. -+ -+* ``--with-framework-name=NAME`` -+ -+ Specify the name for the python framework, defaults to ``Python``. -+ -+ -+Building and using Python on tvOS -+================================= -+ -+ABIs and Architectures -+---------------------- -+ -+tvOS apps can be deployed on physical devices, and on the tvOS simulator. -+Although the API used on these devices is identical, the ABI is different - you -+need to link against different libraries for an tvOS device build -+(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses -+the XCframework format to allow specifying a single dependency that supports -+multiple ABIs. An XCframework is a wrapper around multiple ABI-specific -+frameworks. -+ -+tvOS can also support different CPU architectures within each ABI. At present, -+there is only a single support ed architecture on physical devices - ARM64. -+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -+Silicon machines), and x86_64 (for running on older Intel-based machines.) -+ -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. -+ -+How do I build Python for tvOS? -+------------------------------- -+ -+The Python build system will build a ``Python.framework`` that supports a -+*single* ABI with a *single* architecture. If you want to use Python in an tvOS -+project, you need to: -+ -+1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; -+2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; -+3. Merge the "fat" frameworks for each ABI into a single XCframework. -+ -+tvOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. -+ -+The build also requires the use of cross-compilation. The commands for building -+Python for tvOS will look somethign like:: -+ -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-tvos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++ +diff --git a/Platforms/Apple/tvOS/README.md b/Platforms/Apple/tvOS/README.md +new file mode 100644 +index 00000000000..24e1cb239ba +--- /dev/null ++++ b/Platforms/Apple/tvOS/README.md +@@ -0,0 +1,28 @@ ++# Python on tvOS README + -+In this invocation: ++This document provides a quick overview of some tvOS specific features in the ++Python distribution. + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++Python on tvOS is largely the same as Python on iOS; most of the instructions in ++the [iOS README](../iOS/README.md) can be used for tvOS builds, substituting: + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: ++- `tvOS` as the identifier for `Platforms/Apple` invocations; e.g., ++ - `python Platforms/Apple build tvOS` ++ - `python Platforms/Apple ci tvOS` ++ - `python Platforms/Apple test tvOS` + -+ - ``aarch64-apple-tvos`` for ARM64 tvOS devices. -+ - ``aarch64-apple-tvos-simulator`` for the tvOS simulator running on Apple -+ Silicon devices. -+ - ``x86_64-apple-tvos-simulator`` for the tvOS simulator running on Intel -+ devices. ++- tvOS resources are stored in the `Platforms/Apple/tvOS/Resources` directory. + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: ++- The tvOS SDK version is controlled with the `TVOS_SDK_VERSION` environment ++ variable. + -+ - ``aarch64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. ++- The minimum supported tvOS SDK version is controlled with the ++ `TVOS_DEPLOYMENT_TARGET` environment variable. + -+* ``/path/to/python.exe`` is the path to a Python binary on the machine that -+ will be running the compiler. This is needed because the Python compilation -+ process involves running some Python code. On a normal desktop build of -+ Python, you can compile a python interpreter and then use that interpreter to -+ run Python code. However, the binaries produced for tvOS won't run on macOS, so -+ you need to provide an external Python interpreter. This interpreter must be -+ the version as the Python that is being compiled. ++- When referencing platforms, the tvOS compiler triples are: ++ - `arm64-apple-tvos` for AppleTV devices ++ - `arm64-apple-tvos-simulator` for AppleTV simulators on ARM64 hardware ++ - `x86_64-apple-tvos-simulator` for AppleTV simulators on x86-64 hardware + -+Using a framework-based Python on tvOS -+====================================== ++- The tvOS testbed Xcode template project is stored in ++ `Platforms/Apple/testbed/tvOSTestbed`. diff --git a/Platforms/Apple/tvOS/Resources/Info.plist.in b/Platforms/Apple/tvOS/Resources/Info.plist.in new file mode 100644 -index 00000000000..ab3050804b8 +index 00000000000..b913504d69e --- /dev/null +++ b/Platforms/Apple/tvOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ @@ -3031,7 +9950,7 @@ index 00000000000..ab3050804b8 + CFBundleDevelopmentRegion + en + CFBundleExecutable -+ Python ++ @PYTHONFRAMEWORK@ + CFBundleGetInfoString + Python Runtime and Library + CFBundleIdentifier @@ -3039,7 +9958,7 @@ index 00000000000..ab3050804b8 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName -+ Python ++ @PYTHONFRAMEWORK@ + CFBundlePackageType + FMWK + CFBundleShortVersionString @@ -3191,9 +10110,43 @@ index 00000000000..4acff2c6051 +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif +diff --git a/Platforms/Apple/visionOS/README.md b/Platforms/Apple/visionOS/README.md +new file mode 100644 +index 00000000000..f6bdb2608e2 +--- /dev/null ++++ b/Platforms/Apple/visionOS/README.md +@@ -0,0 +1,28 @@ ++# Python on visionOS README ++ ++This document provides a quick overview of some visionOS specific features in the ++Python distribution. ++ ++Python on visionOS is largely the same as Python on iOS; most of the instructions in ++the [iOS README](../iOS/README.md) can be used for visionOS builds, substituting: ++ ++- `visionOS` as the identifier for `Platforms/Apple` invocations; e.g., ++ - `python Platforms/Apple build visionOS` ++ - `python Platforms/Apple ci visionOS` ++ - `python Platforms/Apple test visionOS` ++ ++- visionOS resources are stored in the `Platforms/Apple/visionOS/Resources` ++ directory. ++ ++- The visionOS SDK version is controlled with the `XROS_SDK_VERSION` environment ++ variable. ++ ++- The minimum supported visionOS SDK version is controlled with the ++ `XROS_DEPLOYMENT_TARGET` environment variable. ++ ++- When referencing platforms, the visionOS compiler triples are: ++ - `arm64-apple-visionOS` for Apple Vision devices ++ - `arm64-apple-visionOS-simulator` for Apple Vision simulators ++ ++- The visionOS testbed Xcode template project is stored in ++ `Platforms/Apple/testbed/visionOSTestbed`. diff --git a/Platforms/Apple/visionOS/Resources/Info.plist.in b/Platforms/Apple/visionOS/Resources/Info.plist.in new file mode 100644 -index 00000000000..2679440da67 +index 00000000000..c3dd63ced8d --- /dev/null +++ b/Platforms/Apple/visionOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ @@ -3204,7 +10157,7 @@ index 00000000000..2679440da67 + CFBundleDevelopmentRegion + en + CFBundleExecutable -+ Python ++ @PYTHONFRAMEWORK@ + CFBundleGetInfoString + Python Runtime and Library + CFBundleIdentifier @@ -3212,7 +10165,7 @@ index 00000000000..2679440da67 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName -+ Python ++ @PYTHONFRAMEWORK@ + CFBundlePackageType + FMWK + CFBundleShortVersionString @@ -3320,123 +10273,44 @@ index 00000000000..2a433078400 +#ifdef __arm64__ +#include "pyconfig-arm64.h" +#endif -diff --git a/Platforms/Apple/watchOS/README.rst b/Platforms/Apple/watchOS/README.rst +diff --git a/Platforms/Apple/watchOS/README.md b/Platforms/Apple/watchOS/README.md new file mode 100644 -index 00000000000..35221478452 +index 00000000000..44f23e327cb --- /dev/null -+++ b/Platforms/Apple/watchOS/README.rst -@@ -0,0 +1,108 @@ -+======================== -+Python on watchOS README -+======================== -+ -+:Authors: -+ Russell Keith-Magee (2023-11) ++++ b/Platforms/Apple/watchOS/README.md +@@ -0,0 +1,29 @@ ++# Python on watchOS README + +This document provides a quick overview of some watchOS specific features in the +Python distribution. + -+Compilers for building on watchOS -+================================= -+ -+Building for watchOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode, on the -+most recently released macOS. -+ -+watchOS specific arguments to configure -+======================================= -+ -+* ``--enable-framework[=DIR]`` -+ -+ This argument specifies the location where the Python.framework will -+ be installed. -+ -+* ``--with-framework-name=NAME`` -+ -+ Specify the name for the python framework, defaults to ``Python``. -+ -+ -+Building and using Python on watchOS -+==================================== -+ -+ABIs and Architectures -+---------------------- -+ -+watchOS apps can be deployed on physical devices, and on the watchOS simulator. -+Although the API used on these devices is identical, the ABI is different - you -+need to link against different libraries for an watchOS device build -+(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the -+XCframework format to allow specifying a single dependency that supports -+multiple ABIs. An XCframework is a wrapper around multiple ABI-specific -+frameworks. -+ -+watchOS can also support different CPU architectures within each ABI. At present, -+there is only a single support ed architecture on physical devices - ARM64. -+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -+Silicon machines), and x86_64 (for running on older Intel-based machines.) -+ -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. -+ -+How do I build Python for watchOS? -+------------------------------- -+ -+The Python build system will build a ``Python.framework`` that supports a -+*single* ABI with a *single* architecture. If you want to use Python in an watchOS -+project, you need to: -+ -+1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; -+2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; -+3. Merge the "fat" frameworks for each ABI into a single XCframework. ++Python on watchOS is largely the same as Python on iOS; most of the instructions in ++the [iOS README](../iOS/README.md) can be used for watchOS builds, substituting: + -+watchOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. ++- `watchOS` as the identifier for `Platforms/Apple` invocations; e.g., ++ - `python Platforms/Apple build watchOS` ++ - `python Platforms/Apple ci watchOS` ++ - `python Platforms/Apple test watchOS` + -+The build also requires the use of cross-compilation. The commands for building -+Python for watchOS will look somethign like:: ++- watchOS resources are stored in the `Platforms/Apple/watchOS/Resources` ++ directory + -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-watchos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++- The watchOS SDK version is controlled with the `WATCHOS_SDK_VERSION` environment ++ variable. + -+In this invocation: ++- The minimum supported watchOS SDK version is controlled with the ++ `WATCHOS_DEPLOYMENT_TARGET` environment variable. + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++- When referencing platforms, the watchOS compiler triples are: ++ - `arm64_32-apple-watchos` for Apple Watch devices ++ - `arm64-apple-watchos-simulator` for Apple Watch simulators on ARM64 hardware ++ - `x86_64-apple-watchos-simulator` for Apple Watch simulators on x86_64 hardware + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: -+ -+ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices. -+ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple -+ Silicon devices. -+ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel -+ devices. -+ -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: -+ -+ - ``aarch64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. -+ -+* ``/path/to/python.exe`` is the path to a Python binary on the machine that -+ will be running the compiler. This is needed because the Python compilation -+ process involves running some Python code. On a normal desktop build of -+ Python, you can compile a python interpreter and then use that interpreter to -+ run Python code. However, the binaries produced for watchOS won't run on macOS, so -+ you need to provide an external Python interpreter. This interpreter must be -+ the version as the Python that is being compiled. -+ -+Using a framework-based Python on watchOS -+====================================== ++- The watchOS testbed Xcode template project is stored in ++ `Platforms/Apple/testbed/watchOSTestbed`. diff --git a/Platforms/Apple/watchOS/Resources/Info.plist.in b/Platforms/Apple/watchOS/Resources/Info.plist.in new file mode 100644 -index 00000000000..e83ddfd2a43 +index 00000000000..b2f64710d70 --- /dev/null +++ b/Platforms/Apple/watchOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ @@ -3447,7 +10321,7 @@ index 00000000000..e83ddfd2a43 + CFBundleDevelopmentRegion + en + CFBundleExecutable -+ Python ++ @PYTHONFRAMEWORK@ + CFBundleGetInfoString + Python Runtime and Library + CFBundleIdentifier @@ -3455,7 +10329,7 @@ index 00000000000..e83ddfd2a43 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName -+ Python ++ @PYTHONFRAMEWORK@ + CFBundlePackageType + FMWK + CFBundleShortVersionString @@ -3611,6 +10485,1189 @@ index 00000000000..f842b987b2e +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif +diff --git a/Platforms/WASI/README.md b/Platforms/WASI/README.md +index a62efbda536..e56645b4db2 100644 +--- a/Platforms/WASI/README.md ++++ b/Platforms/WASI/README.md +@@ -279,10 +279,16 @@ + + ### C code + +-WASI SDK defines several built-in macros. You can dump a full list of built-ins +-with ``/path/to/wasi-sdk/bin/clang -dM -E - < /dev/null``. ++The following built-in macros identify WebAssembly targets and environments. ++For WASI SDK, you can dump a full list of built-ins with ++`/path/to/wasi-sdk/bin/clang -dM -E - < /dev/null`. + +-* WebAssembly ``__wasm__`` (also ``__wasm``) +-* wasm32 ``__wasm32__`` (also ``__wasm32``) +-* wasm64 ``__wasm64__`` +-* WASI ``__wasi__`` ++#### Shared with Emscripten ++ ++- WebAssembly target: `__wasm__` (also `__wasm`) ++- wasm32 target: `__wasm32__` (also `__wasm32`) ++- wasm64 target: `__wasm64__` ++ ++#### Unique to WASI ++ ++- WASI environment: `__wasi__` +diff --git a/Platforms/emscripten/browser_test/package-lock.json b/Platforms/emscripten/browser_test/package-lock.json +index 786226eb38b..2ae84ddf800 100644 +--- a/Platforms/emscripten/browser_test/package-lock.json ++++ b/Platforms/emscripten/browser_test/package-lock.json +@@ -9,8 +9,8 @@ + "version": "1.0.0", + "license": "ISC", + "dependencies": { +- "@playwright/test": "^1.61.1", +- "@types/node": "^24.12.0", ++ "@playwright/test": "^1.63.0", ++ "@types/node": "^26.6.3", + "get-port-cli": "^3.0.0", + "http-server": "^14.1.1", + "playwright": "^1.61.1" +@@ -40,18 +40,18 @@ + } + }, + "node_modules/@playwright/test": { +- "version": "1.61.1", +- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", +- "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", ++ "version": "1.63.0", ++ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", ++ "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "license": "Apache-2.0", + "dependencies": { +- "playwright": "1.61.1" ++ "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { +- "node": ">=18" ++ "node": ">=20" + } + }, + "node_modules/@types/minimist": { +@@ -61,12 +61,12 @@ + "license": "MIT" + }, + "node_modules/@types/node": { +- "version": "24.12.0", +- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", +- "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", ++ "version": "26.6.3", ++ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", ++ "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "license": "MIT", + "dependencies": { +- "undici-types": "~7.16.0" ++ "undici-types": "~8.9.0" + } + }, + "node_modules/@types/normalize-package-data": { +@@ -377,20 +377,6 @@ + } + } + }, +- "node_modules/fsevents": { +- "version": "2.3.2", +- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", +- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", +- "hasInstallScript": true, +- "license": "MIT", +- "optional": true, +- "os": [ +- "darwin" +- ], +- "engines": { +- "node": "^8.16.0 || ^10.6.0 || >=11.0.0" +- } +- }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", +@@ -892,33 +878,30 @@ + "license": "ISC" + }, + "node_modules/playwright": { +- "version": "1.61.1", +- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", +- "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", ++ "version": "1.63.0", ++ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", ++ "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "license": "Apache-2.0", + "dependencies": { +- "playwright-core": "1.61.1" ++ "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { +- "node": ">=18" +- }, +- "optionalDependencies": { +- "fsevents": "2.3.2" ++ "node": ">=20" + } + }, + "node_modules/playwright-core": { +- "version": "1.61.1", +- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", +- "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", ++ "version": "1.63.0", ++ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", ++ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { +- "node": ">=18" ++ "node": ">=20" + } + }, + "node_modules/portfinder": { +@@ -1201,9 +1184,9 @@ + } + }, + "node_modules/undici-types": { +- "version": "7.16.0", +- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", +- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", ++ "version": "8.9.0", ++ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", ++ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/union": { +diff --git a/Platforms/emscripten/browser_test/package.json b/Platforms/emscripten/browser_test/package.json +index fc8e1ce3397..b62b4c657a7 100644 +--- a/Platforms/emscripten/browser_test/package.json ++++ b/Platforms/emscripten/browser_test/package.json +@@ -10,8 +10,8 @@ + "license": "ISC", + "description": "", + "dependencies": { +- "@playwright/test": "^1.61.1", +- "@types/node": "^24.12.0", ++ "@playwright/test": "^1.63.0", ++ "@types/node": "^26.6.3", + "get-port-cli": "^3.0.0", + "http-server": "^14.1.1", + "playwright": "^1.61.1" +diff --git a/Programs/_testembed.c b/Programs/_testembed.c +index 71bc37194e4..89b12ce7733 100644 +--- a/Programs/_testembed.c ++++ b/Programs/_testembed.c +@@ -268,6 +268,23 @@ + return 0; + } + ++ ++static int test_interpreter_clear_and_delete(void) ++{ ++ assert(main_argc == 3); ++ _testembed_initialize(); ++ if (PyRun_SimpleString(main_argv[2]) < 0) { ++ return 1; ++ } ++ ++ PyInterpreterState *interp = PyInterpreterState_Get(); ++ PyInterpreterState_Clear(interp); ++ PyThreadState_Swap(NULL); ++ PyInterpreterState_Delete(interp); ++ // The interpreter has already been deleted, so don't call Py_FinalizeEx(). ++ return 0; ++} ++ + /**************************************************************************** + * Test the Py_Initialize(Ex) convenience/compatibility wrappers + ***************************************************************************/ +@@ -3001,6 +3018,7 @@ + static struct TestCase TestCases[] = { + // Python initialization + {"test_repeated_init_exec", test_repeated_init_exec}, ++ {"test_interpreter_clear_and_delete", test_interpreter_clear_and_delete}, + {"test_repeated_simple_init", test_repeated_simple_init}, + {"test_forced_io_encoding", test_forced_io_encoding}, + {"test_import_in_subinterpreters", test_import_in_subinterpreters}, +diff --git a/Python/_warnings.c b/Python/_warnings.c +index 586cec195fa..8fecff060fd 100644 +--- a/Python/_warnings.c ++++ b/Python/_warnings.c +@@ -9,6 +9,7 @@ + #include "pycore_traceback.h" // _Py_DisplaySourceLine() + #include "pycore_tuple.h" // _PyTuple_FromPair + #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() ++#include "pycore_warnings.h" // _PyWarnings_AfterFork() + + #include + #include "clinic/_warnings.c.h" +@@ -276,6 +277,23 @@ + return PyMutex_IsLocked(&st->lock.mutex); + } + ++#ifdef HAVE_FORK ++void ++_PyWarnings_BeforeFork(PyInterpreterState *interp) ++{ ++ WarningsState *st = warnings_get_state(interp); ++ st->lock_held_at_fork = _PyRecursiveMutex_IsLockedByCurrentThread(&st->lock); ++} ++ ++void ++_PyWarnings_AfterFork(PyInterpreterState *interp) ++{ ++ WarningsState *st = warnings_get_state(interp); ++ _PyRecursiveMutex_at_fork_reinit(&st->lock, st->lock_held_at_fork); ++ st->lock_held_at_fork = false; ++} ++#endif ++ + static PyObject * + get_warnings_context(PyInterpreterState *interp) + { +diff --git a/Python/ceval.c b/Python/ceval.c +index acf012c44a5..a6a21f0ac4c 100644 +--- a/Python/ceval.c ++++ b/Python/ceval.c +@@ -142,11 +142,14 @@ + *top = (uintptr_t)stack_addr; + *base = ((uintptr_t)stack_addr) - stack_size; + #else +- /// XXX musl supports HAVE_PTHRED_GETATTR_NP, but the resulting stack size +- /// (on alpine at least) is much smaller than expected and imposes undue limits +- /// compared to the old stack size estimation. (We assume musl is not glibc.) ++ // musl supports HAVE_PTHREAD_GETATTR_NP, but the resulting stack size ++ // (on alpine at least) is much smaller than expected and imposes undue ++ // limits compared to the old stack size estimation. musl doesn't give ++ // us a way to detect it, so we have to assume any non-glibc, ++ // non-Android Linux is musl and mustn't rely on pthread_getattr_np. + # if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && \ +- !defined(__NetBSD__) && (defined(__GLIBC__) || !defined(__linux__)) ++ !defined(__NetBSD__) && \ ++ (defined(__GLIBC__) || defined(__ANDROID__) || !defined(__linux__)) + size_t stack_size, guard_size; + void *stack_addr; + pthread_attr_t attr; +@@ -1704,7 +1707,7 @@ + static int + positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co, + Py_ssize_t kwcount, PyObject* kwnames, +- PyObject *qualname) ++ PyFunctionObject *func) + { + int posonly_conflicts = 0; + PyObject* posonly_names = PyList_New(0); +@@ -1751,7 +1754,8 @@ + _PyErr_Format(tstate, PyExc_TypeError, + "%U() got some positional-only arguments passed" + " as keyword arguments: '%U'", +- qualname, error_names); ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname), ++ error_names); + Py_DECREF(error_names); + goto fail; + } +@@ -1847,8 +1851,8 @@ + + if (keyword == NULL || !PyUnicode_Check(keyword)) { + _PyErr_Format(tstate, PyExc_TypeError, +- "%U() keywords must be strings", +- func->func_qualname); ++ "%U() keywords must be strings", ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname)); + goto kw_fail; + } + +@@ -1880,7 +1884,7 @@ + if (co->co_posonlyargcount + && positional_only_passed_as_keyword(tstate, co, + kwcount, kwnames, +- func->func_qualname)) ++ func)) + { + goto kw_fail; + } +@@ -1901,15 +1905,16 @@ + } + } + ++ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname); + if (suggestion_keyword) { + _PyErr_Format(tstate, PyExc_TypeError, + "%U() got an unexpected keyword argument '%S'. Did you mean '%S'?", +- func->func_qualname, keyword, suggestion_keyword); ++ qualname, keyword, suggestion_keyword); + Py_DECREF(suggestion_keyword); + } else { + _PyErr_Format(tstate, PyExc_TypeError, + "%U() got an unexpected keyword argument '%S'", +- func->func_qualname, keyword); ++ qualname, keyword); + } + + goto kw_fail; +@@ -1930,8 +1935,9 @@ + kw_found: + if (PyStackRef_AsPyObjectBorrow(localsplus[j]) != NULL) { + _PyErr_Format(tstate, PyExc_TypeError, +- "%U() got multiple values for argument '%S'", +- func->func_qualname, keyword); ++ "%U() got multiple values for argument '%S'", ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname), ++ keyword); + goto kw_fail; + } + localsplus[j] = value_stackref; +@@ -1942,7 +1948,8 @@ + if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) { + int missing_self_hint = suggest_missing_self(func, co, first_argument, argcount); + too_many_positional(tstate, co, argcount, func->func_defaults, localsplus, +- func->func_qualname, missing_self_hint); ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname), ++ missing_self_hint); + goto fail_post_args; + } + +@@ -1958,7 +1965,7 @@ + } + if (missing) { + missing_arguments(tstate, co, missing, defcount, localsplus, +- func->func_qualname); ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname)); + goto fail_post_args; + } + if (n > m) +@@ -1997,7 +2004,7 @@ + } + if (missing) { + missing_arguments(tstate, co, missing, -1, localsplus, +- func->func_qualname); ++ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname)); + goto fail_post_args; + } + } +@@ -2953,14 +2960,40 @@ + return result; + } + ++static const char * ++functionobject_get_name(PyFunctionObject *f) ++{ ++#ifdef Py_GIL_DISABLED ++ for (;;) { ++ PyObject *name = Py_NewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_name)); ++ // UTF-8 conversion may detach while acquiring the string's lock. ++ // This could result in func_name being freed if func_name was ++ // recently replaced using _PyObject_XSetRefDelayed(). The returned ++ // char* is only valid until the next quiescent point. ++ const char *result = PyUnicode_AsUTF8(name); ++ if (result == NULL) { ++ Py_DECREF(name); ++ return NULL; ++ } ++ int unchanged = name == FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_name); ++ Py_DECREF(name); ++ if (unchanged) { ++ return result; ++ } ++ } ++#else ++ return PyUnicode_AsUTF8(f->func_name); ++#endif ++} + + const char * + PyEval_GetFuncName(PyObject *func) + { + if (PyMethod_Check(func)) + return PyEval_GetFuncName(PyMethod_GET_FUNCTION(func)); +- else if (PyFunction_Check(func)) +- return PyUnicode_AsUTF8(((PyFunctionObject*)func)->func_name); ++ else if (PyFunction_Check(func)) { ++ return functionobject_get_name((PyFunctionObject *)func); ++ } + else if (PyCFunction_Check(func)) + return ((PyCFunctionObject*)func)->m_ml->ml_name; + else +diff --git a/Python/ceval.h b/Python/ceval.h +index 0437ab85c5a..00db67265a4 100644 +--- a/Python/ceval.h ++++ b/Python/ceval.h +@@ -257,9 +257,9 @@ + } + PyFunctionObject *f = (PyFunctionObject *)fobj; + PyObject *exc = PyErr_GetRaisedException(); +- PyObject *name = f->func_qualname; ++ PyObject *name = Py_XNewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_qualname)); + if (name == NULL) { +- name = f->func_name; ++ name = Py_XNewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_name)); + } + printf("\nResuming frame"); + if (name) { +@@ -267,12 +267,15 @@ + if (PyObject_Print(name, stdout, 0) < 0) { + PyErr_Clear(); + } ++ Py_DECREF(name); + } +- if (f->func_module) { ++ PyObject *module = Py_XNewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_module)); ++ if (module) { + printf(" in module "); +- if (PyObject_Print(f->func_module, stdout, 0) < 0) { ++ if (PyObject_Print(module, stdout, 0) < 0) { + PyErr_Clear(); + } ++ Py_DECREF(module); + } + printf("\n"); + fflush(stdout); +diff --git a/Python/flowgraph.c b/Python/flowgraph.c +index a5138d1a1fa..17489ae5bfb 100644 +--- a/Python/flowgraph.c ++++ b/Python/flowgraph.c +@@ -188,7 +188,8 @@ + } + + static int +-basicblock_addop(basicblock *b, int opcode, int oparg, location loc) ++basicblock_addop(basicblock *b, int opcode, int oparg, location loc, ++ basicblock *except) + { + assert(IS_WITHIN_OPCODE_RANGE(opcode)); + assert(!IS_ASSEMBLER_OPCODE(opcode)); +@@ -203,15 +204,16 @@ + i->i_opcode = opcode; + i->i_oparg = oparg; + i->i_loc = loc; ++ i->i_except = except; + // memory is already zero initialized + assert(i->i_target == NULL); +- assert(i->i_except == NULL); + + return SUCCESS; + } + + static int +-basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc) ++basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc, ++ basicblock *except) + { + cfg_instr *last = basicblock_last_instr(b); + if (last && is_jump(last)) { +@@ -219,7 +221,7 @@ + } + + RETURN_IF_ERROR( +- basicblock_addop(b, opcode, target->b_label.id, loc)); ++ basicblock_addop(b, opcode, target->b_label.id, loc, except)); + last = basicblock_last_instr(b); + assert(last && last->i_opcode == opcode); + last->i_target = target; +@@ -493,7 +495,7 @@ + _PyCfgBuilder_Addop(cfg_builder *g, int opcode, int oparg, location loc) + { + RETURN_IF_ERROR(cfg_builder_maybe_start_new_block(g)); +- return basicblock_addop(g->g_curblock, opcode, oparg, loc); ++ return basicblock_addop(g->g_curblock, opcode, oparg, loc, NULL); + } + + +@@ -554,7 +556,7 @@ + bool is_forward = last->i_target->b_visited == 0; + if (is_forward) { + RETURN_IF_ERROR( +- basicblock_addop(b, NOT_TAKEN, 0, last->i_loc)); ++ basicblock_addop(b, NOT_TAKEN, 0, last->i_loc, last->i_except)); + return SUCCESS; + } + +@@ -583,9 +585,11 @@ + return ERROR; + } + RETURN_IF_ERROR( +- basicblock_addop(backwards_jump, NOT_TAKEN, 0, last->i_loc)); ++ basicblock_addop(backwards_jump, NOT_TAKEN, 0, last->i_loc, ++ last->i_except)); + RETURN_IF_ERROR( +- basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc)); ++ basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc, ++ last->i_except)); + backwards_jump->b_startdepth = target->b_startdepth; + last->i_opcode = reversed_opcode; + last->i_target = b->b_next; +@@ -1293,7 +1297,8 @@ + + RETURN_IF_ERROR( + basicblock_add_jump( +- bb, opcode, target->i_target, target->i_loc)); ++ bb, opcode, target->i_target, target->i_loc, ++ target->i_except)); + + return true; + } +@@ -3547,7 +3552,7 @@ + b->b_next->b_label.id = next_lbl++; + } + basicblock_addop(explicit_jump, JUMP_NO_INTERRUPT, b->b_next->b_label.id, +- NO_LOCATION); ++ NO_LOCATION, NULL); + explicit_jump->b_cold = 1; + explicit_jump->b_next = b->b_next; + explicit_jump->b_predecessors = 1; +diff --git a/Python/getcompiler.c b/Python/getcompiler.c +index baa03679d79..cc56ad8c895 100644 +--- a/Python/getcompiler.c ++++ b/Python/getcompiler.c +@@ -3,7 +3,6 @@ + + #include "Python.h" + +-// PC/pyconfig.h defines _Py_COMPILER + #ifdef _Py_COMPILER + # define COMPILER _Py_COMPILER + #endif +@@ -13,14 +12,14 @@ + // Note the __clang__ conditional has to come before the __GNUC__ one because + // clang pretends to be GCC. + #if defined(__clang__) +-# define COMPILER "[Clang " __clang_version__ "]" ++#define COMPILER "[Clang " __clang_version__ "]" + #elif defined(__GNUC__) +-# define COMPILER "[GCC " __VERSION__ "]" ++#define COMPILER "[GCC " __VERSION__ "]" + // Generic fallbacks. + #elif defined(__cplusplus) +-# define COMPILER "[C++]" ++#define COMPILER "[C++]" + #else +-# define COMPILER "[C]" ++#define COMPILER "[C]" + #endif + + #endif /* !COMPILER */ +diff --git a/Python/getversion.c b/Python/getversion.c +index 5c05b841de6..8d8bc6ea700 100644 +--- a/Python/getversion.c ++++ b/Python/getversion.c +@@ -2,77 +2,32 @@ + /* Return the full version string. */ + + #include "Python.h" +-#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY() + ++#include "patchlevel.h" + + static int initialized = 0; +-// Use the smallest possible buffer: it's only needed if malloc() fails *and* +-// Py_GetVersion() is called before Py_Initialize(). The buffer should be big +-// enough to store "3.16.0a0 " string. +-static char static_version[20]; +-static char *heap_version = NULL; ++static char version[300]; + +- +-PyStatus +-_Py_GetVersion_Init(void) ++void _Py_InitVersion(void) + { + if (initialized) { +- return _PyStatus_OK(); ++ return; + } +- ++ initialized = 1; + #ifdef Py_GIL_DISABLED +- const char *format = "%s free-threading build (%s) %s"; +- size_t format_len = strlen(" free-threading build () "); ++ const char *buildinfo_format = "%.80s free-threading build (%.80s) %.80s"; + #else +- const char *format = "%s (%s) %s"; +- size_t format_len = strlen(" () "); ++ const char *buildinfo_format = "%.80s (%.80s) %.80s"; + #endif +- const char *version_str = PY_VERSION; +- const char *buildinfo = Py_GetBuildInfo(); +- const char *compiler = Py_GetCompiler(); +- // +1 for the trailing NUL byte +- size_t len = (format_len + strlen(version_str) + strlen(buildinfo) +- + strlen(compiler) + 1); +- +- // Always format the static version +- PyOS_snprintf(static_version, sizeof(static_version), format, +- version_str, buildinfo, compiler); +- +- heap_version = malloc(len); +- if (heap_version == NULL) { +- // If malloc() failed, don't set initialized to 1, so next +- // Py_GetVersion() will try again to allocate memory. +- return _PyStatus_NO_MEMORY(); +- } +- +- PyOS_snprintf(heap_version, len, format, +- version_str, buildinfo, compiler); +- initialized = 1; +- return _PyStatus_OK(); +-} +- +-void +-_Py_GetVersion_Fini(void) +-{ +- if (heap_version) { +- free(heap_version); +- heap_version = NULL; +- } ++ PyOS_snprintf(version, sizeof(version), buildinfo_format, ++ PY_VERSION, Py_GetBuildInfo(), Py_GetCompiler()); + } + + const char * + Py_GetVersion(void) + { +- PyStatus status = _Py_GetVersion_Init(); +- // Ignore error: Py_GetVersion() API cannot report error +- (void)status; +- +- if (heap_version) { +- return heap_version; +- } +- else { +- return static_version; +- } ++ _Py_InitVersion(); ++ return version; + } + + // Export the Python hex version as a constant. +diff --git a/Python/lock.c b/Python/lock.c +index b669e3dcdbc..a2d47eea64a 100644 +--- a/Python/lock.c ++++ b/Python/lock.c +@@ -453,6 +453,25 @@ + return 0; + } + ++#ifdef HAVE_FORK ++// Reinitialize the mutex in the child process after fork(). The caller must ++// record before fork() whether the forking thread owns the mutex, since the ++// thread ident can change in the child (gh-126688). If it does, the child ++// keeps the mutex with the same recursion level; otherwise the owner and any ++// waiters no longer exist in the child and the mutex is reset. ++void ++_PyRecursiveMutex_at_fork_reinit(_PyRecursiveMutex *m, int owned) ++{ ++ if (owned) { ++ m->mutex = (PyMutex){._bits = _Py_LOCKED}; ++ _Py_atomic_store_ullong_relaxed(&m->thread, PyThread_get_thread_ident_ex()); ++ } ++ else { ++ memset(m, 0, sizeof(*m)); ++ } ++} ++#endif ++ + #define _Py_WRITE_LOCKED 1 + #define _PyRWMutex_READER_SHIFT 2 + #define _Py_RWMUTEX_MAX_READERS (UINTPTR_MAX >> _PyRWMutex_READER_SHIFT) +diff --git a/Python/marshal.c b/Python/marshal.c +index 7109e5c56f4..e8ce70f7414 100644 +--- a/Python/marshal.c ++++ b/Python/marshal.c +@@ -44,7 +44,7 @@ + */ + #if defined(MS_WINDOWS) + # define MAX_MARSHAL_STACK_DEPTH 1000 +-#elif defined(__wasi__) ++#elif defined(__wasm__) + # define MAX_MARSHAL_STACK_DEPTH 1500 + // TARGET_OS_IPHONE covers any non-macOS Apple platform. + // It won't be defined on older macOS SDKs +diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c +index ba1b94c4798..8888f523559 100644 +--- a/Python/pylifecycle.c ++++ b/Python/pylifecycle.c +@@ -559,11 +559,7 @@ + */ + _PyRuntimeState_SetFinalizing(runtime, NULL); + +- status = _Py_GetVersion_Init(); +- if (_PyStatus_EXCEPTION(status)) { +- return status; +- } +- ++ _Py_InitVersion(); + _Py_DumpTraceback_Init(); + + status = _Py_HashRandomization_Init(config); +diff --git a/Python/pystate.c b/Python/pystate.c +index 97323ea2ba2..f9debd1e20e 100644 +--- a/Python/pystate.c ++++ b/Python/pystate.c +@@ -1837,13 +1837,6 @@ + Py_CLEAR(((_PyThreadStateImpl *)tstate)->asyncio_running_task); + + +- PyMutex_Lock(&tstate->interp->asyncio_tasks_lock); +- // merge any lingering tasks from thread state to interpreter's +- // tasks list +- llist_concat(&tstate->interp->asyncio_tasks_head, +- &((_PyThreadStateImpl *)tstate)->asyncio_tasks_head); +- PyMutex_Unlock(&tstate->interp->asyncio_tasks_lock); +- + Py_CLEAR(tstate->dict); + Py_CLEAR(tstate->async_exc); + +@@ -1919,11 +1912,30 @@ + static void + decrement_stoptheworld_countdown(struct _stoptheworld_state *stw); + +-/* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */ ++// `release_gil` releases the current thread's GIL. For states already removed ++// by _PyThreadState_RemoveExcept(), `is_unlinked` limits cleanup to task transfer. + static void +-tstate_delete_common(PyThreadState *tstate, int release_gil) ++tstate_delete_common(PyThreadState *tstate, int release_gil, int is_unlinked) + { + assert(tstate->_status.cleared && !tstate->_status.finalized); ++ assert(!is_unlinked || !release_gil); ++ ++ _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; ++ // Finalizers may register tasks during or after PyThreadState_Clear(). ++ // Transfer them before normal unlinking or GIL release: cross-thread task ++ // removal uses stop-the-world or the GIL, not asyncio_tasks_lock. ++ if (!llist_empty(&tstate_impl->asyncio_tasks_head)) { ++ // Serialize transfers from concurrently exiting threads. ++ PyMutex_Lock(&tstate->interp->asyncio_tasks_lock); ++ llist_concat(&tstate->interp->asyncio_tasks_head, ++ &tstate_impl->asyncio_tasks_head); ++ PyMutex_Unlock(&tstate->interp->asyncio_tasks_lock); ++ } ++ assert(llist_empty(&tstate_impl->asyncio_tasks_head)); ++ if (is_unlinked) { ++ return; ++ } ++ + tstate_verify_not_active(tstate); + assert(!_PyThreadState_IsRunningMain(tstate)); + +@@ -1959,7 +1971,6 @@ + + #if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED) + // Add our portion of the total refcount to the interpreter's total. +- _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; + tstate->interp->object_state.reftotal += tstate_impl->reftotal; + tstate_impl->reftotal = 0; + assert(tstate_impl->refcounts.values == NULL); +@@ -2006,7 +2017,7 @@ + */ + while ((tstate = interp->threads.head) != NULL) { + tstate_verify_not_active(tstate); +- tstate_delete_common(tstate, 0); ++ tstate_delete_common(tstate, /*release_gil=*/0, /*is_unlinked=*/0); + free_threadstate((_PyThreadStateImpl *)tstate); + } + } +@@ -2017,7 +2028,7 @@ + { + _Py_EnsureTstateNotNULL(tstate); + tstate_verify_not_active(tstate); +- tstate_delete_common(tstate, 0); ++ tstate_delete_common(tstate, /*release_gil=*/0, /*is_unlinked=*/0); + free_threadstate((_PyThreadStateImpl *)tstate); + } + +@@ -2033,7 +2044,7 @@ + _PyStats_Detach((_PyThreadStateImpl *)tstate); + #endif + current_fast_clear(tstate->interp->runtime); +- tstate_delete_common(tstate, 1); // release GIL as part of call ++ tstate_delete_common(tstate, /*release_gil=*/1, /*is_unlinked=*/0); + free_threadstate((_PyThreadStateImpl *)tstate); + } + +@@ -2103,6 +2114,7 @@ + for (p = list; p; p = next) { + next = p->next; + PyThreadState_Clear(p); ++ tstate_delete_common(p, /*release_gil=*/0, /*is_unlinked=*/1); + if (is_after_fork) { + free_threadstate((_PyThreadStateImpl *)p); + } +diff --git a/Python/pythonrun.c b/Python/pythonrun.c +index 049eaea4994..5e026a93d5c 100644 +--- a/Python/pythonrun.c ++++ b/Python/pythonrun.c +@@ -1285,7 +1285,7 @@ + goto done; + } + +- mod = _PyParser_ASTFromString(str, name, start, flags, arena, module); ++ mod = _PyParser_ASTFromString(str, name, start, flags, arena, module, NULL); + Py_XDECREF(module); + + if (mod != NULL) { +@@ -1569,32 +1569,43 @@ + return NULL; + } + +- PyCodeObject *co; +- mod_ty mod; + PyArena *arena = _PyArena_New(); + if (arena == NULL) + return NULL; + +- mod = _PyParser_ASTFromString(str, filename, start, flags, arena, module); ++ PyObject *result = NULL; ++ PyObject *incomplete = NULL; ++ mod_ty mod = _PyParser_ASTFromString(str, filename, start, flags, arena, ++ module, &incomplete); + if (mod == NULL) { +- _PyArena_Free(arena); +- return NULL; ++ goto done; + } + if (flags && (flags->cf_flags & PyCF_ONLY_AST)) { + int syntax_check_only = ((flags->cf_flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST); /* unoptiomized AST */ + if (_PyCompile_AstPreprocess(mod, filename, flags, optimize, arena, + syntax_check_only, module) < 0) + { +- _PyArena_Free(arena); +- return NULL; ++ goto done; + } +- PyObject *result = PyAST_mod2obj(mod); ++ if (incomplete != NULL) { ++ goto done; ++ } ++ result = PyAST_mod2obj(mod); + _PyArena_Free(arena); + return result; + } +- co = _PyAST_Compile(mod, filename, flags, optimize, arena, module); ++ result = (PyObject *)_PyAST_Compile(mod, filename, flags, optimize, arena, module); ++ ++done: + _PyArena_Free(arena); +- return (PyObject *)co; ++ if (incomplete != NULL && !PyErr_Occurred()) { ++ Py_CLEAR(result); ++ PyErr_SetRaisedException(incomplete); ++ } ++ else { ++ Py_XDECREF(incomplete); ++ } ++ return result; + } + + PyObject * +diff --git a/Python/symtable.c b/Python/symtable.c +index b97778ada20..7f945dad198 100644 +--- a/Python/symtable.c ++++ b/Python/symtable.c +@@ -3271,7 +3271,7 @@ + if (arena == NULL) + return NULL; + +- mod = _PyParser_ASTFromString(str, filename, start, flags, arena, module); ++ mod = _PyParser_ASTFromString(str, filename, start, flags, arena, module, NULL); + if (mod == NULL) { + _PyArena_Free(arena); + return NULL; +diff --git a/Python/sysmodule.c b/Python/sysmodule.c +index afc90d9bf5d..a284c8322ea 100644 +--- a/Python/sysmodule.c ++++ b/Python/sysmodule.c +@@ -64,6 +64,10 @@ + # include + #endif + ++#ifdef __wasi__ ++# include ++#endif ++ + #ifdef HAVE_FCNTL_H + # include + #endif +@@ -3837,6 +3841,49 @@ + } + + ++#ifdef __wasi__ ++ ++static PyObject * ++make_wasi_info(void) ++{ ++#if defined(__wasip1__) ++ const char *wasi_version = "p1"; ++#elif defined(__wasip2__) ++ const char *wasi_version = "p2"; ++#elif defined(__wasip3__) ++ const char *wasi_version = "p3"; ++#else ++# error "Unknown WASI target version" ++#endif ++ ++ // The SDK version is only defined for SDK builds of wasi-libc. ++#ifdef __wasi_sdk_version__ ++ const char *wasi_sdk_version = __wasi_sdk_version__; ++#else ++# error "Unknown WASI SDK version" ++#endif ++ ++#ifdef __wasi_cooperative_threads__ ++ PyObject *cooperative_threads = Py_True; ++#else ++ PyObject *cooperative_threads = Py_False; ++#endif ++ ++ PyObject *wasi_info = Py_BuildValue( ++ "{s:s,s:s,s:O}", ++ "wasi_version", wasi_version, ++ "wasi_sdk_version", wasi_sdk_version, ++ "cooperative_threads", cooperative_threads); ++ if (wasi_info == NULL) { ++ return NULL; ++ } ++ PyObject *ns = _PyNamespace_New(wasi_info); ++ Py_DECREF(wasi_info); ++ return ns; ++} ++ ++#endif // __wasi__ ++ + #ifdef __EMSCRIPTEN__ + + PyDoc_STRVAR(emscripten_info__doc__, +@@ -4084,6 +4131,10 @@ + + SET_SYS("abi_info", make_abi_info()); + ++#ifdef __wasi__ ++ SET_SYS("_wasi_info", make_wasi_info()); ++#endif ++ + /* initialize asyncgen_hooks */ + if (_PyStructSequence_InitBuiltin(interp, &AsyncGenHooksType, + &asyncgen_hooks_desc) < 0) +diff --git a/README.rst b/README.rst +index e3e5cb9f321..d4bea5edcac 100644 +--- a/README.rst ++++ b/README.rst +@@ -1,5 +1,5 @@ +-This is Python version 3.16.0 alpha 1 +-===================================== ++This is Python version 3.16.0 alpha 1+dev ++========================================= + + .. image:: https://github.com/python/cpython/actions/workflows/build.yml/badge.svg?branch=main&event=push + :alt: CPython build status on GitHub Actions +diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py +index e62218f2e86..62ae1fd0bb1 100644 +--- a/Tools/build/check_capi_macros.py ++++ b/Tools/build/check_capi_macros.py +@@ -93,6 +93,7 @@ + if os.path.basename(filename) not in EXCLUDE_HEADERS] + files.extend(glob.glob(os.path.join(include_dir, 'cpython', '*.h'))) + files.append(os.path.join(SRC_DIR, 'pyconfig.h.in')) ++ files.append(os.path.join(SRC_DIR, 'PC/pyconfig.h')) + names = [] # list of (name: str, filename: str, lineno: int, cause: str) + for filename in files: + parse_file(filename, names) +diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt +index 877c8d72e72..238c29b7039 100644 +--- a/Tools/build/check_capi_macros_ignored.txt ++++ b/Tools/build/check_capi_macros_ignored.txt +@@ -38,6 +38,8 @@ + CURSES_ERROR_FORMAT + CURSES_ERROR_MUST_CALL_FORMAT + CURSES_ERROR_VERBOSE_FORMAT ++DONT_HAVE_SIG_ALARM ++DONT_HAVE_SIG_PAUSE + DOUBLE_IS_BIG_ENDIAN_IEEE754 + DOUBLE_IS_LITTLE_ENDIAN_IEEE754 + ENABLE_IPV6 +@@ -301,6 +303,7 @@ + HAVE_INET_NTOA + HAVE_INET_PTON + HAVE_INITGROUPS ++HAVE_INTPTR_T + HAVE_INTTYPES_H + HAVE_IO_H + HAVE_IPA_PURE_CONST_BUG +@@ -318,6 +321,7 @@ + HAVE_LIBIEEE + HAVE_LIBINTL_H + HAVE_LIBSENDFILE ++HAVE_LIBSOCKET + HAVE_LIBSQLITE3 + HAVE_LIBUTIL_H + HAVE_LINK +@@ -436,9 +440,11 @@ + HAVE_PTSNAME + HAVE_PTSNAME_R + HAVE_PTY_H ++HAVE_PUTENV + HAVE_PWRITE + HAVE_PWRITEV + HAVE_PWRITEV2 ++HAVE_PY_SSIZE_T + HAVE_READLINE_READLINE_H + HAVE_READLINK + HAVE_READLINKAT +@@ -519,10 +525,12 @@ + HAVE_STATX + HAVE_STAT_TV_NSEC + HAVE_STAT_TV_NSEC2 ++HAVE_STDDEF_H + HAVE_STDINT_H + HAVE_STDIO_H + HAVE_STDLIB_H + HAVE_STD_ATOMIC ++HAVE_STRERROR + HAVE_STRFTIME + HAVE_STRINGS_H + HAVE_STRING_H +@@ -593,6 +601,7 @@ + HAVE_SYS_TYPES_H + HAVE_SYS_UIO_H + HAVE_SYS_UN_H ++HAVE_SYS_UTIME_H + HAVE_SYS_UTSNAME_H + HAVE_SYS_WAIT_H + HAVE_SYS_XATTR_H +@@ -612,6 +621,7 @@ + HAVE_TRUNCATE + HAVE_TTYNAME_R + HAVE_TZNAME ++HAVE_UINTPTR_T + HAVE_UMASK + HAVE_UNAME + HAVE_UNISTD_H +@@ -641,9 +651,11 @@ + HAVE_WCSCOLL + HAVE_WCSFTIME + HAVE_WCSXFRM ++HAVE_WINDOWS_CONSOLE_IO + HAVE_WMEMCMP + HAVE_WORKING_TZSET + HAVE_WRITEV ++HAVE_X509_VERIFY_PARAM_SET1_HOST + HAVE_ZDICT_H + HAVE_ZLIB_COPY + HAVE_ZLIB_H +@@ -667,10 +679,20 @@ + METH_STACKLESS + METH_STATIC + METH_VARARGS ++MS_COREDLL ++MS_WIN32 ++MS_WIN64 ++MS_WINDOWS ++MS_WINDOWS_APP ++MS_WINDOWS_DESKTOP ++MS_WINDOWS_GAMES ++MS_WINDOWS_SYSTEM + MVWDELCH_IS_EXPRESSION + NATIVE_TSS_KEY_T + NCURSES_OPAQUE + NOWAIT_LOCK ++NTDDI_VERSION ++NT_THREADS + PACKAGE_BUGREPORT + PACKAGE_NAME + PACKAGE_STRING +@@ -678,6 +700,7 @@ + PACKAGE_URL + PACKAGE_VERSION + PDC_NCMOUSE ++PLATFORM + POSIX_SEMAPHORES_NOT_ENABLED + PTHREAD_KEYS_MAX + PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT +@@ -688,6 +711,7 @@ + SIZEOF_DOUBLE + SIZEOF_FLOAT + SIZEOF_FPOS_T ++SIZEOF_HKEY + SIZEOF_INT + SIZEOF_LONG + SIZEOF_LONG_DOUBLE +@@ -724,10 +748,12 @@ + TIMEMODULE_LIB + TM_IN_SYS_TIME + USE_COMPUTED_GOTOS ++USE_SOCKET + USE_STACKCHECK + VA_LIST_IS_ARRAY + WAIT_LOCK + WINDOW_HAS_FLAGS ++WINVER + WITH_DECIMAL_CONTEXTVAR + WITH_DOC_STRINGS + WITH_DTRACE +@@ -740,11 +766,14 @@ + WITH_THREAD + WITH_VALGRIND + WORDS_BIGENDIAN ++WORD_BIT + X87_DOUBLE_ROUNDING + _ALL_SOURCE + _Atomic + _BOOL + _BSD_SOURCE ++_CRT_NONSTDC_NO_DEPRECATE ++_CRT_SECURE_NO_DEPRECATE + _DARWIN_C_SOURCE + _FILE_OFFSET_BITS + _GNU_SOURCE +@@ -764,10 +793,12 @@ + _REENTRANT + _SGI_MP_SOURCE + _TANDEM_SOURCE ++_W64 + _WASI_EMULATED_GETPID + _WASI_EMULATED_PROCESS_CLOCKS + _WASI_EMULATED_SIGNAL + _WCHAR_T ++_WIN32_WINNT + _XOPEN_SOURCE + _XOPEN_SOURCE_EXTENDED + __BSD_VISIBLE +diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv +index 5529a52b0c4..db9f74fdbc5 100644 +--- a/Tools/c-analyzer/cpython/ignored.tsv ++++ b/Tools/c-analyzer/cpython/ignored.tsv +@@ -48,8 +48,7 @@ + Modules/getbuildinfo.c - buildinfo - + Modules/getbuildinfo.c - initialized - + Python/getversion.c - initialized - +-Python/getversion.c - static_version - +-Python/getversion.c - heap_version - ++Python/getversion.c - version - + + ## public C-API - set during first init + Python/bootstrap_hash.c - _Py_HashSecret_Initialized - +diff --git a/Tools/peg_generator/peg_extension/peg_extension.c b/Tools/peg_generator/peg_extension/peg_extension.c +index 2fec5b05129..83dd668a069 100644 +--- a/Tools/peg_generator/peg_extension/peg_extension.c ++++ b/Tools/peg_generator/peg_extension/peg_extension.c +@@ -93,7 +93,7 @@ + + PyCompilerFlags flags = _PyCompilerFlags_INIT; + mod_ty res = _PyPegen_run_parser_from_string(the_string, Py_file_input, filename_ob, +- &flags, arena, NULL); ++ &flags, arena, NULL, NULL); + if (res == NULL) { + goto error; + } diff --git a/config.sub b/config.sub index 1bb6a05dc11..49febd56a37 100755 --- a/config.sub @@ -3634,7 +11691,7 @@ index 1bb6a05dc11..49febd56a37 100755 none--*) # None (no kernel, i.e. freestanding / bare metal), diff --git a/configure b/configure -index a68a98c5a4d..a34d4986bbe 100755 +index c777d8117c6..163679d43e8 100755 --- a/configure +++ b/configure @@ -994,6 +994,10 @@ @@ -3648,7 +11705,20 @@ index a68a98c5a4d..a34d4986bbe 100755 IPHONEOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET -@@ -4161,6 +4165,15 @@ +@@ -3694,9 +3698,9 @@ + fi + if test $HAS_GIT = found + then +- GITVERSION="git --git-dir \$(srcdir)/.git rev-parse --short HEAD" +- GITTAG="git --git-dir \$(srcdir)/.git describe --all --always --dirty" +- GITBRANCH="git --git-dir \$(srcdir)/.git name-rev --name-only HEAD" ++ GITVERSION="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) rev-parse --short HEAD" ++ GITTAG="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) describe --all --always --dirty" ++ GITBRANCH="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) name-rev --name-only HEAD" + else + GITVERSION="" + GITTAG="" +@@ -4188,6 +4192,15 @@ *-apple-ios*) ac_sys_system=iOS ;; @@ -3664,7 +11734,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *-*-darwin*) ac_sys_system=Darwin ;; -@@ -4245,7 +4258,7 @@ +@@ -4272,7 +4285,7 @@ # On cross-compile builds, configure will look for a host-specific compiler by # prepending the user-provided host triple to the required binary name. # @@ -3673,7 +11743,7 @@ index a68a98c5a4d..a34d4986bbe 100755 # which isn't a binary that exists, and isn't very convenient, as it contains the # iOS version. As the default cross-compiler name won't exist, configure falls # back to gcc, which *definitely* won't work. We're providing wrapper scripts for -@@ -4260,6 +4273,17 @@ +@@ -4287,14 +4300,36 @@ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; @@ -3689,9 +11759,11 @@ index a68a98c5a4d..a34d4986bbe 100755 + aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; + x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; *) - esac +- esac ++ esac fi -@@ -4268,6 +4292,17 @@ + if test -z "$CC"; then + case "$host" in aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; @@ -3709,7 +11781,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *) esac fi -@@ -4276,6 +4311,17 @@ +@@ -4303,6 +4338,17 @@ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; @@ -3727,7 +11799,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *) esac fi -@@ -4284,6 +4330,17 @@ +@@ -4311,6 +4357,17 @@ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; @@ -3745,7 +11817,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *) esac fi -@@ -4406,8 +4463,11 @@ +@@ -4433,8 +4490,11 @@ case $enableval in yes) case $ac_sys_system in @@ -3759,17 +11831,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 esac esac -@@ -4416,6 +4476,9 @@ - no) - case $ac_sys_system in - iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; -+ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; -+ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;; -+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -4522,6 +4585,51 @@ +@@ -4545,6 +4605,51 @@ ac_config_files="$ac_config_files Platforms/Apple/iOS/Resources/Info.plist" @@ -3821,17 +11883,7 @@ index a68a98c5a4d..a34d4986bbe 100755 ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 -@@ -4533,6 +4641,9 @@ - e) - case $ac_sys_system in - iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; -+ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; -+ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;; -+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -4587,8 +4698,8 @@ +@@ -4606,8 +4711,8 @@ case "$withval" in yes) case $ac_sys_system in @@ -3842,7 +11894,7 @@ index a68a98c5a4d..a34d4986bbe 100755 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ;; *) as_fn_error $? "no default app store compliance patch available for $ac_sys_system" "$LINENO" 5 ;; -@@ -4606,8 +4717,8 @@ +@@ -4625,8 +4730,8 @@ else case e in #( e) case $ac_sys_system in @@ -3853,7 +11905,7 @@ index a68a98c5a4d..a34d4986bbe 100755 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 printf "%s\n" "applying default app store compliance patch" >&6; } -@@ -4625,6 +4736,8 @@ +@@ -4644,6 +4749,8 @@ @@ -3862,7 +11914,7 @@ index a68a98c5a4d..a34d4986bbe 100755 if test "$cross_compiling" = yes; then case "$host" in -@@ -4665,6 +4778,78 @@ +@@ -4684,6 +4791,78 @@ ;; esac ;; @@ -3925,7 +11977,7 @@ index a68a98c5a4d..a34d4986bbe 100755 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking watchOS deployment target" >&5 +printf %s "checking watchOS deployment target... " >&6; } + WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} -+ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=9.0} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5 +printf "%s\n" "$WATCHOS_DEPLOYMENT_TARGET" >&6; } + @@ -3941,7 +11993,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *-*-darwin*) case "$host_cpu" in arm*) -@@ -4755,9 +4940,15 @@ +@@ -4771,9 +4950,15 @@ define_xopen_source=no;; Darwin/[12][0-9].*) define_xopen_source=no;; @@ -3958,7 +12010,7 @@ index a68a98c5a4d..a34d4986bbe 100755 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -4820,7 +5011,14 @@ +@@ -4852,7 +5037,14 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' @@ -3974,7 +12026,7 @@ index a68a98c5a4d..a34d4986bbe 100755 # checks for alternative programs -@@ -4866,6 +5064,26 @@ +@@ -4898,6 +5090,26 @@ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ;; #( @@ -4001,7 +12053,7 @@ index a68a98c5a4d..a34d4986bbe 100755 *) : ;; esac -@@ -7235,6 +7453,12 @@ +@@ -7379,6 +7591,12 @@ MULTIARCH="" ;; #( iOS) : MULTIARCH="" ;; #( @@ -4013,8 +12065,8 @@ index a68a98c5a4d..a34d4986bbe 100755 + MULTIARCH="" ;; #( FreeBSD*) : MULTIARCH="" ;; #( - *) : -@@ -7255,7 +7479,7 @@ + OpenBSD*) : +@@ -7401,7 +7619,7 @@ printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( @@ -4023,26 +12075,7 @@ index a68a98c5a4d..a34d4986bbe 100755 SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET -@@ -7324,6 +7548,18 @@ - PY_SUPPORT_TIER=3 ;; #( - aarch64-apple-ios*/clang) : - PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-tvos*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-tvos*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-xros*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-xros*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-watchos*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ arm64_32-apple-watchos*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( - aarch64-*-linux-android/clang) : - PY_SUPPORT_TIER=3 ;; #( - x86_64-*-linux-android/clang) : -@@ -7778,7 +8014,7 @@ +@@ -7952,7 +8170,7 @@ case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; @@ -4051,16 +12084,16 @@ index a68a98c5a4d..a34d4986bbe 100755 LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; -@@ -7844,7 +8080,7 @@ +@@ -8018,7 +8236,7 @@ BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; - iOS) + iOS|tvOS|visionOS|watchOS) - LDLIBRARY='libpython$(LDVERSION).dylib' - ;; - AIX*) -@@ -14196,7 +14432,7 @@ + if test -z "$PYTHONFRAMEWORK"; then + as_fn_error $? "iOS shared builds must use --enable-framework; an iOS app can only load a signed framework, never a bare dylib" "$LINENO" 5 + fi +@@ -14445,7 +14663,7 @@ BLDSHARED="$LDSHARED" fi ;; @@ -4069,7 +12102,7 @@ index a68a98c5a4d..a34d4986bbe 100755 LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" -@@ -14329,7 +14565,7 @@ +@@ -14578,7 +14796,7 @@ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys @@ -4078,16 +12111,16 @@ index a68a98c5a4d..a34d4986bbe 100755 LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -14353,7 +14589,7 @@ +@@ -14602,7 +14820,7 @@ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" - elif test $ac_sys_system = "iOS"; then + elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS"; then - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' - fi - ;; -@@ -16182,7 +16418,7 @@ + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + + if test "$enable_framework"; then +@@ -16546,7 +16764,7 @@ ctypes_malloc_closure=yes ;; #( @@ -4096,7 +12129,7 @@ index a68a98c5a4d..a34d4986bbe 100755 ctypes_malloc_closure=yes ;; #( -@@ -20055,12 +20291,6 @@ +@@ -20575,12 +20793,6 @@ then : printf "%s\n" "#define HAVE_DUP 1" >>confdefs.h @@ -4109,7 +12142,7 @@ index a68a98c5a4d..a34d4986bbe 100755 fi ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero" if test "x$ac_cv_func_explicit_bzero" = xyes -@@ -20121,18 +20351,6 @@ +@@ -20641,18 +20853,6 @@ then : printf "%s\n" "#define HAVE_FEXECVE 1" >>confdefs.h @@ -4128,7 +12161,7 @@ index a68a98c5a4d..a34d4986bbe 100755 fi ac_fn_c_check_func "$LINENO" "fpathconf" "ac_cv_func_fpathconf" if test "x$ac_cv_func_fpathconf" = xyes -@@ -20565,24 +20783,6 @@ +@@ -21127,24 +21327,6 @@ then : printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h @@ -4153,7 +12186,7 @@ index a68a98c5a4d..a34d4986bbe 100755 fi ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" if test "x$ac_cv_func_pread" = xyes -@@ -20901,12 +21101,6 @@ +@@ -21463,12 +21645,6 @@ then : printf "%s\n" "#define HAVE_SIGACTION 1" >>confdefs.h @@ -4166,7 +12199,56 @@ index a68a98c5a4d..a34d4986bbe 100755 fi ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset" if test "x$ac_cv_func_sigfillset" = xyes -@@ -21197,18 +21391,12 @@ +@@ -21732,6 +21908,48 @@ + + + ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for freadlink" >&5 ++printf %s "checking for freadlink... " >&6; } ++if test ${ac_cv_func_freadlink+y} ++then : ++ printf %s "(cached) " >&6 ++else case e in #( ++ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext ++/* end confdefs.h. */ ++#include ++int ++main (void) ++{ ++void *x=freadlink ++ ; ++ return 0; ++} ++_ACEOF ++if ac_fn_c_try_compile "$LINENO" ++then : ++ ac_cv_func_freadlink=yes ++else case e in #( ++ e) ac_cv_func_freadlink=no ;; ++esac ++fi ++rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ++ ;; ++esac ++fi ++{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_freadlink" >&5 ++printf "%s\n" "$ac_cv_func_freadlink" >&6; } ++ if test "x$ac_cv_func_freadlink" = xyes ++then : ++ ++printf "%s\n" "#define _Py_HAVE_FREADLINK 1" >>confdefs.h ++ ++fi ++ ++ ++ ++ ++ ++ + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for strerror_r" >&5 + printf %s "checking for strerror_r... " >&6; } + if test ${ac_cv_func_strerror_r+y} +@@ -21803,12 +22021,41 @@ fi @@ -4177,57 +12259,54 @@ index a68a98c5a4d..a34d4986bbe 100755 # raise an error if used at runtime. Force these symbols off. -if test "$ac_sys_system" != "iOS" ; then - ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3" --if test "x$ac_cv_func_dup3" = xyes --then : -- printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h -- --fi --ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" ; then -+ ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" - if test "x$ac_cv_func_getentropy" = xyes - then : - printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h -@@ -21219,6 +21407,24 @@ - then : - printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h - -+fi -+ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system" -+if test "x$ac_cv_func_system" = xyes ++# In particular, dup3 and pipe2 are available as of iOS/tvOS/watchOS/visionOS 27 but were ++# incorrectly detected in older simulator versions. ++case "$ac_sys_system" in ++ iOS|tvOS|watchOS|visionOS) ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if iOS SDK is older than 27" >&5 ++printf %s "checking if iOS SDK is older than 27... " >&6; } ++ ios_sdk_version=$(xcrun --sdk iphoneos --show-sdk-version 2>/dev/null | cut -d. -f1) ++ if test -n "$ios_sdk_version" && test "$ios_sdk_version" -ge 27; then ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 ++printf "%s\n" "no" >&6; } ++ ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3" ++if test "x$ac_cv_func_dup3" = xyes +then : -+ printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h ++ printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h + +fi ++ac_fn_c_check_func "$LINENO" "pipe2" "ac_cv_func_pipe2" ++if test "x$ac_cv_func_pipe2" = xyes ++then : ++ printf "%s\n" "#define HAVE_PIPE2 1" >>confdefs.h + +fi -+# In addition, macOS introduced dup3 and pipe2 in macOS 27, late in the -+# lifetime of this version of Python. Rather than adding weaklinking -+# support for them, just continue to ignore them. -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "Darwin"; then -+ ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3" -+if test "x$ac_cv_func_dup3" = xyes -+then : -+ printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h + - fi - ac_fn_c_check_func "$LINENO" "pipe2" "ac_cv_func_pipe2" - if test "x$ac_cv_func_pipe2" = xyes -@@ -21226,10 +21432,51 @@ - printf "%s\n" "#define HAVE_PIPE2 1" >>confdefs.h ++ else ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 ++printf "%s\n" "yes" >&6; } ++ fi ++ ;; ++ *) ++ # not iOS/tvOS/watchOS/visionOS ++ ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3" + if test "x$ac_cv_func_dup3" = xyes + then : + printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h +@@ -21837,6 +22084,54 @@ + then : + printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h - fi --ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system" --if test "x$ac_cv_func_system" = xyes -+ +fi + ++ ;; ++esac ++ +# tvOS/watchOS have some additional methods that can be found, but not used. +if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then + ac_fn_c_check_func "$LINENO" "execv" "ac_cv_func_execv" +if test "x$ac_cv_func_execv" = xyes - then : -- printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h ++then : + printf "%s\n" "#define HAVE_EXECV 1" >>confdefs.h + +fi @@ -4265,10 +12344,11 @@ index a68a98c5a4d..a34d4986bbe 100755 +if test "x$ac_cv_func_sigaltstack" = xyes +then : + printf "%s\n" "#define HAVE_SIGALTSTACK 1" >>confdefs.h - ++ fi -@@ -24687,7 +24934,8 @@ + fi +@@ -25457,7 +25752,8 @@ fi # check for openpty, login_tty, and forkpty @@ -4278,7 +12358,7 @@ index a68a98c5a4d..a34d4986bbe 100755 for ac_func in openpty do : -@@ -24801,7 +25049,7 @@ +@@ -25571,7 +25867,7 @@ fi done @@ -4287,7 +12367,7 @@ index a68a98c5a4d..a34d4986bbe 100755 printf %s "checking for library containing login_tty... " >&6; } if test ${ac_cv_search_login_tty+y} then : -@@ -24984,6 +25232,7 @@ +@@ -25754,6 +26050,7 @@ fi done @@ -4295,7 +12375,7 @@ index a68a98c5a4d..a34d4986bbe 100755 # check for long file support functions ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64" -@@ -25249,10 +25498,10 @@ +@@ -26019,10 +26316,10 @@ done @@ -4308,7 +12388,7 @@ index a68a98c5a4d..a34d4986bbe 100755 then for ac_func in clock_settime -@@ -25569,7 +25818,7 @@ +@@ -26339,7 +26636,7 @@ e) if test "$cross_compiling" = yes then : @@ -4317,18 +12397,18 @@ index a68a98c5a4d..a34d4986bbe 100755 ac_cv_buggy_getaddrinfo="no" elif test "${enable_ipv6+set}" = set; then ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" -@@ -27647,8 +27896,8 @@ +@@ -28712,8 +29009,8 @@ LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" fi --# On iOS the shared libraries must be linked with the Python framework --if test "$ac_sys_system" = "iOS"; then -+# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework -+if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "visionOS" -o $ac_sys_system = "watchOS"; then +-# On iOS the shared libraries must be linked with the framework, when built +-if test "$ac_sys_system" = "iOS" && test "$enable_framework"; then ++# On iOS/tvOS/watchOS the shared libraries must be linked with the framework, when built ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS" && test "$enable_framework"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi -@@ -30767,7 +31016,7 @@ +@@ -34161,7 +34458,7 @@ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 printf "%s\n" "$as_me: checking for device files" >&6;} @@ -4337,7 +12417,7 @@ index a68a98c5a4d..a34d4986bbe 100755 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else -@@ -31321,7 +31570,7 @@ +@@ -34714,7 +35011,7 @@ with_ensurepip=no ;; #( WASI) : with_ensurepip=no ;; #( @@ -4346,7 +12426,7 @@ index a68a98c5a4d..a34d4986bbe 100755 with_ensurepip=no ;; #( *) : with_ensurepip=upgrade -@@ -32271,6 +32520,9 @@ +@@ -35719,6 +36016,9 @@ NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268 Darwin) _PYTHREAD_NAME_MAXLEN=63;; iOS) _PYTHREAD_NAME_MAXLEN=63;; @@ -4355,8 +12435,8 @@ index a68a98c5a4d..a34d4986bbe 100755 + watchOS) _PYTHREAD_NAME_MAXLEN=63;; FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 - *) _PYTHREAD_NAME_MAXLEN=;; -@@ -32302,7 +32554,7 @@ + CYGWIN*) _PYTHREAD_NAME_MAXLEN=16;; +@@ -35751,7 +36051,7 @@ ;; #( Darwin) : ;; #( @@ -4365,7 +12445,7 @@ index a68a98c5a4d..a34d4986bbe 100755 -@@ -36605,6 +36857,9 @@ +@@ -40066,6 +40366,9 @@ "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; "Platforms/Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/iOS/Resources/Info.plist" ;; @@ -4376,10 +12456,23 @@ index a68a98c5a4d..a34d4986bbe 100755 "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; diff --git a/configure.ac b/configure.ac -index ed8b53dd3a4..39fb6e82fb1 100644 +index a8760c94ac1..75ed4ff01e5 100644 --- a/configure.ac +++ b/configure.ac -@@ -339,6 +339,15 @@ +@@ -128,9 +128,9 @@ + fi + if test $HAS_GIT = found + then +- GITVERSION="git --git-dir \$(srcdir)/.git rev-parse --short HEAD" +- GITTAG="git --git-dir \$(srcdir)/.git describe --all --always --dirty" +- GITBRANCH="git --git-dir \$(srcdir)/.git name-rev --name-only HEAD" ++ GITVERSION="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) rev-parse --short HEAD" ++ GITTAG="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) describe --all --always --dirty" ++ GITBRANCH="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) name-rev --name-only HEAD" + else + GITVERSION="" + GITTAG="" +@@ -345,6 +345,15 @@ *-apple-ios*) ac_sys_system=iOS ;; @@ -4395,7 +12488,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *-*-darwin*) ac_sys_system=Darwin ;; -@@ -417,7 +426,7 @@ +@@ -423,7 +432,7 @@ # On cross-compile builds, configure will look for a host-specific compiler by # prepending the user-provided host triple to the required binary name. # @@ -4404,7 +12497,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 # which isn't a binary that exists, and isn't very convenient, as it contains the # iOS version. As the default cross-compiler name won't exist, configure falls # back to gcc, which *definitely* won't work. We're providing wrapper scripts for -@@ -432,6 +441,17 @@ +@@ -438,14 +447,36 @@ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; @@ -4420,9 +12513,11 @@ index ed8b53dd3a4..39fb6e82fb1 100644 + aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; + x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; *) - esac +- esac ++ esac fi -@@ -440,6 +460,17 @@ + if test -z "$CC"; then + case "$host" in aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; @@ -4440,7 +12535,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *) esac fi -@@ -448,6 +479,17 @@ +@@ -454,6 +485,17 @@ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; @@ -4458,7 +12553,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *) esac fi -@@ -456,6 +498,17 @@ +@@ -462,6 +504,17 @@ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; @@ -4476,7 +12571,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *) esac fi -@@ -570,8 +623,11 @@ +@@ -576,8 +629,11 @@ case $enableval in yes) case $ac_sys_system in @@ -4490,17 +12585,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *) AC_MSG_ERROR([Unknown platform for framework build]) esac esac -@@ -580,6 +636,9 @@ - no) - case $ac_sys_system in - iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; -+ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; -+ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;; -+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -682,6 +741,48 @@ +@@ -684,6 +740,48 @@ AC_CONFIG_FILES([Platforms/Apple/iOS/Resources/Info.plist]) ;; @@ -4549,17 +12634,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *) AC_MSG_ERROR([Unknown platform for framework build]) ;; -@@ -690,6 +791,9 @@ - ],[ - case $ac_sys_system in - iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; -+ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; -+ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;; -+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -742,8 +846,8 @@ +@@ -740,8 +838,8 @@ case "$withval" in yes) case $ac_sys_system in @@ -4570,7 +12645,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ;; *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;; -@@ -757,8 +861,8 @@ +@@ -755,8 +853,8 @@ esac ],[ case $ac_sys_system in @@ -4581,7 +12656,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" AC_MSG_RESULT([applying default app store compliance patch]) ;; -@@ -771,6 +875,8 @@ +@@ -769,6 +867,8 @@ ]) AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) @@ -4590,7 +12665,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 AC_SUBST([_PYTHON_HOST_PLATFORM]) if test "$cross_compiling" = yes; then case "$host" in -@@ -809,6 +915,70 @@ +@@ -807,6 +907,70 @@ ;; esac ;; @@ -4646,7 +12721,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 + # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version + AC_MSG_CHECKING([watchOS deployment target]) + WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} -+ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=9.0} + AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET]) + + case "$host_cpu" in @@ -4661,7 +12736,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 *-*-darwin*) case "$host_cpu" in arm*) -@@ -898,9 +1068,15 @@ +@@ -893,9 +1057,15 @@ define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; @@ -4678,7 +12753,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -959,8 +1135,15 @@ +@@ -968,8 +1138,15 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' @@ -4695,7 +12770,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 # checks for alternative programs -@@ -994,15 +1177,28 @@ +@@ -1003,15 +1180,28 @@ ], ) @@ -4727,7 +12802,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 ], ) -@@ -1191,6 +1387,9 @@ +@@ -1203,6 +1393,9 @@ AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], [iOS], [MULTIARCH=""], @@ -4735,9 +12810,9 @@ index ed8b53dd3a4..39fb6e82fb1 100644 + [visionOS], [MULTIARCH=""], + [watchOS], [MULTIARCH=""], [FreeBSD*], [MULTIARCH=""], + [OpenBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] - ) -@@ -1212,7 +1411,7 @@ +@@ -1225,7 +1418,7 @@ dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of dnl the PLATFORM_TRIPLET that will be used in binary module extensions. AS_CASE([$ac_sys_system], @@ -4746,40 +12821,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) -@@ -1251,16 +1450,22 @@ - [wasm32-unknown-wasip1/clang], [PY_SUPPORT_TIER=2], dnl WebAssembly System Interface preview1, clang - [x86_64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], dnl Linux on AMD64, any vendor, glibc, clang - -- [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC -- [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc -- [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang -- [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc -- [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 -- [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 -- [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 -- [aarch64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 -- [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 -- [wasm32-*-emscripten/emcc], [PY_SUPPORT_TIER=3], dnl Emscripten -+ [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC -+ [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc -+ [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang -+ [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc -+ [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 -+ [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 -+ [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 -+ [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl tvOS Simulator on arm64 -+ [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl tvOS on ARM64 -+ [aarch64-apple-xros*-simulator/clang], [PY_SUPPORT_TIER=3], dnl visionOS Simulator on arm64 -+ [aarch64-apple-xros*/clang], [PY_SUPPORT_TIER=3], dnl visionOS on ARM64 -+ [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl watchOS Simulator on arm64 -+ [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl watchOS on ARM64 -+ [aarch64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 -+ [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 -+ [wasm32-*-emscripten/emcc], [PY_SUPPORT_TIER=3], dnl Emscripten - - [PY_SUPPORT_TIER=0] - ) -@@ -1579,7 +1784,7 @@ +@@ -1608,7 +1801,7 @@ case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; @@ -4788,16 +12830,16 @@ index ed8b53dd3a4..39fb6e82fb1 100644 LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; -@@ -1644,7 +1849,7 @@ +@@ -1673,7 +1866,7 @@ BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; - iOS) + iOS|tvOS|visionOS|watchOS) - LDLIBRARY='libpython$(LDVERSION).dylib' - ;; - AIX*) -@@ -3652,7 +3857,7 @@ + if test -z "$PYTHONFRAMEWORK"; then + AC_MSG_ERROR([iOS shared builds must use --enable-framework; an iOS app can only load a signed framework, never a bare dylib]) + fi +@@ -3704,7 +3897,7 @@ BLDSHARED="$LDSHARED" fi ;; @@ -4806,7 +12848,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" -@@ -3776,7 +3981,7 @@ +@@ -3828,7 +4021,7 @@ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys @@ -4815,16 +12857,16 @@ index ed8b53dd3a4..39fb6e82fb1 100644 LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -3800,7 +4005,7 @@ +@@ -3852,7 +4045,7 @@ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" - elif test $ac_sys_system = "iOS"; then + elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS"; then - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' - fi - ;; -@@ -4308,7 +4513,7 @@ + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + + if test "$enable_framework"; then +@@ -4425,7 +4618,7 @@ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], @@ -4833,7 +12875,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 ctypes_malloc_closure=yes ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] -@@ -5487,9 +5692,9 @@ +@@ -5553,9 +5746,9 @@ AC_CHECK_FUNCS([ \ accept4 alarm bind_textdomain_codeset chmod chown clearenv \ clock closefrom close_range confstr \ @@ -4842,20 +12884,20 @@ index ed8b53dd3a4..39fb6e82fb1 100644 faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ - fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ + fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ + gai_strerror getdents64 getegid geteuid getgid getgrent getgrgid getgrgid_r \ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin getlogin_r \ getpeername getpgid getpid getppid getpriority _getpty \ -@@ -5497,8 +5702,7 @@ - getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ +@@ -5565,8 +5758,7 @@ lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ - mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ + mknod mknodat mktime mmap mremap nice openat opendir pathconf pause \ + pidfd_open pidfd_getfd pidfd_send_signal pipe \ - plock poll ppoll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ - posix_spawn_file_actions_addclosefrom_np \ + plock poll ppoll posix_fadvise posix_fallocate posix_openpt \ pread preadv preadv2 process_vm_readv \ pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ pthread_kill pthread_get_name_np pthread_getname_np pthread_set_name_np \ -@@ -5508,7 +5712,7 @@ +@@ -5576,7 +5768,7 @@ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ @@ -4864,7 +12906,15 @@ index ed8b53dd3a4..39fb6e82fb1 100644 sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ sysconf sysctlbyname tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ -@@ -5529,12 +5733,26 @@ +@@ -5584,6 +5776,7 @@ + wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ + ]) + ++PY_CHECK_FUNC_PRIVATE([freadlink], [@%:@include ]) + PY_CHECK_FUNC_PRIVATE([strerror_r], [@%:@include ]) + + +@@ -5600,12 +5793,35 @@ AC_CHECK_FUNCS([lchmod]) fi @@ -4875,15 +12925,24 @@ index ed8b53dd3a4..39fb6e82fb1 100644 # raise an error if used at runtime. Force these symbols off. -if test "$ac_sys_system" != "iOS" ; then - AC_CHECK_FUNCS([dup3 getentropy getgroups pipe2 system]) -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" ; then -+ AC_CHECK_FUNCS([getentropy getgroups system]) -+fi -+# In addition, macOS introduced dup3 and pipe2 in macOS 27, late in the -+# lifetime of this version of Python. Rather than adding weaklinking -+# support for them, just continue to ignore them. -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "Darwin"; then -+ AC_CHECK_FUNCS([dup3 pipe2]) -+fi ++# In particular, dup3 and pipe2 are available as of iOS/tvOS/watchOS/visionOS 27 but were ++# incorrectly detected in older simulator versions. ++case "$ac_sys_system" in ++ iOS|tvOS|watchOS|visionOS) ++ AC_MSG_CHECKING([if iOS SDK is older than 27]) ++ ios_sdk_version=$(xcrun --sdk iphoneos --show-sdk-version 2>/dev/null | cut -d. -f1) ++ if test -n "$ios_sdk_version" && test "$ios_sdk_version" -ge 27; then ++ AC_MSG_RESULT([no]) ++ AC_CHECK_FUNCS([dup3 pipe2]) ++ else ++ AC_MSG_RESULT([yes]) ++ fi ++ ;; ++ *) ++ # not iOS/tvOS/watchOS/visionOS ++ AC_CHECK_FUNCS([dup3 getentropy getgroups pipe2 system]) ++ ;; ++esac + +# tvOS/watchOS have some additional methods that can be found, but not used. +if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then @@ -4894,7 +12953,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 fi AC_CHECK_DECL([dirfd], -@@ -5861,20 +6079,22 @@ +@@ -5999,20 +6215,22 @@ @%:@include ]) ]) # check for openpty, login_tty, and forkpty @@ -4931,7 +12990,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 # check for long file support functions AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs]) -@@ -5913,10 +6133,10 @@ +@@ -6051,10 +6269,10 @@ ]) ]) @@ -4944,7 +13003,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 then AC_CHECK_FUNCS([clock_settime], [], [ AC_CHECK_LIB([rt], [clock_settime], [ -@@ -6074,7 +6294,7 @@ +@@ -6212,7 +6430,7 @@ [ac_cv_buggy_getaddrinfo=no], [ac_cv_buggy_getaddrinfo=yes], [ @@ -4953,18 +13012,18 @@ index ed8b53dd3a4..39fb6e82fb1 100644 ac_cv_buggy_getaddrinfo="no" elif test "${enable_ipv6+set}" = set; then ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" -@@ -6678,8 +6898,8 @@ +@@ -6824,8 +7042,8 @@ LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" fi --# On iOS the shared libraries must be linked with the Python framework --if test "$ac_sys_system" = "iOS"; then -+# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework -+if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "visionOS" -o $ac_sys_system = "watchOS"; then +-# On iOS the shared libraries must be linked with the framework, when built +-if test "$ac_sys_system" = "iOS" && test "$enable_framework"; then ++# On iOS/tvOS/watchOS the shared libraries must be linked with the framework, when built ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS" && test "$enable_framework"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi -@@ -7352,7 +7572,7 @@ +@@ -7576,7 +7794,7 @@ dnl NOTE: Inform user how to proceed with files when cross compiling. dnl Some cross-compile builds are predictable; they won't ever dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. @@ -4973,7 +13032,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else -@@ -7681,7 +7901,7 @@ +@@ -7904,7 +8122,7 @@ AS_CASE([$ac_sys_system], [Emscripten], [with_ensurepip=no], [WASI], [with_ensurepip=no], @@ -4982,7 +13041,7 @@ index ed8b53dd3a4..39fb6e82fb1 100644 [with_ensurepip=upgrade] ) ]) -@@ -8069,6 +8289,9 @@ +@@ -8335,6 +8553,9 @@ NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268 Darwin) _PYTHREAD_NAME_MAXLEN=63;; iOS) _PYTHREAD_NAME_MAXLEN=63;; @@ -4991,8 +13050,8 @@ index ed8b53dd3a4..39fb6e82fb1 100644 + watchOS) _PYTHREAD_NAME_MAXLEN=63;; FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 - *) _PYTHREAD_NAME_MAXLEN=;; -@@ -8093,7 +8316,7 @@ + CYGWIN*) _PYTHREAD_NAME_MAXLEN=16;; +@@ -8360,7 +8581,7 @@ [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], dnl The _scproxy module is available on macOS [Darwin], [], @@ -5001,3 +13060,17 @@ index ed8b53dd3a4..39fb6e82fb1 100644 dnl subprocess and multiprocessing are not supported (no fork syscall). dnl curses and tkinter user interface are not available. dnl gdbm and nis aren't available +diff --git a/pyconfig.h.in b/pyconfig.h.in +index 08306a1dc8b..65e235c8fd6 100644 +--- a/pyconfig.h.in ++++ b/pyconfig.h.in +@@ -2201,6 +2201,9 @@ + /* Defined if _Float16 C type is supported */ + #undef _Py_HAVE_FLOAT16 + ++/* Define if you have the 'freadlink' function. */ ++#undef _Py_HAVE_FREADLINK ++ + /* Define if you have a working iconv() function. */ + #undef _Py_HAVE_ICONV + From a28d8d02351f38149d26f15dc8acee5d5c1b2d34 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 21 Oct 2026 15:21:53 -0700 Subject: [PATCH 2/3] Ensure watchOS testbed is included. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 030c8640..9906e03b 100644 --- a/Makefile +++ b/Makefile @@ -709,7 +709,7 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ # Disable dSYM production (for now) # $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/Python.dSYM $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) -ifeq ($(filter $(os),iOS tvOS visionOS),$(os)) +ifeq ($(filter $(os),iOS tvOS visionOS watchOS),$(os)) @echo ">>> Clone testbed project for $(os)" $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Platforms/Apple/testbed clone --platform $(os) --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed endif From 519621d8508b9691499e6f8262803b08659b62a2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 21 Oct 2026 15:27:41 -0700 Subject: [PATCH 3/3] Simplified briefcase test. --- .github/workflows/ci.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2eb05781..79fe84d2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -146,9 +146,6 @@ jobs: fail-fast: false matrix: platform: ["macOS", "iOS"] - include: - - briefcase-run-args: - - platform: iOS steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -190,7 +187,7 @@ jobs: PYTHON_VER: ${{ needs.config.outputs.PYTHON_VER }} BUILD_NUMBER: ${{ needs.config.outputs.BUILD_NUMBER }} run: | - briefcase run ${{ matrix.platform }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${PYTHON_VER}-${{ matrix.platform }}-support.${BUILD_NUMBER}.tar.gz\' + briefcase run ${{ matrix.platform }} Xcode --test -C support_package=\'../dist/Python-${PYTHON_VER}-${{ matrix.platform }}-support.${BUILD_NUMBER}.tar.gz\' cpython-testbed: name: CPython testbed (${{ matrix.platform }})